..
This commit is contained in:
@@ -49,6 +49,7 @@ RoleConstant {
|
||||
TEACHER_CONGRESS_DELEGATE_ATTENDANCE("教代会列席代表"),
|
||||
TEACHER_CONGRESS_DELEGATE_SPECIALLY_INVITE("教代会特邀代表"),
|
||||
TEACHER_CONGRESS_DELEGATION_HEAD("教代会代表团团长"),
|
||||
TEACHER_CONGRESS_VICE_DELEGATION_HEAD("教代会代表团副团长"),
|
||||
|
||||
PROPOSAL_COMMITTEE_DIRECTOR("提案委员会主任"),
|
||||
PROPOSAL_COMMITTEE_DEPUTY_DIRECTOR("提案委员会副主任"),
|
||||
|
||||
@@ -81,4 +81,26 @@ public class SysHomeActivityController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("sys.homeActivity")
|
||||
@ApiOperation("推送大图")
|
||||
public Result push(@Valid String id){
|
||||
Sys_home_activity sysHomeActivity = new Sys_home_activity();
|
||||
sysHomeActivity.setId(id);
|
||||
sysHomeActivity.setPush(true);
|
||||
sysHomeActivityService.updateIgnoreNull(sysHomeActivity);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("sys.homeActivity")
|
||||
@ApiOperation("取消推送大图")
|
||||
public Result cancelPush(@Valid String id){
|
||||
Sys_home_activity sysHomeActivity = new Sys_home_activity();
|
||||
sysHomeActivity.setId(id);
|
||||
sysHomeActivity.setPush(false);
|
||||
sysHomeActivityService.updateIgnoreNull(sysHomeActivity);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.budwk.app.base.constant.RedisConstant;
|
||||
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_home_activity;
|
||||
import com.budwk.app.sys.models.Sys_menu;
|
||||
@@ -29,7 +27,6 @@ import org.nutz.dao.util.Daos;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -37,17 +34,16 @@ import org.nutz.log.Log;
|
||||
import org.nutz.log.Logs;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
import org.nutz.plugins.wkcache.annotation.CacheDefaults;
|
||||
import org.nutz.plugins.wkcache.annotation.CacheResult;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@IocBean
|
||||
@@ -148,7 +144,7 @@ public class SysHomeController {
|
||||
@SaCheckLogin
|
||||
@ApiOperation("电脑端推荐应用")
|
||||
@Ok("json")
|
||||
public Result listRecommendApp(String platform) {
|
||||
public Result listRecommendApp(@Param(value = "platform", df = "PC") String platform) {
|
||||
List<Sys_menu> list = sysMenuService.query(Cnd.where(Sys_menu::getIsRecommendApp, "=", 1)
|
||||
.and(Sys_menu::getDisabled, "=", 0)
|
||||
.and(Sys_menu::getShowit, "=", 1)
|
||||
@@ -160,6 +156,23 @@ public class SysHomeController {
|
||||
return Result.success(sysMenus);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("电脑端推荐服务")
|
||||
@Ok("json")
|
||||
public Result listRecommendService(@Param(value = "platform", df = "PC") String platform) {
|
||||
List<Sys_menu> list = sysMenuService.query(Cnd.where(Sys_menu::getIsRecommendService, "=", 1)
|
||||
.and(Sys_menu::getDisabled, "=", 0)
|
||||
.and(Sys_menu::getShowit, "=", 1)
|
||||
.and(Sys_menu::getPlatform, "=", platform)
|
||||
);
|
||||
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
|
||||
List<String> allMenuIds = menus.stream().map(Sys_menu::getId).toList();
|
||||
List<Sys_menu> sysMenus = list.stream().filter(m -> allMenuIds.contains(m.getId())).sorted(Comparator.comparingInt(Sys_menu::getLocation)).toList();
|
||||
return Result.success(sysMenus);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("首页推送活动")
|
||||
|
||||
@@ -28,6 +28,11 @@ public class Sys_home_activity extends BaseModel {
|
||||
@Comment("活动封面")
|
||||
private String cover;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
@Comment("活动内容")
|
||||
private String content;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 1000)
|
||||
@Comment("活动链接")
|
||||
@@ -65,6 +70,12 @@ public class Sys_home_activity extends BaseModel {
|
||||
@Default("0")
|
||||
private Boolean top;
|
||||
|
||||
@Column
|
||||
@Comment("是否推送大图")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean push;
|
||||
|
||||
@Column
|
||||
@Comment("排序号")
|
||||
@Default("0")
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.budwk.app.zhgh.dayofficework.edu.controller;
|
||||
|
||||
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;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.models.EduCourses;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.service.EduCoursesService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@At("/platform/edu/personRank")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "个人排行榜")
|
||||
public class EduPersonRankController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private EduCoursesService eduCoursesService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/edu/personRank/index.html")
|
||||
@SaCheckPermission("edu.person.rank")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/courses")
|
||||
@ApiOperation("获取课程列表")
|
||||
@SaCheckPermission("edu.person.rank")
|
||||
public Result getCourses() {
|
||||
List<EduCourses> courses = dao.query(EduCourses.class, Cnd.where(EduCourses::getDisabled, "=", 0).desc(EduCourses::getCreatedAt));
|
||||
return Result.success(courses);
|
||||
}
|
||||
|
||||
@At("/ranking")
|
||||
@ApiOperation("获取排行榜数据")
|
||||
@SaCheckPermission("edu.person.rank")
|
||||
public Result getRanking(@Param("courseId") String courseId,
|
||||
@Param(value = "rankType", df = "watchTime") String rankType,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate,
|
||||
PageForm pageForm) {
|
||||
// 学习总时长排序
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
r.userId,
|
||||
u.loginname,
|
||||
u.username,
|
||||
u.unitId,
|
||||
u.unionId,
|
||||
u.unitName,
|
||||
u.unionName,
|
||||
SUM(r.watchedDuration) AS totalWatchTime
|
||||
FROM
|
||||
edu_study_records r
|
||||
INNER JOIN vw_user u ON r.userId = u.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("r.courseId", "=", courseId);
|
||||
cnd.andEX("r.lastWatchedTime", ">=", startDate);
|
||||
cnd.andEX("r.lastWatchedTime", "<=", endDate);
|
||||
cnd.groupBy("r.userId");
|
||||
cnd.having(Cnd.where("totalWatchTime", ">", 0));
|
||||
cnd.orderBy("totalWatchTime", "desc");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = eduCoursesService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package com.budwk.app.zhgh.dayofficework.edu.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.edu.models.EduCourses;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.service.EduCoursesService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@At("/platform/edu/unionRank")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "工会排行榜")
|
||||
public class EduUnionRankController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private EduCoursesService eduCoursesService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/edu/unionRank/index.html")
|
||||
@SaCheckPermission("edu.union.rank")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/courses")
|
||||
@ApiOperation("获取课程列表")
|
||||
@SaCheckPermission("edu.union.rank")
|
||||
public Result getCourses() {
|
||||
List<EduCourses> courses = dao.query(EduCourses.class, Cnd.where(EduCourses::getDisabled, "=", 0).desc(EduCourses::getCreatedAt));
|
||||
return Result.success(courses);
|
||||
}
|
||||
|
||||
@At("/ranking")
|
||||
@ApiOperation("获取排行榜数据")
|
||||
@SaCheckPermission("edu.union.rank")
|
||||
public Result getRanking(@Param("courseId") String courseId,
|
||||
@Param(value = "rankType", df = "watchTime") String rankType,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate,
|
||||
PageForm pageForm) {
|
||||
// 学习总时长排序
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
su.id AS unionId,
|
||||
su.name AS unionName,
|
||||
su.unionCode,
|
||||
COALESCE(participant_count.participantCount, 0) AS participantCount
|
||||
FROM
|
||||
sys_union su
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
u.unionId,
|
||||
COUNT(DISTINCT esr.userId) AS participantCount
|
||||
FROM
|
||||
edu_study_records esr
|
||||
INNER JOIN vw_user u ON esr.userId = u.id
|
||||
WHERE
|
||||
esr.watchedDuration > 0
|
||||
AND u.unionId IS NOT NULL
|
||||
$courseId
|
||||
GROUP BY
|
||||
u.unionId) participant_count ON su.id = participant_count.unionId
|
||||
ORDER BY
|
||||
participantCount DESC,
|
||||
su.NAME
|
||||
""");
|
||||
if(StrUtil.isNotBlank(courseId)){
|
||||
sql.setVar("courseId", "and esr.courseId='" + courseId + "'");
|
||||
}
|
||||
Pagination pagination = eduCoursesService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -172,7 +172,7 @@ public class H5EduController {
|
||||
}
|
||||
|
||||
@At("/study/history")
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/edu/study/history.html")
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/edu/history/index.html")
|
||||
@SaCheckLogin
|
||||
@ApiOperation("学习历史页面")
|
||||
public void studyHistory() {
|
||||
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package com.budwk.app.zhgh.dayofficework.edu.h5controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
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.EduStudyRecords;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.models.EduVideos;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.service.EduCoursesService;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.service.EduStudyRecordsService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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 java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@At("/platform/h5/edu/studyhis")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "移动端学习历史统计")
|
||||
public class H5EduStudyHisController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private EduCoursesService eduCoursesService;
|
||||
@Inject
|
||||
private EduStudyRecordsService eduStudyRecordsService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/edu/studyhis/index.html")
|
||||
@SaCheckLogin
|
||||
@ApiOperation("学习历史统计页面")
|
||||
public void studyHistoryPage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学习统计数据
|
||||
*/
|
||||
@At("/stats")
|
||||
@SaCheckLogin
|
||||
@ApiOperation("获取学习统计数据")
|
||||
public Result getStudyStats() {
|
||||
try {
|
||||
// 获取用户学习的课程总数
|
||||
Sql courseCountSql = Sqls.create("SELECT COUNT(DISTINCT courseId) as total_courses FROM edu_study_records WHERE userId = @userId");
|
||||
courseCountSql.params().set("userId", SecurityUtil.getUserId());
|
||||
courseCountSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(courseCountSql);
|
||||
|
||||
int totalCourses = 0;
|
||||
if (!courseCountSql.getList(NutMap.class).isEmpty()) {
|
||||
Object count = courseCountSql.getList(NutMap.class).get(0).get("total_courses");
|
||||
totalCourses = count != null ? ((Number) count).intValue() : 0;
|
||||
}
|
||||
|
||||
// 获取用户完成的视频总数
|
||||
int completedVideos = dao.count(EduStudyRecords.class,
|
||||
Cnd.where("userId", "=", SecurityUtil.getUserId()).and("isCompleted", "=", true));
|
||||
|
||||
// 获取用户总学习时长(秒)
|
||||
Sql watchTimeSql = Sqls.create("SELECT COALESCE(SUM(watchedDuration), 0) as total_watch_time FROM edu_study_records WHERE userId = @userId");
|
||||
watchTimeSql.params().set("userId", SecurityUtil.getUserId());
|
||||
watchTimeSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(watchTimeSql);
|
||||
|
||||
int totalWatchTime = 0;
|
||||
if (!watchTimeSql.getList(NutMap.class).isEmpty()) {
|
||||
Object time = watchTimeSql.getList(NutMap.class).get(0).get("total_watch_time");
|
||||
totalWatchTime = time != null ? ((Number) time).intValue() : 0;
|
||||
}
|
||||
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalCourses", totalCourses);
|
||||
stats.put("completedVideos", completedVideos);
|
||||
stats.put("totalWatchTime", totalWatchTime);
|
||||
|
||||
return Result.success("获取成功", stats);
|
||||
} catch (Exception e) {
|
||||
log.error("获取学习统计数据失败", e);
|
||||
return Result.error("获取失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学习历史列表
|
||||
*/
|
||||
@At("/history")
|
||||
@SaCheckLogin
|
||||
@ApiOperation("获取学习历史列表")
|
||||
public Result getStudyHistory(@Param("filter") String filter) {
|
||||
try {
|
||||
String userId = SecurityUtil.getUserId();
|
||||
if (StrUtil.isBlank(userId)) {
|
||||
return Result.error("用户未登录");
|
||||
}
|
||||
|
||||
List<EduStudyRecords> historyList;
|
||||
if ("completed".equals(filter)) {
|
||||
// 获取已完成的学习记录
|
||||
historyList = dao.query(EduStudyRecords.class,
|
||||
Cnd.where("userId", "=", userId)
|
||||
.and("isCompleted", "=", true)
|
||||
.orderBy("completedAt", "desc"));
|
||||
} else if ("inProgress".equals(filter)) {
|
||||
// 获取进行中的学习记录
|
||||
historyList = dao.query(EduStudyRecords.class,
|
||||
Cnd.where("userId", "=", userId)
|
||||
.and("isCompleted", "=", false)
|
||||
.and("watchedDuration", ">", 0)
|
||||
.orderBy("updatedAt", "desc"));
|
||||
} else {
|
||||
// 获取所有学习记录
|
||||
historyList = dao.query(EduStudyRecords.class,
|
||||
Cnd.where("userId", "=", userId)
|
||||
.orderBy("updatedAt", "desc"));
|
||||
}
|
||||
|
||||
return Result.success("获取成功", historyList);
|
||||
} catch (Exception e) {
|
||||
log.error("获取学习历史失败", e);
|
||||
return Result.error("获取失败");
|
||||
}
|
||||
}
|
||||
|
||||
@At("/progress/detail")
|
||||
@SaCheckLogin
|
||||
@ApiOperation("获取课程详细进度")
|
||||
public Result getCourseProgressDetail(@Param("courseId") String courseId) {
|
||||
try {
|
||||
if (StrUtil.isBlank(courseId)) {
|
||||
return Result.error("课程ID不能为空");
|
||||
}
|
||||
|
||||
String userId = SecurityUtil.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.error("用户未登录");
|
||||
}
|
||||
|
||||
// 获取课程信息
|
||||
EduCourses course = dao.fetch(EduCourses.class, courseId);
|
||||
if (course == null) {
|
||||
return Result.error("课程不存在");
|
||||
}
|
||||
|
||||
// 获取课程章节和视频
|
||||
List<EduChapters> chapters = dao.query(EduChapters.class,
|
||||
Cnd.where("courseId", "=", courseId).orderBy("sortCode", "asc"));
|
||||
|
||||
List<Map<String, Object>> chapterList = new ArrayList<>();
|
||||
int totalVideos = 0;
|
||||
int completedVideos = 0;
|
||||
int totalWatchTime = 0;
|
||||
|
||||
for (EduChapters chapter : chapters) {
|
||||
List<EduVideos> videos = dao.query(EduVideos.class,
|
||||
Cnd.where("chapterId", "=", chapter.getId()).orderBy("sortCode", "asc"));
|
||||
|
||||
List<Map<String, Object>> videoList = new ArrayList<>();
|
||||
|
||||
for (EduVideos video : videos) {
|
||||
totalVideos++;
|
||||
|
||||
// 获取学习记录
|
||||
EduStudyRecords record = dao.fetch(EduStudyRecords.class,
|
||||
Cnd.where("userId", "=", userId)
|
||||
.and("videoId", "=", video.getId())
|
||||
.and("courseId", "=", courseId));
|
||||
|
||||
boolean isCompleted = record != null && record.getIsCompleted();
|
||||
if (isCompleted) completedVideos++;
|
||||
|
||||
int watchDuration = record != null ? (record.getWatchedDuration() != null ? record.getWatchedDuration() : 0) : 0;
|
||||
totalWatchTime += watchDuration;
|
||||
|
||||
Map<String, Object> videoInfo = new HashMap<>();
|
||||
videoInfo.put("videoId", video.getId());
|
||||
videoInfo.put("title", video.getTitle());
|
||||
videoInfo.put("duration", video.getDuration());
|
||||
videoInfo.put("isCompleted", isCompleted);
|
||||
videoInfo.put("watchDuration", watchDuration);
|
||||
videoInfo.put("lastWatchTime", record != null ? record.getLastWatchedTime() : null);
|
||||
|
||||
videoList.add(videoInfo);
|
||||
}
|
||||
|
||||
Map<String, Object> chapterInfo = new HashMap<>();
|
||||
chapterInfo.put("chapterId", chapter.getId());
|
||||
chapterInfo.put("title", chapter.getTitle());
|
||||
chapterInfo.put("videos", videoList);
|
||||
|
||||
chapterList.add(chapterInfo);
|
||||
}
|
||||
|
||||
Map<String, Object> progressDetail = new HashMap<>();
|
||||
progressDetail.put("course", course);
|
||||
progressDetail.put("chapters", chapterList);
|
||||
progressDetail.put("totalVideos", totalVideos);
|
||||
progressDetail.put("completedVideos", completedVideos);
|
||||
progressDetail.put("totalWatchTime", totalWatchTime);
|
||||
progressDetail.put("completionRate", totalVideos > 0 ? (double) completedVideos / totalVideos * 100 : 0);
|
||||
|
||||
return Result.success(progressDetail);
|
||||
} catch (Exception e) {
|
||||
log.error("获取课程详细进度失败", e);
|
||||
return Result.error("获取课程详细进度失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -25,6 +25,26 @@ public class EduStudyRecords extends BaseModel {
|
||||
@Column
|
||||
private String userId;
|
||||
|
||||
@Comment("单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@Column
|
||||
private String unitId;
|
||||
|
||||
@Comment("单位名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
@Column
|
||||
private String unitName;
|
||||
|
||||
@Comment("工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@Column
|
||||
private String unionId;
|
||||
|
||||
@Comment("工会名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
@Column
|
||||
private String unionName;
|
||||
|
||||
@Comment("视频ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@Column
|
||||
|
||||
@@ -16,6 +16,9 @@ import java.util.List;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@TableIndexes(value = {
|
||||
@Index(name = "INDEX_GLOBAL_MESSAGE_STATUS", fields = "status", unique = false)
|
||||
})
|
||||
@Comment("全局消息")
|
||||
public class GlobalMessage extends BaseModel {
|
||||
|
||||
|
||||
+7
@@ -14,6 +14,13 @@ import java.util.Date;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@TableIndexes(value =
|
||||
{
|
||||
@Index(name = "INDEX_GLOBAL_MESSAGE_RECEIVER_MESSAGEID", fields = "messageId", unique = false),
|
||||
@Index(name = "INDEX_GLOBAL_MESSAGE_RECEIVER_RECEIVERID", fields = "receiverId", unique = false),
|
||||
@Index(name = "INDEX_GLOBAL_MESSAGE_RECEIVER_ISREAD", fields = "isRead", unique = false)
|
||||
}
|
||||
)
|
||||
@Comment("全局消息接收用户")
|
||||
public class GlobalMessageReceiver extends BaseModel {
|
||||
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.dashboard;
|
||||
|
||||
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.flow.engine.model.ProcessModel;
|
||||
import com.budwk.app.flow.engine.model.TaskModel;
|
||||
import com.budwk.app.flow.entity.ProcessDefine;
|
||||
import com.budwk.app.flow.service.ProcessDefineService;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/proposal/dashboard")
|
||||
@Api("提案数据看板")
|
||||
@Ok("json:full")
|
||||
public class ProposalDashboardController {
|
||||
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
@Inject
|
||||
private ProcessDefineService processDefineService;
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/dashboard/index.html")
|
||||
@SaCheckPermission("proposal.dashboard")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.dashboard")
|
||||
public Result listNode(@Param("sessionId") String sessionId) {
|
||||
List<NutMap> nodes = new ArrayList<>();
|
||||
|
||||
// 提案总数
|
||||
nodes.add(NutMap.NEW().addv("name", "提案总数").addv("id", "total").addv("type", "total").addv("count", 0));
|
||||
|
||||
// 节点
|
||||
ProcessDefine define = processDefineService.getLastByName("JDHTA");
|
||||
ProcessModel processModel = processDefineService.processDefineToModel(define);
|
||||
List<TaskModel> taskModels = processModel.getTasks();
|
||||
|
||||
List<String> excludeNodes = new ArrayList<>(3);
|
||||
excludeNodes.add("撰写提案");
|
||||
excludeNodes.add("邀请附议人");
|
||||
excludeNodes.add("提案附议");
|
||||
|
||||
List<NutMap> taskNodes = taskModels.stream().filter(taskModel -> !excludeNodes.contains(taskModel.getDisplayName()))
|
||||
.map(taskModel -> NutMap.NEW()
|
||||
.addv("name", taskModel.getDisplayName())
|
||||
.addv("id", taskModel.getName())
|
||||
.addv("type", "task")
|
||||
.addv("count", 0)).toList();
|
||||
nodes.addAll(taskNodes);
|
||||
|
||||
// 立案结果
|
||||
List<Sys_dict> caseFilingResult = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT");
|
||||
for (Sys_dict dict : caseFilingResult) {
|
||||
nodes.add(NutMap.NEW()
|
||||
.addv("name", dict.getName())
|
||||
.addv("id", dict.getCode())
|
||||
.addv("type", "caseFilingResult")
|
||||
.addv("count", 0));
|
||||
}
|
||||
|
||||
// 查询待办任务
|
||||
Sql todoSql = Sqls.create("""
|
||||
SELECT
|
||||
t.taskName
|
||||
FROM
|
||||
wf_process_task t
|
||||
INNER JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
INNER JOIN proposal_info info ON info.id = ins.businessNo
|
||||
WHERE
|
||||
t.taskState = 10
|
||||
AND info.sessionId = @sessionId
|
||||
""");
|
||||
todoSql.setParam("sessionId", sessionId);
|
||||
List<NutMap> todoTasks = processDefineService.listMap(todoSql);
|
||||
|
||||
for (NutMap node : nodes) {
|
||||
if (node.getString("type").equals("task")) {
|
||||
long count = todoTasks.stream().filter(task -> task.getString("taskName").equals(node.getString("id"))).count();
|
||||
node.put("count", count);
|
||||
} else if (node.getString("type").equals("total")) {
|
||||
int count = dao.count(ProposalInfo.class, Cnd.where(ProposalInfo::getSessionId, "=", sessionId));
|
||||
node.put("count", count);
|
||||
} else if (node.getString("type").equals("caseFilingResult")) {
|
||||
int count = dao.count(ProposalInfo.class, Cnd.where(ProposalInfo::getSessionId, "=", sessionId).and(ProposalInfo::getCaseFilingResult, "=", node.getString("id")));
|
||||
node.put("count", count);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(nodes);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.dashboard")
|
||||
public Result pageData(PageForm pageForm, String sessionId, String selectNodeId, String selectNodeType) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
COUNT(p.consolidationIds) > 0 AS isConsolidation,
|
||||
type.name AS typeName,
|
||||
tcs.fullName AS sessionName,
|
||||
tcd.`name` AS delegationName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.displayName curTaskName,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(caseTasks.variable, '$.tf_hostUnitName')) AS masterUnitName,
|
||||
caseTasks.variable->>'$.tf_helpUnitNameStr' AS slaveUnitNames
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN proposal_consolidation p ON JSON_CONTAINS(p.consolidationIds, JSON_QUOTE(info.id))
|
||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
|
||||
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
|
||||
LEFT JOIN teacher_congress_delegate tcde on tcde.loginName = info.createUserLoginName
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||
LEFT JOIN (SELECT processInstanceId, MAX(finishTime) AS finishTime FROM wf_process_task WHERE taskName = '9846ab38-40c5-4093-bafc-a9b3b443338b' AND taskState = 20 GROUP BY processInstanceId) latestTasks ON latestTasks.processInstanceId = ins.id
|
||||
LEFT JOIN wf_process_task caseTasks ON caseTasks.processInstanceId = ins.id AND caseTasks.taskName = '9846ab38-40c5-4093-bafc-a9b3b443338b' AND caseTasks.taskState = 20 AND caseTasks.finishTime = latestTasks.finishTime
|
||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("info.sessionId", "=", sessionId);
|
||||
cnd.groupBy("info.id");
|
||||
|
||||
if(StrUtil.isNotBlank(selectNodeType) && StrUtil.isNotBlank(selectNodeId)){
|
||||
switch (selectNodeType){
|
||||
case "task":
|
||||
cnd.and("t.taskName", "=", selectNodeId);
|
||||
break;
|
||||
case "total":
|
||||
break;
|
||||
case "caseFilingResult":
|
||||
cnd.and("info.caseFilingResult", "=", selectNodeId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+45
@@ -120,6 +120,51 @@ public class ProposalMineController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.mine")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result selectOne(@Valid String id){
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
type.name AS typeName,
|
||||
tcs.fullName AS sessionName,
|
||||
tcd.`name` AS delegationName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.id taskId,
|
||||
t.taskName AS taskKey,
|
||||
t.displayName taskName,
|
||||
t.taskType,
|
||||
t.performType taskPerformType,
|
||||
t.taskState,
|
||||
t.finishTime,
|
||||
t.taskParentId,
|
||||
t.variable taskVariable,
|
||||
IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = t.taskParentId) = 'startTask', 1, 0) canRevoke,
|
||||
(select MAX(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask' AND taskState in (10,20)) AS startTaskId
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
|
||||
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
|
||||
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
|
||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("info.id", "=", id);
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
NutMap data = proposalCommonService.fetchMap(sql);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.mine")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.transact;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
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.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/proposal/vice/delegation")
|
||||
@Slf4j
|
||||
@Ok("json:full")
|
||||
@Api(tags = "提案-办理-副团长查阅")
|
||||
public class ProposalViceDelegationController {
|
||||
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/viceDelegation/index.html")
|
||||
@SaCheckPermission("proposal.vice.delegation")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/viceDelegation/index.html")
|
||||
@SaCheckPermission("proposal.vice.delegation")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.vice.delegation")
|
||||
@ApiOperation("分页列表")
|
||||
public Result pageData(@Valid ProposalSearchParam pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
type.NAME AS typeName,
|
||||
tcs.fullName AS sessionName,
|
||||
tcd.`name` AS delegationName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.id taskId,
|
||||
t.taskName AS taskKey,
|
||||
t.displayName taskName,
|
||||
t.taskType,
|
||||
t.performType taskPerformType,
|
||||
t.taskState,
|
||||
t.finishTime,
|
||||
t.taskParentId
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN proposal_type type ON type.id = info.typeId
|
||||
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
|
||||
LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId
|
||||
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
|
||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id\s
|
||||
AND t.taskState = 10
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
|
||||
cnd.and("info.delegationId", "in", proposalCommonService.getSelfManageDelegationIds());
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -129,8 +129,8 @@ public class ProposalInfo extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Comment("建议落实部门")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String implementUnitName;
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<String> suggestUnits;
|
||||
|
||||
@Column
|
||||
@Comment("提案状态")
|
||||
|
||||
+2
-1
@@ -457,7 +457,8 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
@Override
|
||||
public List<String> getSelfManageDelegationIds() {
|
||||
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD.name());
|
||||
List<Sys_user_role> sysUserRoles = dao().query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", role.getId())
|
||||
Sys_role viceRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD.name());
|
||||
List<Sys_user_role> sysUserRoles = dao().query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "in", List.of(role.getId(), viceRole.getId()))
|
||||
.and(Sys_user_role::getUserId, "=", SecurityUtil.getUserId()));
|
||||
List<String> delegationIds = sysUserRoles.stream()
|
||||
.map(Sys_user_role::getTcDelegationId)
|
||||
|
||||
+31
-17
@@ -18,7 +18,9 @@ import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_unit;
|
||||
import io.swagger.annotations.*;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -69,25 +71,25 @@ public class TeacherCongressDelegationController {
|
||||
@POST
|
||||
@SaCheckPermission("tc.delegation")
|
||||
@ApiOperation(value = "届次信息列表", httpMethod = "POST")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "sessionId", value = "届次ID", dataType = "String", paramType = "query"),
|
||||
@ApiImplicitParam(name = "delegationId", value = "代表团ID", dataType = "String", paramType = "query")
|
||||
})
|
||||
public Result pageData(@Valid @ApiParam(value = "分页表单") PageForm pageForm, @Valid @ApiParam(value = "届次ID") String sessionId) {
|
||||
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
Sys_role role2 = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
tcd.*,
|
||||
tcs.fullName as sessionName,
|
||||
GROUP_CONCAT(u.username,u.loginname) as delegationHead
|
||||
GROUP_CONCAT(u.username,u.loginname) as delegationHead,
|
||||
GROUP_CONCAT(u2.username,u2.loginname) as viceDelegationHead
|
||||
FROM
|
||||
teacher_congress_delegation tcd
|
||||
LEFT JOIN teacher_congress_session tcs ON tcs.id = tcd.sessionId
|
||||
LEFT JOIN sys_user_role sur ON sur.tcDelegationId = tcd.id
|
||||
AND sur.roleId = @roleId
|
||||
LEFT JOIN sys_user_role sur ON sur.tcDelegationId = tcd.id AND sur.roleId = @roleId
|
||||
LEFT JOIN sys_user u ON u.id = sur.userId
|
||||
LEFT JOIN sys_user_role sur2 ON sur2.tcDelegationId = tcd.id AND sur2.roleId = @roleId2
|
||||
LEFT JOIN sys_user u2 ON u2.id = sur2.userId
|
||||
$condition""");
|
||||
sql.setParam("roleId", role.getId());
|
||||
sql.setParam("roleId2", role2.getId());
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("tcd.sessionId", "=", sessionId);
|
||||
cnd.asc("tcd.code");
|
||||
@@ -241,8 +243,8 @@ public class TeacherCongressDelegationController {
|
||||
public Result notHeadUser(@Valid String sessionId, @Valid String delegationId, String keyWord) {
|
||||
Sql sql = Sqls.create("select userId,loginName,userName,unitName from teacher_congress_delegate $condition");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("delegationId","=",delegationId);
|
||||
cnd.and("sessionId","=",sessionId);
|
||||
cnd.and("delegationId", "=", delegationId);
|
||||
cnd.and("sessionId", "=", sessionId);
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.orLike("loginName", keyWord);
|
||||
seg.orLike("userName", keyWord);
|
||||
@@ -261,8 +263,14 @@ public class TeacherCongressDelegationController {
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("tc.delegation")
|
||||
public Result headUser(@Valid String sessionId, @Valid String delegationId) {
|
||||
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
public Result headUser(@Valid String sessionId, @Valid String delegationId, @Param(value = "type", df = "TEACHER_CONGRESS_DELEGATION_HEAD") String type) {
|
||||
Sys_role role = null;
|
||||
if (type.equals(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD.name())) {
|
||||
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
} else {
|
||||
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
|
||||
}
|
||||
|
||||
Record record = dao.fetch("sys_user_role", Cnd.where("tcSessionId", "=", sessionId).and("tcDelegationId", "=", delegationId).and("roleId", "=", role.getId()));
|
||||
String userId = Optional.ofNullable(record).map(r -> r.getString("userId")).orElse(null);
|
||||
|
||||
@@ -297,17 +305,23 @@ public class TeacherCongressDelegationController {
|
||||
@At
|
||||
@SaCheckPermission("tc.delegation")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result insertHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId) {
|
||||
public Result insertHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId, String viceUserId) {
|
||||
// 团长
|
||||
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
|
||||
//删除权限 只能有一个团长
|
||||
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId)
|
||||
.and(Sys_user_role::getTcSessionId, "=", sessionId)
|
||||
.and(Sys_user_role::getRoleId, "=", role.getId())
|
||||
);
|
||||
|
||||
//再加
|
||||
dao.insert("sys_user_role", Chain.make("userId", userId).add("roleId", role.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
|
||||
|
||||
// 副团长
|
||||
Sys_role role2 = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
|
||||
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId)
|
||||
.and(Sys_user_role::getTcSessionId, "=", sessionId)
|
||||
.and(Sys_user_role::getRoleId, "=", role2.getId())
|
||||
);
|
||||
dao.insert("sys_user_role", Chain.make("userId", viceUserId).add("roleId", role2.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
|
||||
|
||||
sysUserService.clearCache();
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@@ -221,7 +221,8 @@ const commonUtil = {
|
||||
})
|
||||
viewer.show()
|
||||
} else {
|
||||
this.$message.warning("暂不支持预览该文件,请下载后查看")
|
||||
// this.$message.warning("暂不支持预览该文件,请下载后查看")
|
||||
alert("暂不支持预览该文件,请下载后查看")
|
||||
}
|
||||
},
|
||||
|
||||
@@ -317,5 +318,15 @@ function base64ToFile(base64Data, filename) {
|
||||
return new File([blob], filename, {type: contentType})
|
||||
}
|
||||
|
||||
function createLoading(text= '加载中...'){
|
||||
return ELEMENT.Loading.service({
|
||||
lock: true,
|
||||
text: text,
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,278 +1,293 @@
|
||||
<template>
|
||||
<van-pull-refresh v-model="tableRefreshing" @refresh="onRefresh">
|
||||
<div v-if="tableData.length === 0" class="empty-state">
|
||||
<van-empty description="暂无数据"></van-empty>
|
||||
</div>
|
||||
<van-list v-if="tableData && tableData.length>0"
|
||||
v-model="tableLoading"
|
||||
:finished="tableFinished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad">
|
||||
<div class="table-list-container">
|
||||
<div v-for="(row, index) in tableData" :key="index" class="table-list-item">
|
||||
<slot name="header" :index="index" :row="row">
|
||||
<div class="item-header">
|
||||
<div class="item-title">{{ row[title] }}</div>
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<div style="display: flex;column-gap: 10px"
|
||||
:style="{'max-height':img ? '100px':'unset', 'overflow': img ? 'hidden':'unset' }">
|
||||
<div class="img-container" v-if="img">
|
||||
<img :src="row[img]" alt="" style="object-fit: cover">
|
||||
</div>
|
||||
<div class="">
|
||||
<slot :index="index" :row="row"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<slot name="actions" :index="index" :row="row"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<van-pull-refresh v-model="tableRefreshing" @refresh="onRefresh">
|
||||
<div v-if="tableData.length === 0" class="empty-state">
|
||||
<van-empty description="暂无数据"></van-empty>
|
||||
</div>
|
||||
<van-list v-if="tableData && tableData.length>0"
|
||||
v-model="tableLoading"
|
||||
:finished="tableFinished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad">
|
||||
<div class="table-list-container">
|
||||
<div v-for="(row, index) in tableData" :key="index" class="table-list-item">
|
||||
<slot name="header" :index="index" :row="row">
|
||||
<div class="item-header">
|
||||
<div class="item-title">{{ calcTitle(row) }}</div>
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</slot>
|
||||
|
||||
<div style="display: flex;column-gap: 10px"
|
||||
:style="{'max-height':img ? '100px':'unset', 'overflow': img ? 'hidden':'unset' }">
|
||||
<div class="img-container" v-if="img">
|
||||
<img :src="row[img]" alt="" style="object-fit: cover">
|
||||
</div>
|
||||
<div style="width: 100%">
|
||||
<slot :index="index" :row="row"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<slot name="actions" :index="index" :row="row"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</template>
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "TableList",
|
||||
props: {
|
||||
api: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
page_form: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => {
|
||||
return {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
searchKeyword: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
json: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
img: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
name: "TableList",
|
||||
props: {
|
||||
api: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
watch: {
|
||||
page_form: {
|
||||
handler(newVal, oldVal) {
|
||||
this.localPageForm = { ...newVal }
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
page_form: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => {
|
||||
return {
|
||||
tableData: [],
|
||||
tableLoading: false,
|
||||
tableFinished: false,
|
||||
tableRefreshing: false,
|
||||
localPageForm: { ...this.page_form }
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
searchKeyword: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onLoad() {
|
||||
if (this.tableFinished) return
|
||||
this.localPageForm.pageNumber++
|
||||
this.$emit("update:page_form", { ...this.localPageForm })
|
||||
this.pageData()
|
||||
},
|
||||
|
||||
pageData() {
|
||||
this.tableLoading = true
|
||||
|
||||
const loading = createListLoading()
|
||||
this.$axios.post(this.api, this.json ? ({ pageForm: JSON.stringify(this.localPageForm) }) : this.localPageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = this.tableData.concat(res.data.list)
|
||||
this.localPageForm.totalCount = res.data.totalCount
|
||||
if (this.tableData.length >= this.localPageForm.totalCount) {
|
||||
this.tableFinished = true
|
||||
}
|
||||
this.$emit("update:page_form", { ...this.localPageForm })
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
console.log(this.tableLoading)
|
||||
this.tableLoading = false
|
||||
console.log(this.tableLoading)
|
||||
this.tableRefreshing = false
|
||||
})
|
||||
},
|
||||
doSearch() {
|
||||
this.tableFinished = false
|
||||
this.tableData = []
|
||||
this.localPageForm.pageNumber = 1
|
||||
this.$emit("update:page_form", { ...this.localPageForm })
|
||||
this.pageData()
|
||||
},
|
||||
onRefresh() {
|
||||
this.localPageForm.pageNumber = 1
|
||||
this.$emit("update:page_form", { ...this.localPageForm })
|
||||
this.doSearch()
|
||||
}
|
||||
json: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mounted() {
|
||||
this.$emit("ready")
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
img: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
page_form: {
|
||||
handler(newVal, oldVal) {
|
||||
this.localPageForm = {...newVal}
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
tableLoading: false,
|
||||
tableFinished: false,
|
||||
tableRefreshing: false,
|
||||
localPageForm: {...this.page_form}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
calcTitle(row) {
|
||||
if (!this.title) return ""
|
||||
// 处理key1.key2.key3 这样的形式
|
||||
const keys = this.title.split(".")
|
||||
|
||||
let val = JSON.parse(JSON.stringify(row));
|
||||
for (let key of keys) {
|
||||
if (val == null || typeof val !== 'object') {
|
||||
return "";
|
||||
}
|
||||
val = val[key];
|
||||
}
|
||||
return val == null ? "" : val;
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
if (this.tableFinished) return
|
||||
this.localPageForm.pageNumber++
|
||||
this.$emit("update:page_form", {...this.localPageForm})
|
||||
this.pageData()
|
||||
},
|
||||
|
||||
pageData() {
|
||||
this.tableLoading = true
|
||||
|
||||
const loading = createListLoading()
|
||||
this.$axios.post(this.api, this.json ? ({pageForm: JSON.stringify(this.localPageForm)}) : this.localPageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = this.tableData.concat(res.data.list)
|
||||
this.localPageForm.totalCount = res.data.totalCount
|
||||
if (this.tableData.length >= this.localPageForm.totalCount) {
|
||||
this.tableFinished = true
|
||||
}
|
||||
this.$emit("update:page_form", {...this.localPageForm})
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
console.log(this.tableLoading)
|
||||
this.tableLoading = false
|
||||
console.log(this.tableLoading)
|
||||
this.tableRefreshing = false
|
||||
})
|
||||
},
|
||||
doSearch() {
|
||||
this.tableFinished = false
|
||||
this.tableData = []
|
||||
this.localPageForm.pageNumber = 1
|
||||
this.$emit("update:page_form", {...this.localPageForm})
|
||||
this.pageData()
|
||||
},
|
||||
onRefresh() {
|
||||
this.localPageForm.pageNumber = 1
|
||||
this.$emit("update:page_form", {...this.localPageForm})
|
||||
this.doSearch()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit("ready")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-list-container {
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .item-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #1f2f3d;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
padding-right: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #1f2f3d;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .img-container {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 6px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .img-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
|
||||
.table-list-container .table-list-item .item-meta {
|
||||
display: flex;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .meta-item i {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .item-content {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 1.5;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .item-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed #eaecef;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed #eaecef;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .item-actions {
|
||||
display: flex;
|
||||
column-gap: 20px;
|
||||
justify-content: end;
|
||||
margin-top: 15px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #eaecef;
|
||||
display: flex;
|
||||
column-gap: 20px;
|
||||
justify-content: end;
|
||||
margin-top: 15px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #eaecef;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
font-size: 14px;
|
||||
color: var(--color-primary);
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
font-size: 14px;
|
||||
color: var(--color-primary);
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .action-btn i {
|
||||
margin-right: 6px;
|
||||
font-size: 16px;
|
||||
margin-right: 6px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .action-btn.delete {
|
||||
color: #ff0000;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.table-list-container .table-list-item .action-btn.review {
|
||||
color: #67c23a;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.table-list-container .empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.table-list-container .empty-state i {
|
||||
font-size: 60px;
|
||||
margin-bottom: 16px;
|
||||
color: #dcdee0;
|
||||
font-size: 60px;
|
||||
margin-bottom: 16px;
|
||||
color: #dcdee0;
|
||||
}
|
||||
|
||||
.table-list-container .empty-state p {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -239,7 +239,6 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
$(document).on("pjax:complete", function () {
|
||||
NProgress.done()
|
||||
$("#sub-app-container-main-content").show()
|
||||
setContentTitle()
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<!--g2plot-->
|
||||
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/goober@2.1.10/dist/goober.umd.js"></script>
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/goober@2.1.10/dist/goober.umd.js"></script>-->
|
||||
|
||||
<script>
|
||||
window._AMapSecurityConfig = {
|
||||
|
||||
@@ -604,7 +604,7 @@
|
||||
<header class="v4-header">
|
||||
<div class="v4-left-section">
|
||||
<div class="v4-logo-container">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i843ts1nwl3w2n5iw31ohn2n10cripk3gw0" alt="Logo" class="v4-logo" />
|
||||
<img src="${AppLogo!}" alt="Logo" class="v4-logo" />
|
||||
</div>
|
||||
|
||||
<nav class="v4-nav">
|
||||
|
||||
@@ -318,7 +318,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
/* 右侧活动列表 */
|
||||
.oa-activity-content {
|
||||
padding: 20px;
|
||||
/*padding: 20px;*/
|
||||
background: #ffffff;
|
||||
max-height: 410px;
|
||||
display: flex;
|
||||
@@ -354,7 +354,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
}
|
||||
|
||||
.oa-activity-tab:hover {
|
||||
color: #4a90e2;
|
||||
color: #4a90e2;
|
||||
}
|
||||
|
||||
.oa-activity-tab.active {
|
||||
@@ -377,10 +377,10 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
/* 活动轮播样式 */
|
||||
.oa-activity-carousel {
|
||||
flex: 1;
|
||||
margin-top: 15px;
|
||||
/*margin-top: 15px;*/
|
||||
}
|
||||
|
||||
.oa-activity-carousel .el-carousel,.oa-activity-carousel .el-carousel .el-carousel__container {
|
||||
.oa-activity-carousel .el-carousel, .oa-activity-carousel .el-carousel .el-carousel__container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -481,17 +481,26 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
/* box2 - 系统通知公告 */
|
||||
.oa-box-2-section {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 400px;
|
||||
gap: 30px;
|
||||
border-radius: 6px;
|
||||
width: 1500px;
|
||||
margin: 30px auto 0;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oa-box-2-section-act {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
|
||||
.oa-notice-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@@ -509,17 +518,19 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background-color: #000000;
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.oa-notice-header-icon {
|
||||
font-size: 24px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.oa-notice-header-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.oa-notice-header-more {
|
||||
@@ -556,7 +567,16 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
|
||||
.oa-notice-content {
|
||||
padding: 10px;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
.oa-notice-content::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari and Opera */
|
||||
}
|
||||
|
||||
.oa-notice-list {
|
||||
@@ -591,9 +611,28 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.oa-notice-item-img {
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.oa-notice-item-img img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.oa-notice-item-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
/*padding: 10px 0;*/
|
||||
}
|
||||
|
||||
.oa-notice-item-title {
|
||||
@@ -612,10 +651,40 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.oa-notice-item-desc i {
|
||||
/*color: var(--color-primary);*/
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.oa-notice-item-detail {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.oa-detail-link {
|
||||
color: var(--color-primary);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.oa-detail-link i {
|
||||
font-size: 12px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.oa-detail-link:hover i {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.oa-notice-item-date {
|
||||
@@ -625,6 +694,157 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/*推荐服务*/
|
||||
.oa-box-2-section-recommend {
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.oa-recommend-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.oa-recommend-tab-header {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.oa-recommend-tab-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.oa-recommend-tab-btn:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.oa-recommend-tab-btn.active {
|
||||
color: var(--color-primary);
|
||||
border-bottom-color: var(--color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.oa-recommend-tab-btn i {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.oa-recommend-tab-content {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.oa-recommend-content {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
.oa-recommend-content::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari and Opera */
|
||||
}
|
||||
|
||||
.oa-recommend-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.oa-recommend-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 20px 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-recommend-item:hover {
|
||||
background: #e9ecef;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.oa-recommend-item-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: var(--color-primary);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.oa-recommend-item-icon i {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.oa-recommend-item-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.oa-recommend-item-info h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.oa-recommend-item-info p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 空数据提示样式 */
|
||||
.oa-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.oa-empty-state-icon {
|
||||
font-size: 48px;
|
||||
color: #ddd;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.oa-empty-state-title {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.oa-empty-state-desc {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div class="oa-container" id="v4-home-app">
|
||||
@@ -634,10 +854,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
<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=""
|
||||
/>
|
||||
<img src="${config.AppHomeImg!}" alt=""/>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和统计区域 -->
|
||||
@@ -647,7 +864,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
<!-- <h1 class="oa-search-title">精业济群</h1>-->
|
||||
<div class="oa-search-container">
|
||||
<input type="text" class="oa-search-input" placeholder="请输入您要查询的关键字"
|
||||
v-model="searchQuery" />
|
||||
v-model="searchQuery"/>
|
||||
<button class="oa-search-btn" @click="performSearch">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -739,16 +956,16 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
<!-- 右侧活动列表 -->
|
||||
<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-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">
|
||||
@@ -763,9 +980,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
<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" />
|
||||
<img :src="activity.cover" :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
|
||||
@@ -782,31 +997,121 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
<!-- 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 class="oa-box-2-section-act">
|
||||
<div class="oa-notice-header">
|
||||
<div class="oa-notice-header-content">
|
||||
<div class="oa-notice-header-icon">
|
||||
<i class="fa fa-users"></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>
|
||||
<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">
|
||||
<div class="oa-notice-content">
|
||||
<div v-if="recentActivities && recentActivities.length > 0" class="oa-notice-list">
|
||||
<div v-for="notice in recentActivities" :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-img">
|
||||
<img :src="notice.cover" alt="">
|
||||
</div>
|
||||
<div class="oa-notice-item-content">
|
||||
<div class="oa-notice-item-title">{{ notice.name }}</div>
|
||||
<div class="oa-notice-item-desc">
|
||||
<i class="fa fa-calendar-o"></i>
|
||||
开始时间:{{ formatDate(notice.startDate) }}
|
||||
</div>
|
||||
<div class="oa-notice-item-desc">
|
||||
<i class="fa fa-calendar-check-o"></i>
|
||||
结束时间:{{ formatDate(notice.endDate) }}
|
||||
</div>
|
||||
<div class="oa-notice-item-detail">
|
||||
<span class="oa-detail-link" @click.stop="openActivity(notice)">
|
||||
立即前往
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="oa-notice-item-date">{{ notice.publishDate }}</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="oa-empty-state">
|
||||
<div class="oa-empty-state-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 class="oa-empty-state-title">暂无通知公告</div>
|
||||
<div class="oa-empty-state-desc">当前没有可显示的通知公告内容<br>请稍后再来查看</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oa-box-2-section-recommend">
|
||||
<div class="oa-recommend-tabs">
|
||||
<div class="oa-recommend-tab-header">
|
||||
<button
|
||||
class="oa-recommend-tab-btn"
|
||||
:class="{ active: recommendTab === 'service' }"
|
||||
@click="setRecommendTab('service')">
|
||||
<i class="fa fa-cogs"></i>
|
||||
推荐服务
|
||||
</button>
|
||||
<button
|
||||
class="oa-recommend-tab-btn"
|
||||
:class="{ active: recommendTab === 'app' }"
|
||||
@click="setRecommendTab('app')">
|
||||
<i class="fa fa-th-large"></i>
|
||||
推荐应用
|
||||
</button>
|
||||
</div>
|
||||
<div class="oa-recommend-tab-content">
|
||||
<div v-if="recommendTab === 'service'" class="oa-recommend-content">
|
||||
<div v-if="recommendServices && recommendServices.length > 0" class="oa-recommend-grid">
|
||||
<div class="oa-recommend-item" v-for="service in recommendServices" :key="service.id" @click="openService(service)">
|
||||
<div class="oa-recommend-item-icon">
|
||||
<img v-if="service.picIcon" :src="service.picIcon" :alt="service.name" style="width: 32px; height: 32px; object-fit: cover;">
|
||||
<i v-else-if="service.icon" :class="service.icon"></i>
|
||||
<i v-else class="fa fa-cog"></i>
|
||||
</div>
|
||||
<div class="oa-recommend-item-info">
|
||||
<h4>{{ service.name }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="oa-empty-state">
|
||||
<div class="oa-empty-state-icon">
|
||||
<i class="fa fa-cog"></i>
|
||||
</div>
|
||||
<div class="oa-empty-state-title">暂无推荐服务</div>
|
||||
<div class="oa-empty-state-desc">当前没有可用的推荐服务<br>请联系管理员添加服务内容</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="recommendTab === 'app'" class="oa-recommend-content">
|
||||
<div v-if="recommendApps && recommendApps.length > 0" class="oa-recommend-grid">
|
||||
<div class="oa-recommend-item" v-for="app in recommendApps" :key="app.id" @click="openApp(app)">
|
||||
<div class="oa-recommend-item-icon">
|
||||
<img v-if="app.picIcon" :src="app.picIcon" :alt="app.name" style="width: 32px; height: 32px; object-fit: cover;">
|
||||
<i v-else-if="app.icon" :class="app.icon"></i>
|
||||
<i v-else class="fa fa-cube"></i>
|
||||
</div>
|
||||
<div class="oa-recommend-item-info">
|
||||
<h4>{{ app.name }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="oa-empty-state">
|
||||
<div class="oa-empty-state-icon">
|
||||
<i class="fa fa-cube"></i>
|
||||
</div>
|
||||
<div class="oa-empty-state-title">暂无推荐应用</div>
|
||||
<div class="oa-empty-state-desc">当前没有可用的推荐应用<br>请联系管理员添加应用内容</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oa-notice-item-date">{{ notice.publishDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -822,6 +1127,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
searchQuery: "",
|
||||
activeTab: "recent",
|
||||
noticeTab: "system",
|
||||
recommendTab: "service",
|
||||
currentIndex: 0,
|
||||
stats: {
|
||||
todo: 3,
|
||||
@@ -830,30 +1136,130 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
started: 9
|
||||
},
|
||||
activityList: [],
|
||||
noticeList: [
|
||||
noticeList: [],
|
||||
recommendServices: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'system',
|
||||
title: '系统维护通知',
|
||||
content: '系统将于本周六晚上22:00-24:00进行例行维护,期间可能影响正常使用,请提前做好相关准备。',
|
||||
publishDate: '2024-01-15',
|
||||
isRead: false
|
||||
name: "人事服务",
|
||||
description: "员工信息管理、考勤统计、薪资查询",
|
||||
icon: "fa fa-users",
|
||||
picIcon: "/static/assets/platform/images/service-hr.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'announcement',
|
||||
title: '新功能上线公告',
|
||||
content: '移动端应用已正式上线,支持手机端办公审批,欢迎大家下载使用。',
|
||||
publishDate: '2024-01-14',
|
||||
isRead: true
|
||||
name: "财务服务",
|
||||
description: "报销申请、费用审批、财务报表",
|
||||
icon: "fa fa-calculator"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'urgent',
|
||||
title: '紧急安全提醒',
|
||||
content: '近期发现钓鱼邮件攻击,请勿点击可疑链接,如有疑问请联系IT部门。',
|
||||
publishDate: '2024-01-13',
|
||||
isRead: false
|
||||
name: "IT服务",
|
||||
description: "设备申请、故障报修、系统支持",
|
||||
icon: "fa fa-laptop",
|
||||
picIcon: "/static/assets/platform/images/service-it.png"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "行政服务",
|
||||
description: "会议室预订、办公用品申请、车辆管理",
|
||||
icon: "fa fa-building"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "培训服务",
|
||||
description: "员工培训、技能提升、证书管理",
|
||||
icon: "fa fa-graduation-cap"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "法务服务",
|
||||
description: "合同审核、法律咨询、风险评估",
|
||||
icon: "fa fa-gavel"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "采购服务",
|
||||
description: "供应商管理、采购申请、合同管理",
|
||||
icon: "fa fa-shopping-cart"
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "质量服务",
|
||||
description: "质量检测、标准制定、改进建议",
|
||||
icon: "fa fa-check-circle"
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "安全服务",
|
||||
description: "安全培训、风险评估、应急预案"
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "客服服务",
|
||||
description: "客户咨询、投诉处理、满意度调查",
|
||||
icon: "fa fa-headphones"
|
||||
}
|
||||
],
|
||||
recommendApps: [
|
||||
{
|
||||
id: 1,
|
||||
name: "移动办公",
|
||||
description: "随时随地处理工作事务",
|
||||
icon: "fa fa-mobile",
|
||||
picIcon: "/static/assets/platform/images/app-mobile.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "项目管理",
|
||||
description: "高效协作,项目进度一目了然",
|
||||
icon: "fa fa-tasks"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "文档中心",
|
||||
description: "企业知识库,文档共享平台",
|
||||
picIcon: "/static/assets/platform/images/app-docs.png"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "视频会议",
|
||||
description: "高清视频通话,远程协作无障碍",
|
||||
icon: "fa fa-video-camera"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "邮件系统",
|
||||
description: "企业邮箱,高效沟通协作",
|
||||
icon: "fa fa-envelope"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "考勤打卡",
|
||||
description: "智能考勤,工时统计管理"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "报销系统",
|
||||
description: "便捷报销,财务审批流程",
|
||||
icon: "fa fa-credit-card"
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "通讯录",
|
||||
description: "企业通讯录,联系人管理",
|
||||
icon: "fa fa-address-book"
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "日程管理",
|
||||
description: "日程安排,时间管理助手",
|
||||
icon: "fa fa-calendar"
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "数据分析",
|
||||
description: "数据可视化,业务分析报表",
|
||||
icon: "fa fa-bar-chart"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -879,6 +1285,8 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
// 初始化数据
|
||||
this.getActivity()
|
||||
this.getStatistics()
|
||||
this.getRecommendService()
|
||||
this.getRecommendApp()
|
||||
},
|
||||
methods: {
|
||||
performSearch() {
|
||||
@@ -887,10 +1295,24 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
// 实现搜索逻辑
|
||||
}
|
||||
},
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
},
|
||||
setActiveTab(tab) {
|
||||
this.activeTab = tab
|
||||
this.currentIndex = 0 // 切换标签时重置索引
|
||||
},
|
||||
setRecommendTab(tab) {
|
||||
this.recommendTab = tab
|
||||
},
|
||||
|
||||
// 获取活动列表
|
||||
getActivity() {
|
||||
$.get("/platform/home/listHomeActivity").then((res) => {
|
||||
if (res.code === 0) {
|
||||
@@ -905,16 +1327,19 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
// 这里可以添加查看详情的逻辑
|
||||
},
|
||||
|
||||
handleStatClick(val){
|
||||
if(['todo', 'done', 'application']){
|
||||
// 待办、已办、消息、我的点击
|
||||
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
|
||||
const {todoCount, doneCount, startedCount} = res.data
|
||||
this.stats.todo = todoCount
|
||||
this.stats.done = doneCount
|
||||
this.stats.started = startedCount
|
||||
@@ -925,6 +1350,48 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
}
|
||||
},
|
||||
|
||||
// 活动点击
|
||||
openActivity(act) {
|
||||
if (!act.url) {
|
||||
this.$message.warning("管理员未配置活动链接")
|
||||
return
|
||||
}
|
||||
window.open(act.url)
|
||||
},
|
||||
|
||||
// 查询推荐服务
|
||||
async getRecommendService() {
|
||||
|
||||
|
||||
this.$axios.post('/platform/home/listRecommendService', {platform: "PC"}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.recommendServices = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取推荐应用
|
||||
async getRecommendApp() {
|
||||
this.$axios.post('/platform/home/listRecommendApp', {platform: "PC"}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.recommendApps = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 推荐服务点击事件
|
||||
openService(service) {
|
||||
window.open(service.href)
|
||||
},
|
||||
|
||||
// 推荐应用点击事件
|
||||
openApp(app) {
|
||||
// 储存到缓存
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(app))
|
||||
// 新标签页打开
|
||||
window.open("/platform/v4/subApp?appId=" + app.id, "_blank")
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -322,17 +322,17 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
<div class="apps-wrapper" id="app">
|
||||
<div class="apps-hero">
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i8hmpaqdwmvwajjvw2c8isi61jj0gkmosw0" alt="应用中心" />
|
||||
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i8hmpaqdwmvwajjvw2c8isi61jj0gkmosw0" alt="应用中心"/>
|
||||
</div>
|
||||
|
||||
<div class="apps-container">
|
||||
<!-- Left Sidebar -->
|
||||
<div class="apps-sidebar">
|
||||
<div
|
||||
v-for="category in allCategories"
|
||||
:key="category.id"
|
||||
:class="['apps-sidebar-item', activeCategory === category.id ? 'active' : '']"
|
||||
@click="changeCategory(category.id)"
|
||||
v-for="category in allCategories"
|
||||
:key="category.id"
|
||||
:class="['apps-sidebar-item', activeCategory === category.id ? 'active' : '']"
|
||||
@click="changeCategory(category.id)"
|
||||
>
|
||||
<i :class="category.icon"></i>
|
||||
{{ category.name }}
|
||||
@@ -344,7 +344,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
<div class="apps-header">
|
||||
<div class="apps-search-container">
|
||||
<div class="apps-search">
|
||||
<input type="text" v-model="searchKeyword" @keyup.enter="search" placeholder="请输入内容" />
|
||||
<input type="text" v-model="searchKeyword" @keyup.enter="search" placeholder="请输入内容"/>
|
||||
<i class="el-icon-search search-icon" @click="search"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -353,12 +353,14 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
<div class="apps-filter">
|
||||
<div class="apps-filter-title">首字母:</div>
|
||||
<div class="apps-filter-tags alphabet-filter">
|
||||
<div :class="['filter-tag', activeAlphabet === '' ? 'active' : '']" @click="changeAlphabet('')">全部</div>
|
||||
<div :class="['filter-tag', activeAlphabet === '' ? 'active' : '']" @click="changeAlphabet('')">
|
||||
全部
|
||||
</div>
|
||||
<div
|
||||
v-for="letter in alphabets"
|
||||
:key="letter"
|
||||
:class="['alphabet-item', activeAlphabet === letter ? 'active' : '']"
|
||||
@click="changeAlphabet(letter)"
|
||||
v-for="letter in alphabets"
|
||||
:key="letter"
|
||||
:class="['alphabet-item', activeAlphabet === letter ? 'active' : '']"
|
||||
@click="changeAlphabet(letter)"
|
||||
>
|
||||
{{ letter }}
|
||||
</div>
|
||||
@@ -404,7 +406,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
// 应用列表
|
||||
applications: [],
|
||||
// 分类列表
|
||||
categories: [{ id: "all", name: "全部服务", icon: "fa fa-th-large" }],
|
||||
categories: [{id: "all", name: "全部服务", icon: "fa fa-th-large"}],
|
||||
// 动态加载的分类
|
||||
dynamicCategories: [],
|
||||
// 当前选中的分类
|
||||
@@ -486,8 +488,8 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
this.activeCategory === "all"
|
||||
? ""
|
||||
: this.activeCategory === "favorites" || this.activeCategory === "recommended"
|
||||
? this.activeCategory
|
||||
: this.activeCategory,
|
||||
? this.activeCategory
|
||||
: this.activeCategory,
|
||||
letter: this.activeAlphabet,
|
||||
keyword: this.searchKeyword
|
||||
}
|
||||
@@ -541,7 +543,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
if (!app) return
|
||||
|
||||
const url = app.isFavorite ? "/platform/v4/serv/removeFavorite" : "/platform/v4/serv/addFavorite"
|
||||
const params = { appId: appId }
|
||||
const params = {appId: appId}
|
||||
|
||||
$.post(url, params)
|
||||
.then((result) => {
|
||||
@@ -585,8 +587,11 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
// 打开应用
|
||||
openApp(app) {
|
||||
console.log(app)
|
||||
// 新标签页打开
|
||||
window.open("/flow/common/approval/form?defineKey=" + app.name, "_blank")
|
||||
if (!app.instanceUrl) {
|
||||
this.$message.warning("管理员未配置服务地址")
|
||||
return
|
||||
}
|
||||
window.open(app.instanceUrl, "_blank")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -13,9 +13,12 @@ layout("/layouts/platform.html"){
|
||||
</el-card>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%">
|
||||
<el-table-column header-align="center" label="参数名" prop="configKey" sortable width="200"></el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" header-align="center" label="参数值" prop="configValue" width="200"></el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" header-align="center" label="说明" prop="note"></el-table-column>
|
||||
<el-table-column header-align="center" label="参数名" prop="configKey" sortable
|
||||
width="200"></el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" header-align="center" label="参数值" prop="configValue"
|
||||
width="200"></el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" header-align="center" label="说明"
|
||||
prop="note"></el-table-column>
|
||||
<el-table-column label="操作" prop="note" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-dropdown @command="dropdownCommand">
|
||||
@@ -24,8 +27,10 @@ layout("/layouts/platform.html"){
|
||||
<span class="ti-angle-down"></span>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :command="{type:'edit',id:scope.row.configKey}">修改</el-dropdown-item>
|
||||
<el-dropdown-item :command="{type:'delete',id:scope.row.configKey}" v-if="scope.row.configKey.indexOf('App')<0">
|
||||
<el-dropdown-item :command="{type:'edit',id:scope.row.configKey}">修改
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :command="{type:'delete',id:scope.row.configKey}"
|
||||
v-if="scope.row.configKey.indexOf('App')<0">
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
@@ -40,10 +45,12 @@ layout("/layouts/platform.html"){
|
||||
<el-dialog title="新增参数" :visible.sync="addDialogVisible" :close-on-click-modal="false" width="40%">
|
||||
<el-form :model="formData" ref="addForm" :rules="formRules" label-width="80px">
|
||||
<el-form-item prop="configKey" label="参数名">
|
||||
<el-input maxlength="100" placeholder="参数名" v-model="formData.configKey" auto-complete="off" tabindex="1" type="text"></el-input>
|
||||
<el-input maxlength="100" placeholder="参数名" v-model="formData.configKey" auto-complete="off"
|
||||
tabindex="1" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="configValue" label="参数值">
|
||||
<el-input maxlength="100" placeholder="参数值" v-model="formData.configValue" auto-complete="off" tabindex="2" type="text"></el-input>
|
||||
<el-input maxlength="100" placeholder="参数值" v-model="formData.configValue" auto-complete="off"
|
||||
tabindex="2" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="note" label="说 明">
|
||||
<el-input type="textarea" v-model="formData.note"></el-input>
|
||||
@@ -58,32 +65,46 @@ layout("/layouts/platform.html"){
|
||||
<el-form :model="formData" ref="editForm" :rules="formRules" label-width="80px">
|
||||
<el-form-item prop="configKey" label="参数名">
|
||||
<el-input
|
||||
maxlength="100"
|
||||
placeholder="参数名"
|
||||
v-model="formData.configKey"
|
||||
auto-complete="off"
|
||||
tabindex="1"
|
||||
type="text"
|
||||
disabled
|
||||
maxlength="100"
|
||||
placeholder="参数名"
|
||||
v-model="formData.configKey"
|
||||
auto-complete="off"
|
||||
tabindex="1"
|
||||
type="text"
|
||||
disabled
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="configValue" label="参数值">
|
||||
<div v-if="'true'===formData.configValue||'false'===formData.configValue">
|
||||
<el-radio-group v-model="formData.configValue">
|
||||
<el-radio label="true">是</el-radio>
|
||||
<el-radio label="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-input
|
||||
maxlength="100"
|
||||
placeholder="参数值"
|
||||
v-model="formData.configValue"
|
||||
auto-complete="off"
|
||||
tabindex="2"
|
||||
type="text"
|
||||
></el-input>
|
||||
</div>
|
||||
<template v-if="formData.configKey === 'AppLogo' || formData.configKey === 'AppHomeImg'">
|
||||
<file-upload
|
||||
style="--upload-width: 214px;--upload-height:64px"
|
||||
:value.sync="formData.configValue"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<span v-if="formData.configKey === 'AppLogo'">最佳分辨率:428 x 128</span>
|
||||
<span v-else-if="formData.configKey === 'AppHomeImg'">最佳分辨率:2756 x 732</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="'true'===formData.configValue||'false'===formData.configValue">
|
||||
<el-radio-group v-model="formData.configValue">
|
||||
<el-radio label="true">是</el-radio>
|
||||
<el-radio label="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-input
|
||||
maxlength="100"
|
||||
placeholder="参数值"
|
||||
v-model="formData.configValue"
|
||||
auto-complete="off"
|
||||
tabindex="2"
|
||||
type="text"
|
||||
></el-input>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<el-form-item prop="note" label="说 明">
|
||||
<el-input type="textarea" v-model="formData.note"></el-input>
|
||||
@@ -116,8 +137,8 @@ layout("/layouts/platform.html"){
|
||||
note: ""
|
||||
},
|
||||
formRules: {
|
||||
configKey: [{ required: true, message: "请输入参数名", trigger: "blur" }],
|
||||
configValue: [{ required: true, message: "请输入参数值", trigger: "blur" }]
|
||||
configKey: [{required: true, message: "请输入参数名", trigger: "blur"}],
|
||||
configValue: [{required: true, message: "请输入参数值", trigger: "blur"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -21,25 +21,41 @@ layout("/layouts/platform.html"){
|
||||
<el-image :src="scope.row.cover" v-if="scope.row.icon" style="width: 30px; height: 30px"></el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="enable" width="80px">
|
||||
<template slot-scope="scope">
|
||||
<i v-if="!scope.row.enable" class="fa fa-circle text-danger ml5"></i>
|
||||
<i v-else class="fa fa-circle text-success ml5"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否置顶" prop="top" width="100px">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" v-if="scope.row.top" type="success">是</el-tag>
|
||||
<el-tag size="mini" v-else type="info">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关联类路径" prop="classPath" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" width="150px">
|
||||
<el-table-column label="推送大图" prop="push" width="100px">
|
||||
<template slot-scope="scope">
|
||||
<el-link type="danger" size="mini" @click="disable(scope.row.id)" v-if="scope.row.enable">关闭</el-link>
|
||||
<el-link type="primary" size="mini" @click="enable(scope.row.id)" v-if="!scope.row.enable">开启</el-link>
|
||||
<el-link type="primary" size="mini" @click="topUp(scope.row.id)" v-if="!scope.row.top">置顶</el-link>
|
||||
<el-link type="danger" size="mini" @click="cancelTopUp(scope.row.id)" v-if="scope.row.top">取消置顶</el-link>
|
||||
<el-tag size="mini" v-if="scope.row.push" type="success">是</el-tag>
|
||||
<el-tag size="mini" v-else type="info">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关联类路径" prop="classPath" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="状态" prop="enable" width="80px">
|
||||
<template slot-scope="scope">
|
||||
<i v-if="!scope.row.enable" class="fa fa-circle text-danger ml5"></i>
|
||||
<i v-else class="fa fa-circle text-success ml5"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250px">
|
||||
<template slot-scope="scope">
|
||||
<el-link type="danger" size="mini" @click="disable(scope.row.id)" v-if="scope.row.enable">关闭
|
||||
</el-link>
|
||||
<el-link type="primary" size="mini" @click="enable(scope.row.id)" v-if="!scope.row.enable">开启
|
||||
</el-link>
|
||||
<el-link type="primary" size="mini" @click="topUp(scope.row.id)" v-if="!scope.row.top">置顶
|
||||
</el-link>
|
||||
<el-link type="danger" size="mini" @click="cancelTopUp(scope.row.id)" v-if="scope.row.top">
|
||||
取消置顶
|
||||
</el-link>
|
||||
<el-link type="primary" size="mini" @click="push(scope.row.id)" v-if="!scope.row.push">推送大图
|
||||
</el-link>
|
||||
<el-link type="danger" size="mini" @click="cancelPush(scope.row.id)" v-if="scope.row.push">
|
||||
取消推送大图
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -61,7 +77,7 @@ layout("/layouts/platform.html"){
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/disable", { id }).then((resp) => {
|
||||
this.$axios.post(loc() + "/disable", {id}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
@@ -75,7 +91,7 @@ layout("/layouts/platform.html"){
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/enable", { id }).then((resp) => {
|
||||
this.$axios.post(loc() + "/enable", {id}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
@@ -89,7 +105,7 @@ layout("/layouts/platform.html"){
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/topUp", { id }).then((resp) => {
|
||||
this.$axios.post(loc() + "/topUp", {id}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
@@ -103,7 +119,35 @@ layout("/layouts/platform.html"){
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/cancelTopUp", { id }).then((resp) => {
|
||||
this.$axios.post(loc() + "/cancelTopUp", {id}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
push(id) {
|
||||
this.$confirm("您确定要推送大图吗?", "提示", {
|
||||
type: "warning",
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/push", {id}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
cancelPush(id) {
|
||||
this.$confirm("您确定要取消推送大图吗?", "提示", {
|
||||
type: "warning",
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/cancelPush", {id}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="课程">
|
||||
<el-select v-model="pageForm.courseId" placeholder="请选择课程" clearable>
|
||||
<el-option v-for="item in coursesOptions" :key="item.id" :value="item.id"
|
||||
:label="item.title"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="开始时间">
|
||||
<el-date-picker v-model="pageForm.startDate" type="date" placeholder="请选择开始时间"
|
||||
format="yyyy-MM-dd" value-format="yyyy-MM-dd" clearable>
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="结束时间">
|
||||
<el-date-picker v-model="pageForm.endDate" type="date" placeholder="请选择结束时间"
|
||||
format="yyyy-MM-dd" value-format="yyyy-MM-dd" clearable>
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
<el-card shadow="never">
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="序号" width="80" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="工号" prop="loginname"></el-table-column>
|
||||
<el-table-column label="姓名" prop="username"></el-table-column>
|
||||
<el-table-column label="单位" prop="unitName"></el-table-column>
|
||||
<el-table-column label="分工会" prop="unionName"></el-table-column>
|
||||
<el-table-column label="学习时长(秒)" prop="totalWatchTime"></el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
coursesOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pageData() {
|
||||
this.$axios.post('/platform/edu/personRank/ranking', this.pageForm).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
})
|
||||
},
|
||||
listCourses() {
|
||||
this.$axios.post('/platform/edu/personRank/courses').then(res => {
|
||||
if (res.code === 0) {
|
||||
this.coursesOptions = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listCourses()
|
||||
this.pageData()
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,77 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="课程">
|
||||
<el-select v-model="pageForm.courseId" placeholder="请选择课程" clearable>
|
||||
<el-option v-for="item in coursesOptions" :key="item.id" :value="item.id"
|
||||
:label="item.title"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="开始时间">
|
||||
<el-date-picker v-model="pageForm.startDate" type="date" placeholder="请选择开始时间"
|
||||
format="yyyy-MM-dd" value-format="yyyy-MM-dd" clearable>
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="结束时间">
|
||||
<el-date-picker v-model="pageForm.endDate" type="date" placeholder="请选择结束时间"
|
||||
format="yyyy-MM-dd" value-format="yyyy-MM-dd" clearable>
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
<el-card shadow="never">
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="序号" width="80" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="分工会名称" prop="unionName"></el-table-column>
|
||||
<el-table-column label="分工会编码" prop="unionCode"></el-table-column>
|
||||
<el-table-column label="参与人数" prop="participantCount"></el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
coursesOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pageData() {
|
||||
this.$axios.post('/platform/edu/unionRank/ranking', this.pageForm).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
})
|
||||
},
|
||||
listCourses() {
|
||||
this.$axios.post('/platform/edu/unionRank/courses').then(res => {
|
||||
if (res.code === 0) {
|
||||
this.coursesOptions = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listCourses()
|
||||
this.pageData()
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -21,6 +21,9 @@ const PROPOSAL_INFO = {
|
||||
<el-descriptions-item label="提案类别">
|
||||
{{viewData.typeName}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="建议承办单位">
|
||||
{{viewData.suggestUnits}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="立案结果" :span="2">
|
||||
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
|
||||
:value="viewData.caseFilingResult"></dict-tag>
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.nodes-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.nodes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.node-card {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-top: 4px solid var(--theme-color);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.node-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: var(--theme-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.node-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.node-count {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--theme-color);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.node-card.selected {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
border-top-width: 4px;
|
||||
}
|
||||
|
||||
.node-card.selected .node-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nodes-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nodes-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.node-card {
|
||||
padding: 16px;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 20px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-count {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="教代会">
|
||||
<el-select @change="sessionChange" clearable filterable placeholder="所属教代会"
|
||||
v-model="pageForm.sessionId">
|
||||
<el-option :key="item.id" :label="item.fullName" :value="item.id"
|
||||
v-for="item in sessionOptions"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<div slot="header" class="nodes-title">统计看板</div>
|
||||
<div class="nodes-grid">
|
||||
<div
|
||||
v-for="(node, index) in nodes"
|
||||
:key="node.id"
|
||||
class="node-card"
|
||||
:class="{ selected: pageForm.selectNodeId === node.id }"
|
||||
:style="'--theme-color:' + getThemeColor(index)"
|
||||
@click="selectNode(node,index)"
|
||||
>
|
||||
<div class="node-icon">
|
||||
<i class="fa fa-tasks"></i>
|
||||
</div>
|
||||
<div class="node-content">
|
||||
<div class="node-name">{{ node.name || '未命名节点' }}</div>
|
||||
<div class="node-count">{{ node.count || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="nodes-title">{{ tableCardTitle }}</span>
|
||||
</div>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
|
||||
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="提案编号" prop="code" width="100px" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="提案人" prop="createUserName"></el-table-column>
|
||||
<el-table-column label="提案类别" prop="typeName"></el-table-column>
|
||||
<el-table-column label="代表团" prop="delegationName"></el-table-column>
|
||||
<el-table-column label="立案结果" prop="caseFilingResult">
|
||||
<template scope="{row}">
|
||||
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否并案" prop="isConsolidation" width="100">
|
||||
<template scope="{row}">
|
||||
<el-tag size="mini" v-if="row.isConsolidation" type="success">是</el-tag>
|
||||
<el-tag size="mini" v-else type="danger">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="主办单位" prop="masterUnitName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="协办单位" prop="slaveUnitNames" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="200px">
|
||||
<template scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #view>
|
||||
<proposal-info ref="infoRef"></proposal-info>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!--#include("../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
dicts: ["PROPOSAL_TYPE", "PROPOSAL_CASE_FILING_RESULT"],
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
sessionOptions: [],
|
||||
nodes: [],
|
||||
pageForm: {
|
||||
selectNodeId: null,
|
||||
selectNodeType: null,
|
||||
pageSize: 5
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 查看
|
||||
openView(row) {
|
||||
this.$refs.guava.view(() => {
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
|
||||
// 届次改变
|
||||
sessionChange() {
|
||||
this.getNodes()
|
||||
this.doSearch()
|
||||
},
|
||||
|
||||
// 获取届次
|
||||
listSession() {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = res.data
|
||||
if (this.sessionOptions) {
|
||||
this.$set(this.pageForm, "sessionId", this.sessionOptions[0].id)
|
||||
this.getNodes()
|
||||
this.doSearch()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取节点
|
||||
getNodes() {
|
||||
this.$axios.post('/platform/proposal/dashboard/listNode', {sessionId: this.pageForm.sessionId}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.nodes = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取主题色
|
||||
getThemeColor(index) {
|
||||
const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#722ED1']
|
||||
return colors[index % colors.length]
|
||||
},
|
||||
|
||||
// 节点选择
|
||||
selectNode(node, index) {
|
||||
this.pageForm.selectNodeId = node.id
|
||||
this.pageForm.selectNodeType = node.type
|
||||
this.doSearch()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableCardTitle() {
|
||||
if (this.pageForm.selectNodeId) {
|
||||
const selectedNode = this.nodes.find(node => node.id === this.pageForm.selectNodeId)
|
||||
return selectedNode ? selectedNode.name : '提案总数'
|
||||
}
|
||||
return '提案总数'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listSession()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+34
-13
@@ -9,12 +9,15 @@ layout("/layouts/platform.html"){
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="教代会">
|
||||
<el-select @change="meetingChange" clearable filterable placeholder="所属教代会" v-model="pageForm.sessionId">
|
||||
<el-option :key="item.id" :label="item.fullName" :value="item.id" v-for="item in sessionOptions"></el-option>
|
||||
<el-select @change="meetingChange" clearable filterable placeholder="所属教代会"
|
||||
v-model="pageForm.sessionId">
|
||||
<el-option :key="item.id" :label="item.fullName" :value="item.id"
|
||||
v-for="item in sessionOptions"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="提案名称">
|
||||
<el-input v-model="pageForm.name" placeholder="提案名称" @keyup.enter.native="doSearch" clearable style="width: 100%"></el-input>
|
||||
<el-input v-model="pageForm.name" placeholder="提案名称" @keyup.enter.native="doSearch" clearable
|
||||
style="width: 100%"></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
@@ -38,21 +41,22 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column label="操作" fixed="right" width="400px">
|
||||
<template scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" size="mini" type="primary" @click="openEdit(row)">
|
||||
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" size="mini" type="primary"
|
||||
@click="openEdit(row)">
|
||||
编辑提案
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="onOpenInviteSeconder(row)"
|
||||
v-if="row.taskKey === '85b7b9bd-d706-48cb-99a1-ef1370fb1819' || row.taskKey === '74458b33-ad6e-46c4-b8d9-1aa897142b25'"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="onOpenInviteSeconder(row)"
|
||||
v-if="row.taskKey === '85b7b9bd-d706-48cb-99a1-ef1370fb1819' || row.taskKey === '74458b33-ad6e-46c4-b8d9-1aa897142b25'"
|
||||
>
|
||||
邀请附议人
|
||||
</el-button>
|
||||
<el-button v-if="row.canRevoke" size="mini" type="danger" @click="openRevoke(row)">
|
||||
撤销
|
||||
</el-button>
|
||||
<!-- v-if="row.taskKey === 'startTask' || !row.instanceId"-->
|
||||
<!-- v-if="row.taskKey === 'startTask' || !row.instanceId"-->
|
||||
<el-button size="mini" type="danger" @click="del(row.id)">
|
||||
删除
|
||||
</el-button>
|
||||
@@ -112,7 +116,7 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
},
|
||||
onOpenInviteSeconder(row) {
|
||||
this.$axios.post(loc() + "/inviteCheck", { id: row.id }).then((res) => {
|
||||
this.$axios.post(loc() + "/inviteCheck", {id: row.id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.guava.public(() => {
|
||||
this.$refs.inviteSeconderRef.onOpen(row)
|
||||
@@ -134,13 +138,13 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
})
|
||||
},
|
||||
withdraw({ processInstId }) {
|
||||
withdraw({processInstId}) {
|
||||
this.$confirm("您确定要撤回吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "info"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/withdraw", { processInstId }).then((res) => {
|
||||
this.$axios.post(loc() + "/withdraw", {processInstId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
@@ -154,18 +158,35 @@ layout("/layouts/platform.html"){
|
||||
cancelButtonText: "取消",
|
||||
type: "info"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/delete", { id }).then((res) => {
|
||||
this.$axios.post(loc() + "/delete", {id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 检测操作
|
||||
checkOperation() {
|
||||
const operation = GetQueryString("operation")
|
||||
const bizId = GetQueryString("bizId")
|
||||
if (!operation) return
|
||||
if (operation === "invite" && bizId) {
|
||||
// 邀请附议人
|
||||
this.$axios.post('/platform/proposal/mine/selectOne',{id: bizId}).then(res=>{
|
||||
if (res.code === 0) {
|
||||
this.onOpenInviteSeconder(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.listSession()
|
||||
this.pageData()
|
||||
this.checkOperation()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+48
-26
@@ -19,30 +19,51 @@ const PROPOSAL_INVITE_SECONDER_COMPONENT = {
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider class="mt10 mb10"></el-divider>
|
||||
<table-tool>
|
||||
<el-button size="small" @click="invite" type="primary" icon="el-icon-plus"
|
||||
:disabled="pageForm.isInvite">邀请
|
||||
</el-button>
|
||||
<el-radio-group v-model="pageForm.isInvite" @change="doSearch" size="small" style="margin-left: 5px;">
|
||||
<el-radio-button :label="true">已邀请</el-radio-button>
|
||||
<el-radio-button :label="false">可邀请</el-radio-button>
|
||||
</el-radio-group>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder" header-align="center"
|
||||
style="width: 100%" :row-key="getRowKey">
|
||||
<el-table-column type="selection" reserve-selection v-if="!pageForm.isInvite"></el-table-column>
|
||||
<el-table-column label="序号" width="50px" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="工号" prop="loginName"></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName"></el-table-column>
|
||||
<el-table-column label="性别" prop="sex"></el-table-column>
|
||||
<el-table-column label="代表团" prop="delegationName"></el-table-column>
|
||||
<!-- <el-table-column label="操作" width="150px">-->
|
||||
<!-- <template scope="{row}">-->
|
||||
<!-- <el-button size="mini" type="primary" @click="invite(row)">邀请</el-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
<div>
|
||||
<table-tool label="代表数据">
|
||||
<!-- <el-button size="small" @click="invite" type="primary" icon="el-icon-plus"-->
|
||||
<!-- :disabled="pageForm.isInvite">邀请-->
|
||||
<!-- </el-button>-->
|
||||
<el-radio-group v-model="pageForm.isInvite" @change="doSearch" size="small" style="margin-left: 5px;">
|
||||
<el-radio-button :label="true">已邀请</el-radio-button>
|
||||
<el-radio-button :label="false">可邀请</el-radio-button>
|
||||
</el-radio-group>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder" header-align="center"
|
||||
style="width: 100%" :row-key="getRowKey">
|
||||
<el-table-column type="selection" reserve-selection v-if="!pageForm.isInvite"></el-table-column>
|
||||
<el-table-column label="序号" width="50px" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="工号" prop="loginName"></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName"></el-table-column>
|
||||
<el-table-column label="性别" prop="sex"></el-table-column>
|
||||
<el-table-column label="代表团" prop="delegationName"></el-table-column>
|
||||
<!-- <el-table-column label="操作" width="150px">-->
|
||||
<!-- <template scope="{row}">-->
|
||||
<!-- <el-button size="mini" type="primary" @click="invite(row)">邀请</el-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</div>
|
||||
|
||||
<el-divider class="mt10 mb10"></el-divider>
|
||||
<div v-if="$refs.tableRef">
|
||||
<table-tool label="当前已选择"></table-tool>
|
||||
|
||||
<el-table :data="$refs.tableRef.selection">
|
||||
<el-table-column label="序号" width="50px" type="index"></el-table-column>
|
||||
<el-table-column label="工号" prop="loginName"></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName"></el-table-column>
|
||||
<el-table-column label="性别" prop="sex"></el-table-column>
|
||||
<el-table-column label="代表团" prop="delegationName"></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-row type="flex" justify="center" class="p10">
|
||||
<el-button @click="">取消</el-button>
|
||||
<el-button @click="invite" type="primary">邀请</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`,
|
||||
mixins: [initTableMixins],
|
||||
@@ -52,7 +73,8 @@ const PROPOSAL_INVITE_SECONDER_COMPONENT = {
|
||||
secondedNum: 0,
|
||||
record: {},
|
||||
pageForm: {
|
||||
isInvite: false
|
||||
isInvite: false,
|
||||
pageSize: 5
|
||||
},
|
||||
config: {},
|
||||
delegationOptions: []
|
||||
@@ -93,7 +115,7 @@ const PROPOSAL_INVITE_SECONDER_COMPONENT = {
|
||||
invite() {
|
||||
const selection = this.$refs.tableRef.selection
|
||||
if (selection.length === 0) {
|
||||
this.$message.warning("请先选择附议人后在点击邀请")
|
||||
this.$message.warning("请先选择附议人后再邀请")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="教代会">
|
||||
<el-select @change="meetingChange" clearable filterable placeholder="所属教代会"
|
||||
v-model="pageForm.sessionId">
|
||||
<el-option :key="item.id" :label="item.fullName" :value="item.id"
|
||||
v-for="item in sessionOptions"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="提案名称">
|
||||
<el-input v-model="pageForm.name" placeholder="提案名称" @keyup.enter.native="doSearch" clearable
|
||||
style="width: 100%"></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<!-- <el-radio-group v-model="pageForm.approval" size="small" @change="doSearch">-->
|
||||
<!-- <el-radio-button :label="true">已审核</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="false">未审核</el-radio-button>-->
|
||||
<!-- </el-radio-group>-->
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="提案编号" prop="code"></el-table-column>
|
||||
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="提案类别" prop="typeName"></el-table-column>
|
||||
<el-table-column label="届次" prop="sessionName"></el-table-column>
|
||||
<el-table-column label="代表团" prop="delegationName"></el-table-column>
|
||||
<el-table-column prop="taskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="100px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #edit>
|
||||
<proposal-info ref="proposalInfoRef"></proposal-info>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!--#include('../../common/info.js'){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
approval: false
|
||||
},
|
||||
showApprovalForm: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openView(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
openAudit(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.showApprovalForm = true
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskName: row.curTaskName
|
||||
}
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
|
||||
handleTaskAction(val) {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.$confirm("您确定要提交吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.guava.index()
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onRevoke(row) {
|
||||
this.$confirm("您确定要撤回吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "info"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 教代会
|
||||
async meetingChange(val) {
|
||||
this.formData.delegationId = null
|
||||
this.formData.committeeId = null
|
||||
this.doSearch()
|
||||
},
|
||||
|
||||
// 查询开启的教代会
|
||||
listOpenSession() {
|
||||
this.$axios.post("/platform/proposal/common/listOpenSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = res.data
|
||||
if (this.sessionOptions) {
|
||||
this.$set(this.pageForm, "sessionId", this.sessionOptions[0].id)
|
||||
this.pageData()
|
||||
this.listDelegation()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listOpenSession()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+52
-21
@@ -99,21 +99,21 @@ layout("/layouts/platform.html"){
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- <el-form-item label="提案摘要" prop="excerpt">-->
|
||||
<!-- <span slot="label">-->
|
||||
<!-- 提案摘要-->
|
||||
<!-- <el-tooltip content="请简述提案内容和依据、改进建议和措施摘要" placement="right">-->
|
||||
<!-- <i class="el-icon-question"></i>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </span>-->
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="formData.excerpt"-->
|
||||
<!-- type="textarea"-->
|
||||
<!-- autosize-->
|
||||
<!-- :autosize="{ minRows: 4, maxRows: 4}"-->
|
||||
<!-- placeholder="提案摘要"-->
|
||||
<!-- ></el-input>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item label="提案摘要" prop="excerpt">-->
|
||||
<!-- <span slot="label">-->
|
||||
<!-- 提案摘要-->
|
||||
<!-- <el-tooltip content="请简述提案内容和依据、改进建议和措施摘要" placement="right">-->
|
||||
<!-- <i class="el-icon-question"></i>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </span>-->
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="formData.excerpt"-->
|
||||
<!-- type="textarea"-->
|
||||
<!-- autosize-->
|
||||
<!-- :autosize="{ minRows: 4, maxRows: 4}"-->
|
||||
<!-- placeholder="提案摘要"-->
|
||||
<!-- ></el-input>-->
|
||||
<!-- </el-form-item>-->
|
||||
|
||||
<el-form-item label="调研情况" prop="researchFindings">
|
||||
<el-input
|
||||
@@ -125,6 +125,13 @@ layout("/layouts/platform.html"){
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="建议承办单位" prop="suggestUnits">
|
||||
<el-select v-model="formData.suggestUnits" multiple style="width: 100%">
|
||||
<el-option :key="item.id" :label="item.name" :value="item.name"
|
||||
v-for="item in suggestUnitOptions"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="案由" prop="brief">
|
||||
<text-editor v-model="formData.brief" key="brief" placeholder="案由"></text-editor>
|
||||
</el-form-item>
|
||||
@@ -142,9 +149,9 @@ layout("/layouts/platform.html"){
|
||||
></file-upload>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item label="电子签名" prop="signature">-->
|
||||
<!-- <pc-signature v-model="formData.signature"></pc-signature>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item label="电子签名" prop="signature">-->
|
||||
<!-- <pc-signature v-model="formData.signature"></pc-signature>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
<el-row justify="end" type="flex" v-if="isWriteTime">
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
@@ -189,7 +196,7 @@ layout("/layouts/platform.html"){
|
||||
sessionId: [{required: true, message: "请选择所属教代会", trigger: ["blur", "change"]}],
|
||||
committeeId: [{required: true, message: "请选择所属委员会", trigger: ["blur", "change"]}],
|
||||
typeId: [{required: true, message: "请选择提案类别", trigger: ["blur", "change"]}],
|
||||
implementUnitId: [{required: false, message: "请选择建以落实部门", trigger: ["blur", "change"]}],
|
||||
suggestUnits: [{required: true, message: "请选择建议承办单位", trigger: ["blur", "change"]}],
|
||||
sign: [{required: true, message: "请扫描二维码进行签字", trigger: ["blur", "change"]}],
|
||||
excerpt: [{required: true, message: "请填写", trigger: ["blur", "change"]}],
|
||||
researchFindings: [{required: true, message: "请填写", trigger: ["blur", "change"]}],
|
||||
@@ -202,7 +209,10 @@ layout("/layouts/platform.html"){
|
||||
proposalConfig: {},
|
||||
noticeDialogVisible: false,
|
||||
|
||||
isWriteTime: true
|
||||
isWriteTime: true,
|
||||
|
||||
// 建议承办单位
|
||||
suggestUnitOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -243,10 +253,20 @@ layout("/layouts/platform.html"){
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
const loading = createLoading('正在提交中')
|
||||
this.$axios.post('/platform/proposal/write/submit', {info: JSON.stringify(this.formData)}).then(res => {
|
||||
loading.close()
|
||||
if (res.code === 0) {
|
||||
this.$message.success("提交成功")
|
||||
commonUtil.pjaxPush('/platform/proposal/mine')
|
||||
this.$confirm("提交成功,是否立即去邀请附议人?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
commonUtil.pjaxPush('/platform/proposal/mine?bizId=' + res.data.id + "&operation=invite")
|
||||
}).catch(() => {
|
||||
commonUtil.pjaxPush('/platform/proposal/mine')
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -312,6 +332,16 @@ layout("/layouts/platform.html"){
|
||||
// this.committeeOptions = await this.getInstitutions(val)
|
||||
},
|
||||
|
||||
|
||||
// 建议承办单位
|
||||
listSuggestUnit() {
|
||||
this.$axios.post("/platform/proposal/common/listUnderTake").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.suggestUnitOptions = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取代表团
|
||||
listDelegation() {
|
||||
return this.$axios.post("/platform/proposal/common/listDelegation", {sessionId: this.formData.sessionId}).then((res) => {
|
||||
@@ -394,6 +424,7 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.listSuggestUnit()
|
||||
this.listProposalType()
|
||||
this.getProposalConfig()
|
||||
}
|
||||
|
||||
+44
-14
@@ -19,6 +19,18 @@ const HEAD_FORM_TEMPLATE = {
|
||||
placeholder="请输入工号或者姓名"
|
||||
style="width: 100%"></user-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="viceUserId" label="副团长">
|
||||
<user-select v-model="formData.viceUserId"
|
||||
v-if="headDialogFormVisible"
|
||||
api="/platform/teacherCongress/delegation/notHeadUser"
|
||||
:api_params="{sessionId:formData.sessionId,delegationId:formData.delegationId}"
|
||||
api_input_key_name="keyWord"
|
||||
:option_list="viceHeadOptions"
|
||||
option_value="userId"
|
||||
:option_label_func="(item)=>{return item.userName + item.loginName + '(' + item.unitName + ')'}"
|
||||
placeholder="请输入工号或者姓名"
|
||||
style="width: 100%"></user-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="headDialogFormVisible = false">取 消</el-button>
|
||||
@@ -31,12 +43,15 @@ const HEAD_FORM_TEMPLATE = {
|
||||
formData: {},
|
||||
headDialogFormVisible: false,
|
||||
headOptions: [],
|
||||
viceHeadOptions: [],
|
||||
sessionOptions: [],
|
||||
|
||||
formRules: {
|
||||
sessionId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
name: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
code: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
sessionId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
name: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
code: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
userId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
viceUserId: [{required: true, message: "必填", trigger: ["change", "blur"]}]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -51,19 +66,34 @@ const HEAD_FORM_TEMPLATE = {
|
||||
sessionId,
|
||||
delegationId
|
||||
}
|
||||
// this.findHeadUser()
|
||||
this.findHeadUser()
|
||||
},
|
||||
|
||||
// findHeadUser() {
|
||||
// this.$axios.post("/platform/teacherCongress/delegation/headUser", this.formData).then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// if (res.data) {
|
||||
// this.headOptions = [res.data]
|
||||
// this.$set(this.formData, "userId", res.data.id)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
// 查询团长副团长
|
||||
findHeadUser() {
|
||||
this.$axios.post("/platform/teacherCongress/delegation/headUser", {
|
||||
...this.formData,
|
||||
type: 'TEACHER_CONGRESS_DELEGATION_HEAD'
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
if (res.data) {
|
||||
this.headOptions = [res.data]
|
||||
this.$set(this.formData, "userId", res.data.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
this.$axios.post("/platform/teacherCongress/delegation/headUser", {
|
||||
...this.formData,
|
||||
type: 'TEACHER_CONGRESS_VICE_DELEGATION_HEAD'
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
if (res.data) {
|
||||
this.viceHeadOptions = [res.data]
|
||||
this.$set(this.formData, "viceUserId", res.data.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
doSubmitHead() {
|
||||
this.$refs.headFormRef.validate((valid) => {
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column label="编码" prop="code" width="150px"></el-table-column>
|
||||
<el-table-column label="教代会" prop="sessionName"></el-table-column>
|
||||
<el-table-column label="团长" prop="delegationHead"></el-table-column>
|
||||
<el-table-column label="副团长" prop="viceDelegationHead"></el-table-column>
|
||||
<el-table-column label="操作" width="300">
|
||||
<template slot-scope="scope">
|
||||
<el-link @click="$refs.auFormRef.onOpen(scope.row.id)" size="mini" type="primary">编辑</el-link>
|
||||
|
||||
@@ -209,7 +209,7 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
// 前往学习历史
|
||||
goToHistory() {
|
||||
pjaxReplace('/platform/h5/edu/history');
|
||||
pjaxReplace('/platform/h5/edu/studyhis/');
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
<%
|
||||
layout('/platform/zhghh5/layout/layout.html',{
|
||||
title: '学习历史',
|
||||
keywords: '学习历史,视频学习,在线教育',
|
||||
description: '查看学习历史记录和进度统计'
|
||||
}){
|
||||
%>
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.history-container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 15px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
@@ -27,32 +25,38 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1989fa;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
margin: 15px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.history-header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f8f9fa;
|
||||
@@ -60,21 +64,25 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.course-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.course-meta {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.course-category {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
@@ -82,16 +90,19 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
border-radius: 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
text-align: right;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #28a745;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 60px;
|
||||
height: 4px;
|
||||
@@ -100,14 +111,17 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
overflow: hidden;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #28a745, #20c997);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.video-list {
|
||||
padding: 0 20px 15px;
|
||||
}
|
||||
|
||||
.video-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -115,9 +129,11 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
border-bottom: 1px solid #f8f9fa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.video-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.video-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -130,30 +146,37 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-icon.completed {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
}
|
||||
|
||||
.video-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.video-meta {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.video-duration {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.video-progress {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.watch-time {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
@@ -163,19 +186,19 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
|
||||
<div id="app" class="history-container">
|
||||
<van-nav-bar
|
||||
title="学习历史"
|
||||
left-text="返回"
|
||||
left-arrow
|
||||
@click-left="onClickLeft"
|
||||
/>
|
||||
|
||||
title="学习历史"
|
||||
left-text="返回"
|
||||
left-arrow
|
||||
@click-left="onClickLeft"
|
||||
></van-nav-bar>
|
||||
|
||||
<van-tabs v-model="activeTab" @change="onTabChange">
|
||||
<van-tab title="全部" name="all"></van-tab>
|
||||
<van-tab title="已完成" name="completed"></van-tab>
|
||||
<van-tab title="学习中" name="in_progress"></van-tab>
|
||||
<van-tab title="最近观看" name="recent"></van-tab>
|
||||
</van-tabs>
|
||||
|
||||
|
||||
<!-- 学习统计 -->
|
||||
<div v-if="showStats && stats" class="stats-card">
|
||||
<div class="stats-title">
|
||||
@@ -197,16 +220,16 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 学习历史列表 -->
|
||||
<van-loading v-if="loading" type="spinner" color="#1989fa" style="margin: 40px auto; display: block;">
|
||||
加载中...
|
||||
</van-loading>
|
||||
|
||||
|
||||
<van-empty v-else-if="historyList.length === 0" description="暂无学习记录">
|
||||
<van-button round type="primary" @click="loadData">刷新</van-button>
|
||||
</van-empty>
|
||||
|
||||
|
||||
<div v-else>
|
||||
<div v-for="course in historyList" :key="course.courseId" class="history-item">
|
||||
<div class="history-header" @click="viewCourse(course.courseId)">
|
||||
@@ -224,16 +247,16 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="course.videos && course.videos.length > 0" class="video-list">
|
||||
<div
|
||||
v-for="video in course.videos"
|
||||
:key="video.videoId"
|
||||
class="video-item"
|
||||
@click="playVideo(video.videoId, course.courseId, video.videoTitle)"
|
||||
<div
|
||||
v-for="video in course.videos"
|
||||
:key="video.videoId"
|
||||
class="video-item"
|
||||
@click="playVideo(video.videoId, course.courseId, video.videoTitle)"
|
||||
>
|
||||
<div class="video-icon" :class="{ completed: video.isCompleted }">
|
||||
<van-icon :name="video.isCompleted ? 'success' : 'play-circle-o'" />
|
||||
<van-icon :name="video.isCompleted ? 'success' : 'play-circle-o'"/>
|
||||
</div>
|
||||
<div class="video-info">
|
||||
<div class="video-title">{{ video.videoTitle || '未命名视频' }}</div>
|
||||
@@ -254,132 +277,134 @@ layout('/platform/zhghh5/layout/layout.html',{
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'all',
|
||||
loading: false,
|
||||
showStats: false,
|
||||
stats: null,
|
||||
historyList: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
onClickLeft() {
|
||||
window.location.href = '${base}/platform/h5/edu/courses';
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'all',
|
||||
loading: false,
|
||||
showStats: false,
|
||||
stats: null,
|
||||
historyList: []
|
||||
};
|
||||
},
|
||||
|
||||
onTabChange(name) {
|
||||
this.activeTab = name;
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 如果是全部标签,先加载统计信息
|
||||
if (this.activeTab === 'all') {
|
||||
await this.loadStats();
|
||||
this.showStats = true;
|
||||
methods: {
|
||||
onClickLeft() {
|
||||
window.location.href = '/platform/h5/edu/courses';
|
||||
},
|
||||
|
||||
onTabChange(name) {
|
||||
this.activeTab = name;
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 如果是全部标签,先加载统计信息
|
||||
if (this.activeTab === 'all') {
|
||||
await this.loadStats();
|
||||
this.showStats = true;
|
||||
} else {
|
||||
this.showStats = false;
|
||||
}
|
||||
|
||||
// 加载学习历史
|
||||
await this.loadHistory();
|
||||
} catch (error) {
|
||||
this.$toast('加载失败,请重试');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadStats() {
|
||||
try {
|
||||
const response = await this.$axios.post('/platform/h5/edu/study/stats');
|
||||
if (response.data.code === 0) {
|
||||
this.stats = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async loadHistory() {
|
||||
try {
|
||||
const response = await this.$axios.post('/platform/h5/edu/study/history', {
|
||||
params: {filter: this.activeTab}
|
||||
});
|
||||
if (response.data.code === 0) {
|
||||
this.historyList = response.data.data || [];
|
||||
} else {
|
||||
this.$toast(response.data.msg || '加载失败');
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast('网络错误,请检查网络连接');
|
||||
}
|
||||
},
|
||||
|
||||
viewCourse(courseId) {
|
||||
window.location.href = `/platform/h5/edu/course/detail?courseId=` + courseId;
|
||||
},
|
||||
|
||||
playVideo(videoId, courseId, videoTitle) {
|
||||
pjaxReplace('/platform/h5/edu/video/play?videoId=' + videoId + '&courseId=' + courseId + '&title=' + encodeURIComponent(videoTitle))
|
||||
},
|
||||
|
||||
getProgressPercent(course) {
|
||||
if (!course.videos || course.videos.length === 0) return 0;
|
||||
const completedCount = course.videos.filter(v => v.isCompleted).length;
|
||||
return Math.round((completedCount / course.videos.length) * 100);
|
||||
},
|
||||
|
||||
getVideoProgress(video) {
|
||||
if (video.isCompleted) return '已完成';
|
||||
if (video.duration > 0 && video.watchDuration > 0) {
|
||||
const percent = Math.round((video.watchDuration / video.duration) * 100);
|
||||
return `观看` + percent + '%';
|
||||
}
|
||||
return '未开始';
|
||||
},
|
||||
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '未知';
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diffTime = now - date;
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
|
||||
if (diffHours === 0) {
|
||||
const diffMinutes = Math.floor(diffTime / (1000 * 60));
|
||||
return diffMinutes <= 0 ? '刚刚' : diffMinutes + `分钟前`;
|
||||
}
|
||||
return diffHours + `小时前`;
|
||||
} else if (diffDays === 1) {
|
||||
return '昨天';
|
||||
} else if (diffDays < 7) {
|
||||
return diffDays + `天前`;
|
||||
} else {
|
||||
this.showStats = false;
|
||||
return date.getFullYear() + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0');
|
||||
}
|
||||
|
||||
// 加载学习历史
|
||||
await this.loadHistory();
|
||||
} catch (error) {
|
||||
this.$toast('加载失败,请重试');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
formatDuration(seconds) {
|
||||
if (!seconds) return '00:00';
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return String(minutes).padStart(2, '0') + ':' + String(remainingSeconds).padStart(2, '0');
|
||||
}
|
||||
},
|
||||
|
||||
async loadStats() {
|
||||
try {
|
||||
const response = await this.$http.get('${base}/platform/h5/edu/study/stats');
|
||||
if (response.data.code === 0) {
|
||||
this.stats = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async loadHistory() {
|
||||
try {
|
||||
const response = await this.$http.get('${base}/platform/h5/edu/study/history', {
|
||||
params: { filter: this.activeTab }
|
||||
});
|
||||
if (response.data.code === 0) {
|
||||
this.historyList = response.data.data || [];
|
||||
} else {
|
||||
this.$toast(response.data.msg || '加载失败');
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast('网络错误,请检查网络连接');
|
||||
}
|
||||
},
|
||||
|
||||
viewCourse(courseId) {
|
||||
window.location.href = `${base}/platform/h5/edu/course/detail?courseId=${courseId}`;
|
||||
},
|
||||
|
||||
playVideo(videoId, courseId, videoTitle) {
|
||||
window.location.href = `${base}/platform/h5/edu/video/play?videoId=${videoId}&courseId=${courseId}&title=${encodeURIComponent(videoTitle)}`;
|
||||
},
|
||||
|
||||
getProgressPercent(course) {
|
||||
if (!course.videos || course.videos.length === 0) return 0;
|
||||
const completedCount = course.videos.filter(v => v.isCompleted).length;
|
||||
return Math.round((completedCount / course.videos.length) * 100);
|
||||
},
|
||||
|
||||
getVideoProgress(video) {
|
||||
if (video.isCompleted) return '已完成';
|
||||
if (video.duration > 0 && video.watchDuration > 0) {
|
||||
const percent = Math.round((video.watchDuration / video.duration) * 100);
|
||||
return `观看${percent}%`;
|
||||
}
|
||||
return '未开始';
|
||||
},
|
||||
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '未知';
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diffTime = now - date;
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
|
||||
if (diffHours === 0) {
|
||||
const diffMinutes = Math.floor(diffTime / (1000 * 60));
|
||||
return diffMinutes <= 0 ? '刚刚' : `${diffMinutes}分钟前`;
|
||||
}
|
||||
return `${diffHours}小时前`;
|
||||
} else if (diffDays === 1) {
|
||||
return '昨天';
|
||||
} else if (diffDays < 7) {
|
||||
return `${diffDays}天前`;
|
||||
} else {
|
||||
return date.getFullYear() + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0');
|
||||
}
|
||||
},
|
||||
|
||||
formatDuration(seconds) {
|
||||
if (!seconds) return '00:00';
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return String(minutes).padStart(2, '0') + ':' + String(remainingSeconds).padStart(2, '0');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<%}%>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.study-his-container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.stats-overview {
|
||||
background: linear-gradient(135deg, #1989fa, #1976d2);
|
||||
padding: 20px 15px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
background: white;
|
||||
padding: 0 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
padding: 15px 20px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #1989fa;
|
||||
border-bottom-color: #1989fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.history-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 15px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.course-header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.course-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.course-meta {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.course-category {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
padding: 3px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.last-study-time {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
text-align: right;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #28a745;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 80px;
|
||||
height: 6px;
|
||||
background: #e9ecef;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #28a745, #20c997);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.video-list {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.video-list.expanded {
|
||||
max-height: 1000px;
|
||||
padding: 0 20px 15px;
|
||||
}
|
||||
|
||||
.video-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f8f9fa;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.video-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
margin: 0 -10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.video-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.video-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #1989fa, #1976d2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
margin-right: 15px;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.video-icon.completed {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
}
|
||||
|
||||
.video-icon.in-progress {
|
||||
background: linear-gradient(135deg, #ffc107, #ff9800);
|
||||
}
|
||||
|
||||
.video-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.4;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.video-meta {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.video-duration {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.video-progress {
|
||||
color: #28a745;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.video-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
min-width: 60px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.expand-btn {
|
||||
color: #1989fa;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.expand-btn.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
color: #ddd;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="study-his-container">
|
||||
<van-nav-bar
|
||||
title="学习统计"
|
||||
left-text="返回"
|
||||
left-arrow
|
||||
@click-left="onClickLeft"
|
||||
></van-nav-bar>
|
||||
|
||||
<!-- 学习统计概览 -->
|
||||
<div v-if="stats" class="stats-overview">
|
||||
<div class="stats-title">我的学习成果</div>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ stats.totalCourses || 0 }}</div>
|
||||
<div class="stat-label">学习课程</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ stats.completedVideos || 0 }}</div>
|
||||
<div class="stat-label">完成视频</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ Math.round((stats.totalWatchTime || 0) / 60) }}</div>
|
||||
<div class="stat-label">学习时长(分)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<div class="filter-tabs">
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<van-loading type="spinner" color="#1989fa" size="24px">加载中...</van-loading>
|
||||
</div>
|
||||
|
||||
<!-- 学习历史列表 -->
|
||||
<div v-else-if="historyList.length > 0" class="history-list">
|
||||
<div v-for="course in historyList" :key="course.courseId" class="history-card">
|
||||
<div class="course-header" @click="toggleCourseExpand(course.courseId)">
|
||||
<div class="course-info">
|
||||
<div class="course-title">{{ course.courseTitle || '未命名课程' }}</div>
|
||||
<div class="course-meta">
|
||||
<span class="course-category">{{ course.courseCategory || '未分类' }}</span>
|
||||
<span class="last-study-time">{{ formatDate(course.lastWatchTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-section">
|
||||
<div class="progress-percent">{{ getProgressPercent(course) }}%</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: getProgressPercent(course) + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<van-icon
|
||||
name="arrow-down"
|
||||
class="expand-btn"
|
||||
:class="{ expanded: expandedCourses.includes(course.courseId) }"
|
||||
></van-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="video-list"
|
||||
:class="{ expanded: expandedCourses.includes(course.courseId) }"
|
||||
>
|
||||
<div
|
||||
v-for="video in course.videos"
|
||||
:key="video.videoId"
|
||||
class="video-item"
|
||||
@click="playVideo(video.videoId, course.courseId, video.videoTitle)"
|
||||
>
|
||||
<div
|
||||
class="video-icon"
|
||||
:class="getVideoIconClass(video)"
|
||||
>
|
||||
<van-icon :name="getVideoIconName(video)" ></van-icon>
|
||||
</div>
|
||||
<div class="video-content">
|
||||
<div class="video-title">{{ video.videoTitle || '未命名视频' }}</div>
|
||||
<div class="video-meta">
|
||||
<span class="video-duration">{{ formatDuration(video.duration) }}</span>
|
||||
<span class="video-progress">{{ getVideoProgressText(video) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-time">
|
||||
{{ formatDate(video.lastWatchTime) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="empty-state">
|
||||
<div class="empty-icon">📚</div>
|
||||
<div class="empty-text">暂无学习记录</div>
|
||||
<van-button round type="primary" size="small" @click="goToCourses">
|
||||
开始学习
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
stats: null,
|
||||
activeTab: 'all',
|
||||
tabs: [
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'completed', label: '已完成' },
|
||||
{ key: 'in_progress', label: '学习中' },
|
||||
{ key: 'recent', label: '最近' }
|
||||
],
|
||||
historyList: [],
|
||||
expandedCourses: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
onClickLeft() {
|
||||
window.location.href = '/platform/h5/edu/courses';
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
await Promise.all([
|
||||
this.loadStats(),
|
||||
this.loadHistory()
|
||||
]);
|
||||
} catch (error) {
|
||||
this.$toast('加载失败,请重试');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadStats() {
|
||||
try {
|
||||
const response = await this.$axios.post('/platform/h5/edu/studyhis/stats');
|
||||
if (response.data.code === 0) {
|
||||
this.stats = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async loadHistory() {
|
||||
try {
|
||||
const response = await this.$axios.post('/platform/h5/edu/studyhis/history', {
|
||||
filter: this.activeTab
|
||||
});
|
||||
if (response.data.code === 0) {
|
||||
this.historyList = response.data.data || [];
|
||||
} else {
|
||||
this.$toast(response.data.msg || '加载失败');
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast('网络错误,请检查网络连接');
|
||||
}
|
||||
},
|
||||
|
||||
switchTab(tabKey) {
|
||||
if (this.activeTab === tabKey) return;
|
||||
|
||||
this.activeTab = tabKey;
|
||||
this.expandedCourses = [];
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
toggleCourseExpand(courseId) {
|
||||
const index = this.expandedCourses.indexOf(courseId);
|
||||
if (index > -1) {
|
||||
this.expandedCourses.splice(index, 1);
|
||||
} else {
|
||||
this.expandedCourses.push(courseId);
|
||||
}
|
||||
},
|
||||
|
||||
playVideo(videoId, courseId, videoTitle) {
|
||||
const url = '/platform/h5/edu/video/play?videoId=' + videoId +
|
||||
'&courseId=' + courseId +
|
||||
'&title=' + encodeURIComponent(videoTitle || '');
|
||||
window.location.href = url;
|
||||
},
|
||||
|
||||
goToCourses() {
|
||||
window.location.href = '/platform/h5/edu/courses';
|
||||
},
|
||||
|
||||
getProgressPercent(course) {
|
||||
if (!course.videos || course.videos.length === 0) return 0;
|
||||
const completedCount = course.videos.filter(v => v.isCompleted).length;
|
||||
return Math.round((completedCount / course.videos.length) * 100);
|
||||
},
|
||||
|
||||
getVideoIconClass(video) {
|
||||
if (video.isCompleted) return 'completed';
|
||||
if (video.watchDuration > 0) return 'in-progress';
|
||||
return '';
|
||||
},
|
||||
|
||||
getVideoIconName(video) {
|
||||
if (video.isCompleted) return 'success';
|
||||
if (video.watchDuration > 0) return 'pause-circle-o';
|
||||
return 'play-circle-o';
|
||||
},
|
||||
|
||||
getVideoProgressText(video) {
|
||||
if (video.isCompleted) return '已完成';
|
||||
if (video.duration > 0 && video.watchDuration > 0) {
|
||||
const percent = Math.round((video.watchDuration / video.duration) * 100);
|
||||
return '观看' + percent + '%';
|
||||
}
|
||||
return '未开始';
|
||||
},
|
||||
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '未知';
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diffTime = now - date;
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
|
||||
if (diffHours === 0) {
|
||||
const diffMinutes = Math.floor(diffTime / (1000 * 60));
|
||||
return diffMinutes <= 0 ? '刚刚' : diffMinutes + '分钟前';
|
||||
}
|
||||
return diffHours + '小时前';
|
||||
} else if (diffDays === 1) {
|
||||
return '昨天';
|
||||
} else if (diffDays < 7) {
|
||||
return diffDays + '天前';
|
||||
} else {
|
||||
return (date.getMonth() + 1) + '-' + date.getDate();
|
||||
}
|
||||
},
|
||||
|
||||
formatDuration(seconds) {
|
||||
if (!seconds) return '00:00';
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return String(minutes).padStart(2, '0') + ':' + String(remainingSeconds).padStart(2, '0');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="团长审核" placeholder fixed></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
:show-action="false"
|
||||
:reverse-color="false"
|
||||
input-align="left"
|
||||
placeholder="请输入提案名称搜索"
|
||||
@search="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions" :multiple="false"
|
||||
@change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.approvalText"
|
||||
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/proposal/delegation/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案编号">{{row.code}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<proposal-info ref="proposalInfoRef"></proposal-info>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
name: null,
|
||||
sessionId: null,
|
||||
approvalText: "0",
|
||||
approval: false
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
formData: {},
|
||||
showApprovalForm: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
this.listSession()
|
||||
},
|
||||
listSession() {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = [
|
||||
{
|
||||
text: "全部届次",
|
||||
value: null
|
||||
}
|
||||
].concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
|
||||
if (this.sessionOptions.length > 0) {
|
||||
this.pageForm.sessionId = this.sessionOptions[0].value
|
||||
this.doSearch()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -17,14 +17,16 @@ layout("/layouts/platform_h5.html"){
|
||||
<work v-if="homeTabbarActive === 'work'"></work>
|
||||
<mine v-if="homeTabbarActive === 'mine'"></mine>
|
||||
<apps v-if="homeTabbarActive === 'apps'"></apps>
|
||||
<msg v-if="homeTabbarActive === 'msg'"></msg>
|
||||
</div>
|
||||
|
||||
<div id="page-tarbar" class="page-tarbar">
|
||||
<van-tabbar v-model="homeTabbarActive" @change="homeTabbarChange">
|
||||
<van-tabbar-item name="home" icon="wap-home-o">首页</van-tabbar-item>
|
||||
<van-tabbar-item name="apps" icon="gem">应用</van-tabbar-item>
|
||||
<van-tabbar-item name="work" icon="gem">工作台</van-tabbar-item>
|
||||
<!-- <van-tabbar-item name="work" icon="gem">工作台</van-tabbar-item>-->
|
||||
<van-tabbar-item name="todo" icon="gem">待办</van-tabbar-item>
|
||||
<van-tabbar-item name="msg" icon="gem">消息</van-tabbar-item>
|
||||
<van-tabbar-item name="mine" icon="user-o">我的</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
@@ -37,6 +39,7 @@ layout("/layouts/platform_h5.html"){
|
||||
<!--#include("mine.js"){}#-->
|
||||
<!--#include("todo.js"){}#-->
|
||||
<!--#include("apps.js"){}#-->
|
||||
<!--#include("msg.js"){}#-->
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
@@ -45,7 +48,8 @@ layout("/layouts/platform_h5.html"){
|
||||
mine,
|
||||
work,
|
||||
todo,
|
||||
apps
|
||||
apps,
|
||||
msg
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,598 @@
|
||||
const msg= {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="message-center">
|
||||
<van-nav-bar title="消息中心" placeholder fixed></van-nav-bar>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<van-sticky offset-top="46px">
|
||||
<!-- <div class="filter-section">-->
|
||||
<!-- <van-row gutter="10">-->
|
||||
<!-- <van-col span="12">-->
|
||||
<!-- <van-field-->
|
||||
<!-- v-model="filters.type"-->
|
||||
<!-- is-link-->
|
||||
<!-- readonly-->
|
||||
<!-- label="消息类型"-->
|
||||
<!-- placeholder="选择消息类型"-->
|
||||
<!-- @click="showTypePicker = true"-->
|
||||
<!-- />-->
|
||||
<!-- </van-col>-->
|
||||
<!-- <van-col span="12" v-if="pageForm.isRead == 0">-->
|
||||
<!-- <van-button -->
|
||||
<!-- type="primary" -->
|
||||
<!-- size="small" -->
|
||||
<!-- :loading="loading"-->
|
||||
<!-- @click="markAllAsRead"-->
|
||||
<!-- style="margin-top: 6px; width: 100%;"-->
|
||||
<!-- >-->
|
||||
<!-- 全部已读-->
|
||||
<!-- </van-button>-->
|
||||
<!-- </van-col>-->
|
||||
<!-- </van-row>-->
|
||||
<!-- </div>-->
|
||||
<!-- 标签页 -->
|
||||
<van-tabs v-model="activeTab" @change="handleTabChange">
|
||||
<van-tab title="全部消息" name="-1">
|
||||
<template #title>
|
||||
<van-icon name="chat-o" />
|
||||
全部消息 ({{stats.total}})
|
||||
</template>
|
||||
</van-tab>
|
||||
<van-tab title="未读消息" name="0">
|
||||
<template #title>
|
||||
<van-icon name="bell" />
|
||||
未读消息 ({{stats.unread}})
|
||||
</template>
|
||||
</van-tab>
|
||||
<van-tab title="已读消息" name="1">
|
||||
<template #title>
|
||||
<van-icon name="passed" />
|
||||
已读消息 ({{stats.read}})
|
||||
</template>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
@click="viewMessage(message)"
|
||||
class="custom-message-item"
|
||||
:class="{'unread-message': !message.isRead}"
|
||||
>
|
||||
<div class="message-header">
|
||||
<div class="message-title-wrapper">
|
||||
<van-tag
|
||||
:type="message.type === 1 ? 'danger' : 'primary'"
|
||||
size="mini"
|
||||
style="margin-right: 8px;"
|
||||
>
|
||||
{{getMessageTypeName(message.type)}}
|
||||
</van-tag>
|
||||
<!-- <span class="message-title">{{message.title}}</span>-->
|
||||
<van-tag
|
||||
v-if="!message.isRead"
|
||||
type="warning"
|
||||
size="mini"
|
||||
style="margin-left: 8px;"
|
||||
>
|
||||
未读
|
||||
</van-tag>
|
||||
</div>
|
||||
<div class="message-time">{{formatTime(message.createdAt)}}</div>
|
||||
</div>
|
||||
<div class="message-content">{{getContentPreview(message.content)}}</div>
|
||||
<div class="message-arrow">
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
|
||||
<!-- 消息类型选择器 -->
|
||||
<van-popup v-model="showTypePicker" position="bottom">
|
||||
<van-picker
|
||||
:columns="typeColumns"
|
||||
@confirm="onTypeConfirm"
|
||||
@cancel="showTypePicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<!-- 消息详情弹窗 -->
|
||||
<van-popup
|
||||
v-model="detailVisible"
|
||||
position="bottom"
|
||||
:style="{ height: '80%' }"
|
||||
closeable
|
||||
close-icon-position="top-right"
|
||||
>
|
||||
<div class="message-detail" v-if="currentMessage.id">
|
||||
<div class="detail-header">
|
||||
<h3>{{currentMessage.title}}</h3>
|
||||
<div class="detail-meta">
|
||||
<van-tag :type="currentMessage?.globalMessage?.type === 1 ? 'danger' : 'primary'">
|
||||
{{getMessageTypeName(currentMessage?.globalMessage?.type)}}
|
||||
</van-tag>
|
||||
<span class="detail-time">{{formatTime(currentMessage.createdAt)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-content" v-html="currentMessage?.globalMessage?.content"></div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
`,
|
||||
data(){
|
||||
return{
|
||||
activeTab: '-1',
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
finished: false,
|
||||
messages: [],
|
||||
stats: {
|
||||
total: 0,
|
||||
unread: 0,
|
||||
read: 0
|
||||
},
|
||||
filters: {
|
||||
type: ''
|
||||
},
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
isRead: -1
|
||||
},
|
||||
detailVisible: false,
|
||||
currentMessage: {},
|
||||
showTypePicker: false,
|
||||
typeColumns: [
|
||||
{ text: '全部类型', value: '' },
|
||||
{ text: '系统公告', value: '1' },
|
||||
{ text: '消息通知', value: '2' }
|
||||
]
|
||||
}
|
||||
},
|
||||
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(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.messages = []
|
||||
this.finished = false
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post('/platform/v4/msg/pageData', this.pageForm)
|
||||
if (resp.code === 0) {
|
||||
const newMessages = resp.data.list || []
|
||||
if (isRefresh) {
|
||||
this.messages = newMessages
|
||||
} else {
|
||||
this.messages = [...this.messages, ...newMessages]
|
||||
}
|
||||
|
||||
this.pageForm.totalCount = resp.data.totalCount
|
||||
|
||||
// 判断是否还有更多数据
|
||||
if (this.messages.length >= this.pageForm.totalCount) {
|
||||
this.finished = true
|
||||
}
|
||||
} else {
|
||||
this.$toast(resp.msg)
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast('加载消息失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
}
|
||||
},
|
||||
|
||||
// 查看消息详情
|
||||
async viewMessage(message) {
|
||||
try {
|
||||
const resp = await this.$axios.post(`/platform/v4/msg/detail/` + message.id)
|
||||
if (resp.code === 0) {
|
||||
this.currentMessage = resp.data
|
||||
this.detailVisible = true
|
||||
|
||||
// 如果是未读消息,标记为已读
|
||||
if (!message.isRead) {
|
||||
await this.markAsRead(message.id)
|
||||
message.isRead = true
|
||||
this.loadStats()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast('加载消息详情失败')
|
||||
}
|
||||
},
|
||||
|
||||
// 标记单条消息为已读
|
||||
async markAsRead(messageId) {
|
||||
try {
|
||||
await this.$axios.post(`/platform/v4/msg/read/` + messageId)
|
||||
} catch (error) {
|
||||
console.error('标记已读失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
// 全部标记为已读
|
||||
async markAllAsRead() {
|
||||
this.$dialog.confirm({
|
||||
title: '提示',
|
||||
message: '确定要将所有未读消息标记为已读吗?'
|
||||
}).then(async () => {
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post('/platform/v4/msg/read/all')
|
||||
if (resp.code === 0) {
|
||||
this.$toast.success('操作成功')
|
||||
this.loadMessages(true)
|
||||
this.loadStats()
|
||||
} else {
|
||||
this.$toast(resp.msg)
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast('操作失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
},
|
||||
|
||||
// 切换标签页
|
||||
handleTabChange(name) {
|
||||
this.pageForm.isRead = parseInt(name)
|
||||
this.loadMessages(true)
|
||||
},
|
||||
|
||||
// 点击统计卡片切换标签
|
||||
switchTab(tabValue) {
|
||||
this.activeTab = tabValue.toString()
|
||||
this.pageForm.isRead = tabValue
|
||||
this.loadMessages(true)
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onRefresh() {
|
||||
this.loadMessages(true)
|
||||
this.loadStats()
|
||||
},
|
||||
|
||||
// 上拉加载更多
|
||||
onLoad() {
|
||||
if (this.finished) {
|
||||
return
|
||||
}
|
||||
this.pageForm.pageNumber++
|
||||
this.loadMessages()
|
||||
},
|
||||
|
||||
// 消息类型选择确认
|
||||
onTypeConfirm(value) {
|
||||
this.filters.type = value.value
|
||||
this.showTypePicker = false
|
||||
this.loadMessages(true)
|
||||
},
|
||||
|
||||
// 获取消息类型名称
|
||||
getMessageTypeName(type) {
|
||||
return type === 1 ? '系统公告' : '消息通知'
|
||||
},
|
||||
|
||||
// 获取内容预览
|
||||
getContentPreview(content) {
|
||||
if (!content) return ''
|
||||
// 移除HTML标签
|
||||
const text = content.replace(/<[^>]*>/g, '')
|
||||
return text.length > 60 ? text.substring(0, 30) + '...' : text
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(timestamp) {
|
||||
return this.$moment(timestamp).format('MM-DD HH:mm')
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadStats()
|
||||
this.loadMessages(true)
|
||||
},
|
||||
|
||||
style: /*language=CSS*/ `
|
||||
.message-center {
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 自定义消息列表项样式 */
|
||||
/deep/ .custom-message-item {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/deep/ .custom-message-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/deep/ .custom-message-item:active {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/deep/ .message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/deep/ .message-time {
|
||||
font-size: 12px;
|
||||
color: #969799;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
/deep/ .message-content {
|
||||
font-size: 14px;
|
||||
color: #646566;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
/deep/ .message-arrow {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #c8c9cc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 筛选区域样式 */
|
||||
/deep/ .filter-section {
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/deep/ .message-title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/deep/ .message-title {
|
||||
flex: 1;
|
||||
/*font-weight: 500;*/
|
||||
color: #323233;
|
||||
}
|
||||
|
||||
/* 消息详情弹窗样式 */
|
||||
/deep/ .message-detail {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/deep/ .detail-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
/deep/ .detail-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
margin: 0 0 8px 0;
|
||||
color: #323233;
|
||||
}
|
||||
|
||||
/deep/ .detail-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/deep/ .detail-time {
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
/deep/ .detail-content {
|
||||
line-height: 1.8;
|
||||
font-size: 15px;
|
||||
color: #323233;
|
||||
}
|
||||
|
||||
/deep/ .detail-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/deep/ .detail-content p {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
/* 标签页样式优化 */
|
||||
/deep/ .van-tabs__nav {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/deep/ .van-tab {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/deep/ .van-tab--active {
|
||||
color: var(--color-primary, #1989fa);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/deep/ .van-tabs__line {
|
||||
background-color: var(--color-primary, #1989fa);
|
||||
}
|
||||
|
||||
/* 列表样式优化 */
|
||||
/deep/ .van-cell__title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/deep/ .van-cell__label {
|
||||
font-size: 13px;
|
||||
color: #969799;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/deep/ .van-cell__value {
|
||||
font-size: 12px;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
/deep/ .van-tag--mini {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
/deep/ .van-popup--bottom {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
/* 下拉刷新和上拉加载样式 */
|
||||
/deep/ .van-pull-refresh__track {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/deep/ .van-list__finished-text {
|
||||
color: #969799;
|
||||
font-size: 12px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
/deep/ .van-empty {
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/deep/ .van-empty__description {
|
||||
color: #969799;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 简约动画效果 */
|
||||
|
||||
/* 基础过渡效果 */
|
||||
/deep/ .oa-message-item,
|
||||
/deep/ .oa-stat-item,
|
||||
/deep/ .oa-refresh-btn,
|
||||
/deep/ .oa-search-input {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 页面进入动画 */
|
||||
/deep/ .page-enter {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 简化的响应式动画 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
/deep/ .oa-header {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
/deep/ .oa-header-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/deep/ .oa-stats-panel,
|
||||
/deep/ .oa-toolbar,
|
||||
/deep/ .oa-message-list {
|
||||
margin: 6px 12px;
|
||||
}
|
||||
|
||||
/deep/ .oa-stat-item {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
/deep/ .oa-stat-number {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/deep/ .oa-stat-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/deep/ .oa-message-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/deep/ .oa-message-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/deep/ .oa-message-content {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -5,15 +5,16 @@ const todo = {
|
||||
<!-- 标签页 -->
|
||||
<van-sticky offset-top="46px">
|
||||
<van-tabs v-model="activeTab" @change="handleTabChange" animated swipeable>
|
||||
<van-tab title="待办" name="todo"></van-tab>
|
||||
<van-tab title="已办" name="done"></van-tab>
|
||||
<van-tab title="我发起的" name="started"></van-tab>
|
||||
<van-tab :title="'待办(' + todoCount + ')'" name="todo"></van-tab>
|
||||
<van-tab :title="'已办(' + doneCount + ')'" name="done"></van-tab>
|
||||
<van-tab :title="'发起(' + startedCount + ')'" name="started"></van-tab>
|
||||
</van-tabs>
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="filter-section">
|
||||
<div>
|
||||
<div class="search-form">
|
||||
<van-search v-model="pageForm.searchKeyword" placeholder="请输入搜索关键词"></van-search>
|
||||
<van-search v-model="pageForm.searchKeyword" placeholder="请输入搜索关键词"
|
||||
@search="doSearch"></van-search>
|
||||
</div>
|
||||
|
||||
<!-- <van-field-->
|
||||
@@ -39,63 +40,27 @@ const todo = {
|
||||
</van-sticky>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="getTasks"
|
||||
>
|
||||
<div v-if="tasks.length > 0" class="task-list">
|
||||
<div
|
||||
v-for="task in tasks"
|
||||
:key="task.taskId"
|
||||
class="task-item"
|
||||
>
|
||||
<div class="task-content">
|
||||
<div class="task-title">{{task.variable?.instanceName || '无标题流程'}}</div>
|
||||
|
||||
<div class="task-info">
|
||||
<span class="task-info-label">任务节点:</span>
|
||||
<span class="task-info-value">{{task.taskName}}</span>
|
||||
</div>
|
||||
|
||||
<div class="task-info">
|
||||
<span class="task-info-label">流程分类:</span>
|
||||
<span class="task-info-value">
|
||||
{{categoryOptions.find(item => item.id === task.category)?.name || '未知分类'}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="task-info">
|
||||
<span class="task-info-label">申请人:</span>
|
||||
<span class="task-info-value">{{task.variable?.initiatorName || '未知'}}</span>
|
||||
</div>
|
||||
|
||||
<div class="task-info" v-if="activeTab === 'started'">
|
||||
<span class="task-info-label">状态:</span>
|
||||
<span class="task-info-value">
|
||||
{{processStatusMap[task.state]?.text || '未知状态'}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="task-footer">
|
||||
<van-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
@click="openView(task)"
|
||||
>查看
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table-list :api="'/flow/todoCenter/'+ activeTab" :page_form.sync="pageForm"
|
||||
ref="tableListRef"
|
||||
@ready="doSearch"
|
||||
title="variable.instanceName">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="任务节点">{{row.taskName}}</table-column>
|
||||
<table-column label="流程分类">{{categoryOptions.find(item => item.id === row.category)?.name ||
|
||||
'未知分类'}}
|
||||
</table-column>
|
||||
<table-column label="申请人">{{row.variable?.initiatorName || '未知'}}</table-column>
|
||||
<table-column label="状态" v-if="activeTab === 'started'">{{processStatusMap[row.state]?.text ||
|
||||
'未知状态'}}
|
||||
</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-section">
|
||||
<van-empty :description="getEmptyText()"></van-empty>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
@@ -147,8 +112,6 @@ const todo = {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.listCategory()]);
|
||||
// 初始加载任务
|
||||
// this.getTasks();
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
@@ -181,61 +144,20 @@ const todo = {
|
||||
}
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
if (this.refreshing) {
|
||||
this.tasks = [];
|
||||
this.refreshing = false;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const {code,data,msg} = await this.$axios.post("/flow/todoCenter/" + this.activeTab, this.pageForm);
|
||||
if (code === 0) {
|
||||
this.tasks = this.tasks.concat(data.list || []);
|
||||
this.pageForm.totalCount = data.totalCount;
|
||||
this.loading = false;
|
||||
|
||||
// 数据全部加载完成
|
||||
if (this.tasks.length >= this.pageForm.totalCount) {
|
||||
this.finished = true;
|
||||
} else {
|
||||
this.pageForm.pageNumber++;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast("获取任务列表失败");
|
||||
console.error("获取任务列表失败:", error);
|
||||
this.loading = false;
|
||||
this.refreshing = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onRefresh() {
|
||||
this.pageForm.pageNumber = 1;
|
||||
this.finished = false;
|
||||
this.getTasks();
|
||||
},
|
||||
|
||||
// 处理标签页变化
|
||||
handleTabChange(name) {
|
||||
this.activeTab = name;
|
||||
this.pageForm.pageNumber = 1;
|
||||
this.tasks = [];
|
||||
this.finished = false;
|
||||
this.getTasks();
|
||||
this.doSearch()
|
||||
this.getStatistics();
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.pageForm.pageNumber = 1;
|
||||
this.tasks = [];
|
||||
this.finished = false;
|
||||
this.getTasks();
|
||||
this.getStatistics();
|
||||
doSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
@@ -243,7 +165,7 @@ const todo = {
|
||||
this.pageForm.searchKeyword = "";
|
||||
this.pageForm.category = "";
|
||||
this.categoryLabel = "";
|
||||
this.search();
|
||||
this.doSearch();
|
||||
},
|
||||
|
||||
// 分类选择确认
|
||||
@@ -254,7 +176,7 @@ const todo = {
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
openView(task) {
|
||||
onView(task) {
|
||||
const {taskId, taskKey, instanceId, businessNo, formKey} = task;
|
||||
|
||||
if (!formKey) {
|
||||
@@ -278,10 +200,24 @@ const todo = {
|
||||
}
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.task-center {
|
||||
margin: 0 auto;
|
||||
/* 标签页样式优化 */
|
||||
/deep/ .van-tabs__nav {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/deep/ .van-tab {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/deep/ .van-tab--active {
|
||||
color: var(--color-primary, #1989fa);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/deep/ .van-tabs__line {
|
||||
background-color: var(--color-primary, #1989fa);
|
||||
}
|
||||
|
||||
/deep/ .filter-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user