feat: 工作日志增加标题字段及移动端UI优化
后端: - 工作日志表增加title字段 - 新增按日期查询日志接口 - 修复逻辑删除时updated_by为null的问题 移动端: - 日历月份切换功能 - 日期点击弹窗(有日志显示详情,无日志显示新建表单) - 登录页面居中及样式调整 - 日志列表卡片样式优化(标题加粗、内容去边框) - 新增"我的"页面(用户信息+退出登录) - 操作按钮使用|分隔符 构建脚本: - 自动复制构建产物到deploy目录
This commit is contained in:
parent
e36ac36af5
commit
4218128e38
@ -74,8 +74,3 @@ echo -e "产物列表:"
|
|||||||
echo -e " - worklog-api.tar.gz (${API_SIZE})"
|
echo -e " - worklog-api.tar.gz (${API_SIZE})"
|
||||||
echo -e " - wladmin/ (${WEB_SIZE})"
|
echo -e " - wladmin/ (${WEB_SIZE})"
|
||||||
echo -e " - wlmobile/ (${MOBILE_SIZE})"
|
echo -e " - wlmobile/ (${MOBILE_SIZE})"
|
||||||
echo ""
|
|
||||||
echo -e "部署命令示例:"
|
|
||||||
echo -e " scp ${DEPLOY_DIR}/worklog-api.tar.gz user@server:/opt/worklog/"
|
|
||||||
echo -e " scp -r ${DEPLOY_DIR}/wladmin user@server:/var/www/worklog/"
|
|
||||||
echo -e " scp -r ${DEPLOY_DIR}/wlmobile user@server:/var/www/worklog/"
|
|
||||||
|
|||||||
@ -52,12 +52,15 @@ fi
|
|||||||
DIST_SIZE=$(du -sh dist | cut -f1)
|
DIST_SIZE=$(du -sh dist | cut -f1)
|
||||||
DIST_FILES=$(find dist -type f | wc -l)
|
DIST_FILES=$(find dist -type f | wc -l)
|
||||||
|
|
||||||
|
# 部署到 deploy 目录
|
||||||
|
echo -e "${YELLOW}部署到 ${DEPLOY_DIR}/wlmobile/...${NC}"
|
||||||
|
mkdir -p ${DEPLOY_DIR}/wlmobile
|
||||||
|
rm -rf ${DEPLOY_DIR}/wlmobile/*
|
||||||
|
cp -r dist/* ${DEPLOY_DIR}/wlmobile/
|
||||||
|
|
||||||
echo -e "${GREEN}=========================================${NC}"
|
echo -e "${GREEN}=========================================${NC}"
|
||||||
echo -e "${GREEN}构建完成!${NC}"
|
echo -e "${GREEN}构建并部署完成!${NC}"
|
||||||
echo -e "${GREEN}=========================================${NC}"
|
echo -e "${GREEN}=========================================${NC}"
|
||||||
echo -e "输出目录: ${MOBILE_DIR}/dist"
|
echo -e "输出目录: ${DEPLOY_DIR}/wlmobile/"
|
||||||
echo -e "总大小: ${DIST_SIZE}"
|
echo -e "总大小: ${DIST_SIZE}"
|
||||||
echo -e "文件数量: ${DIST_FILES}"
|
echo -e "文件数量: ${DIST_FILES}"
|
||||||
echo ""
|
|
||||||
echo -e "部署目录: ${DEPLOY_DIR}/wlmobile/"
|
|
||||||
echo -e "部署命令: cp -r dist/* ${DEPLOY_DIR}/wlmobile/"
|
|
||||||
|
|||||||
@ -52,12 +52,15 @@ fi
|
|||||||
DIST_SIZE=$(du -sh dist | cut -f1)
|
DIST_SIZE=$(du -sh dist | cut -f1)
|
||||||
DIST_FILES=$(find dist -type f | wc -l)
|
DIST_FILES=$(find dist -type f | wc -l)
|
||||||
|
|
||||||
|
# 部署到 deploy 目录
|
||||||
|
echo -e "${YELLOW}部署到 ${DEPLOY_DIR}/wladmin/...${NC}"
|
||||||
|
mkdir -p ${DEPLOY_DIR}/wladmin
|
||||||
|
rm -rf ${DEPLOY_DIR}/wladmin/*
|
||||||
|
cp -r dist/* ${DEPLOY_DIR}/wladmin/
|
||||||
|
|
||||||
echo -e "${GREEN}=========================================${NC}"
|
echo -e "${GREEN}=========================================${NC}"
|
||||||
echo -e "${GREEN}构建完成!${NC}"
|
echo -e "${GREEN}构建并部署完成!${NC}"
|
||||||
echo -e "${GREEN}=========================================${NC}"
|
echo -e "${GREEN}=========================================${NC}"
|
||||||
echo -e "输出目录: ${WEB_DIR}/dist"
|
echo -e "输出目录: ${DEPLOY_DIR}/wladmin/"
|
||||||
echo -e "总大小: ${DIST_SIZE}"
|
echo -e "总大小: ${DIST_SIZE}"
|
||||||
echo -e "文件数量: ${DIST_FILES}"
|
echo -e "文件数量: ${DIST_FILES}"
|
||||||
echo ""
|
|
||||||
echo -e "部署目录: ${DEPLOY_DIR}/wladmin/"
|
|
||||||
echo -e "部署命令: cp -r dist/* ${DEPLOY_DIR}/wladmin/"
|
|
||||||
|
|||||||
@ -94,6 +94,7 @@ CREATE TABLE `work_log` (
|
|||||||
`id` VARCHAR(20) NOT NULL COMMENT '日志ID(雪花算法生成)',
|
`id` VARCHAR(20) NOT NULL COMMENT '日志ID(雪花算法生成)',
|
||||||
`user_id` VARCHAR(20) NOT NULL COMMENT '记录人ID',
|
`user_id` VARCHAR(20) NOT NULL COMMENT '记录人ID',
|
||||||
`log_date` DATE NOT NULL COMMENT '日志日期',
|
`log_date` DATE NOT NULL COMMENT '日志日期',
|
||||||
|
`title` VARCHAR(200) DEFAULT NULL COMMENT '日志标题',
|
||||||
`record_time` DATETIME NOT NULL COMMENT '记录时间',
|
`record_time` DATETIME NOT NULL COMMENT '记录时间',
|
||||||
`content` TEXT NOT NULL COMMENT '日志内容(Markdown格式,最大2000汉字)',
|
`content` TEXT NOT NULL COMMENT '日志内容(Markdown格式,最大2000汉字)',
|
||||||
`template_id` VARCHAR(20) DEFAULT NULL COMMENT '使用模板ID',
|
`template_id` VARCHAR(20) DEFAULT NULL COMMENT '使用模板ID',
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import org.springframework.format.annotation.DateTimeFormat;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志控制器
|
* 日志控制器
|
||||||
@ -117,4 +119,30 @@ public class LogController {
|
|||||||
logService.deleteLog(id);
|
logService.deleteLog(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日历数据(有日志的日期列表)
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取日历数据", description = "获取指定月份有日志的日期列表")
|
||||||
|
@GetMapping("/calendar")
|
||||||
|
public Result<List<String>> getCalendarData(
|
||||||
|
@Parameter(description = "年份") @RequestParam Integer year,
|
||||||
|
@Parameter(description = "月份(1-12)") @RequestParam Integer month) {
|
||||||
|
Set<LocalDate> dates = logService.getLogDatesByMonth(year, month);
|
||||||
|
List<String> dateStrings = dates.stream()
|
||||||
|
.map(LocalDate::toString)
|
||||||
|
.toList();
|
||||||
|
return Result.success(dateStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据日期获取日志
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据日期获取日志", description = "获取当前用户指定日期的日志")
|
||||||
|
@GetMapping("/by-date")
|
||||||
|
public Result<LogVO> getLogByDate(
|
||||||
|
@Parameter(description = "日期(yyyy-MM-dd)") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
|
||||||
|
LogVO log = logService.getLogByDate(date);
|
||||||
|
return Result.success(log);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,11 @@ public class WorkLog implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private LocalDate logDate;
|
private LocalDate logDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录时间
|
* 记录时间
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
|||||||
import com.wjbl.worklog.data.entity.WorkLog;
|
import com.wjbl.worklog.data.entity.WorkLog;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作日志数据服务接口
|
* 工作日志数据服务接口
|
||||||
@ -42,4 +43,14 @@ public interface WorkLogDataService extends IService<WorkLog> {
|
|||||||
* @return 工作日志
|
* @return 工作日志
|
||||||
*/
|
*/
|
||||||
WorkLog getByUserIdAndLogDate(String userId, LocalDate logDate);
|
WorkLog getByUserIdAndLogDate(String userId, LocalDate logDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定用户指定月份有日志的日期列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param year 年份
|
||||||
|
* @param month 月份(1-12)
|
||||||
|
* @return 有日志的日期集合
|
||||||
|
*/
|
||||||
|
Set<LocalDate> getLogDatesByMonth(String userId, int year, int month);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import com.wjbl.worklog.data.service.WorkLogDataService;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作日志数据服务实现类
|
* 工作日志数据服务实现类
|
||||||
@ -46,4 +48,21 @@ public class WorkLogDataServiceImpl extends ServiceImpl<WorkLogMapper, WorkLog>
|
|||||||
.eq(WorkLog::getLogDate, logDate)
|
.eq(WorkLog::getLogDate, logDate)
|
||||||
.one();
|
.one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<LocalDate> getLogDatesByMonth(String userId, int year, int month) {
|
||||||
|
LocalDate startDate = LocalDate.of(year, month, 1);
|
||||||
|
LocalDate endDate = startDate.plusMonths(1).minusDays(1);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<WorkLog> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(WorkLog::getUserId, userId)
|
||||||
|
.ge(WorkLog::getLogDate, startDate)
|
||||||
|
.le(WorkLog::getLogDate, endDate)
|
||||||
|
.select(WorkLog::getLogDate);
|
||||||
|
|
||||||
|
Set<LocalDate> dates = new HashSet<>();
|
||||||
|
list(wrapper).forEach(log -> dates.add(log.getLogDate()));
|
||||||
|
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,13 @@ public class LogCreateDTO implements Serializable {
|
|||||||
@Schema(description = "日志日期,默认当天")
|
@Schema(description = "日志日期,默认当天")
|
||||||
private LocalDate logDate;
|
private LocalDate logDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志标题
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "标题不能为空")
|
||||||
|
@Schema(description = "日志标题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志内容(Markdown格式)
|
* 日志内容(Markdown格式)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -14,6 +14,12 @@ public class LogUpdateDTO implements Serializable {
|
|||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志标题
|
||||||
|
*/
|
||||||
|
@Schema(description = "日志标题")
|
||||||
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志内容(Markdown格式)
|
* 日志内容(Markdown格式)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import com.wjbl.worklog.dto.LogUpdateDTO;
|
|||||||
import com.wjbl.worklog.vo.LogVO;
|
import com.wjbl.worklog.vo.LogVO;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志服务接口
|
* 日志服务接口
|
||||||
@ -66,4 +67,21 @@ public interface LogService {
|
|||||||
* @return 日志信息
|
* @return 日志信息
|
||||||
*/
|
*/
|
||||||
LogVO getLogById(String id);
|
LogVO getLogById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定月份有日志的日期列表
|
||||||
|
*
|
||||||
|
* @param year 年份
|
||||||
|
* @param month 月份(1-12)
|
||||||
|
* @return 有日志的日期集合
|
||||||
|
*/
|
||||||
|
Set<LocalDate> getLogDatesByMonth(int year, int month);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据日期获取日志
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 日志信息(如果不存在返回null)
|
||||||
|
*/
|
||||||
|
LogVO getLogByDate(LocalDate date);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
package com.wjbl.worklog.service.impl;
|
package com.wjbl.worklog.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.wjbl.worklog.common.context.UserContext;
|
import com.wjbl.worklog.common.context.UserContext;
|
||||||
import com.wjbl.worklog.common.exception.BusinessException;
|
import com.wjbl.worklog.common.exception.BusinessException;
|
||||||
|
import com.wjbl.worklog.data.entity.User;
|
||||||
import com.wjbl.worklog.data.entity.WorkLog;
|
import com.wjbl.worklog.data.entity.WorkLog;
|
||||||
|
import com.wjbl.worklog.data.service.UserDataService;
|
||||||
import com.wjbl.worklog.data.service.WorkLogDataService;
|
import com.wjbl.worklog.data.service.WorkLogDataService;
|
||||||
import com.wjbl.worklog.dto.LogCreateDTO;
|
import com.wjbl.worklog.dto.LogCreateDTO;
|
||||||
import com.wjbl.worklog.dto.LogUpdateDTO;
|
import com.wjbl.worklog.dto.LogUpdateDTO;
|
||||||
@ -16,6 +19,7 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志服务实现类
|
* 日志服务实现类
|
||||||
@ -26,6 +30,7 @@ import java.time.LocalDateTime;
|
|||||||
public class LogServiceImpl implements LogService {
|
public class LogServiceImpl implements LogService {
|
||||||
|
|
||||||
private final WorkLogDataService workLogDataService;
|
private final WorkLogDataService workLogDataService;
|
||||||
|
private final UserDataService userDataService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogVO createLog(LogCreateDTO dto) {
|
public LogVO createLog(LogCreateDTO dto) {
|
||||||
@ -42,6 +47,7 @@ public class LogServiceImpl implements LogService {
|
|||||||
WorkLog workLog = new WorkLog();
|
WorkLog workLog = new WorkLog();
|
||||||
workLog.setUserId(currentUserId);
|
workLog.setUserId(currentUserId);
|
||||||
workLog.setLogDate(logDate);
|
workLog.setLogDate(logDate);
|
||||||
|
workLog.setTitle(dto.getTitle());
|
||||||
workLog.setRecordTime(LocalDateTime.now());
|
workLog.setRecordTime(LocalDateTime.now());
|
||||||
workLog.setContent(dto.getContent());
|
workLog.setContent(dto.getContent());
|
||||||
workLog.setTemplateId(dto.getTemplateId());
|
workLog.setTemplateId(dto.getTemplateId());
|
||||||
@ -77,6 +83,9 @@ public class LogServiceImpl implements LogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新日志
|
// 更新日志
|
||||||
|
if (dto.getTitle() != null) {
|
||||||
|
workLog.setTitle(dto.getTitle());
|
||||||
|
}
|
||||||
if (dto.getContent() != null) {
|
if (dto.getContent() != null) {
|
||||||
workLog.setContent(dto.getContent());
|
workLog.setContent(dto.getContent());
|
||||||
}
|
}
|
||||||
@ -107,7 +116,13 @@ public class LogServiceImpl implements LogService {
|
|||||||
throw new BusinessException("无权删除他人的日志");
|
throw new BusinessException("无权删除他人的日志");
|
||||||
}
|
}
|
||||||
|
|
||||||
workLogDataService.removeById(id);
|
// 使用 UpdateWrapper 执行逻辑删除,同时设置 updated_by
|
||||||
|
LambdaUpdateWrapper<WorkLog> updateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
updateWrapper.eq(WorkLog::getId, id)
|
||||||
|
.set(WorkLog::getUpdatedBy, currentUserId)
|
||||||
|
.set(WorkLog::getUpdatedTime, LocalDateTime.now())
|
||||||
|
.set(WorkLog::getDeleted, 1);
|
||||||
|
workLogDataService.update(updateWrapper);
|
||||||
|
|
||||||
log.info("删除工作日志成功:ID={}", id);
|
log.info("删除工作日志成功:ID={}", id);
|
||||||
}
|
}
|
||||||
@ -167,6 +182,31 @@ public class LogServiceImpl implements LogService {
|
|||||||
private LogVO convertToVO(WorkLog workLog) {
|
private LogVO convertToVO(WorkLog workLog) {
|
||||||
LogVO vo = new LogVO();
|
LogVO vo = new LogVO();
|
||||||
BeanUtils.copyProperties(workLog, vo);
|
BeanUtils.copyProperties(workLog, vo);
|
||||||
|
|
||||||
|
// 填充用户姓名
|
||||||
|
if (workLog.getUserId() != null) {
|
||||||
|
User user = userDataService.getById(workLog.getUserId());
|
||||||
|
if (user != null) {
|
||||||
|
vo.setUserName(user.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<LocalDate> getLogDatesByMonth(int year, int month) {
|
||||||
|
String currentUserId = UserContext.getUserId();
|
||||||
|
return workLogDataService.getLogDatesByMonth(currentUserId, year, month);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogVO getLogByDate(LocalDate date) {
|
||||||
|
String currentUserId = UserContext.getUserId();
|
||||||
|
WorkLog workLog = workLogDataService.getByUserIdAndLogDate(currentUserId, date);
|
||||||
|
if (workLog == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return convertToVO(workLog);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,12 +28,24 @@ public class LogVO implements Serializable {
|
|||||||
@Schema(description = "用户ID")
|
@Schema(description = "用户ID")
|
||||||
private String userId;
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户姓名
|
||||||
|
*/
|
||||||
|
@Schema(description = "用户姓名")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志日期
|
* 日志日期
|
||||||
*/
|
*/
|
||||||
@Schema(description = "日志日期")
|
@Schema(description = "日志日期")
|
||||||
private LocalDate logDate;
|
private LocalDate logDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志标题
|
||||||
|
*/
|
||||||
|
@Schema(description = "日志标题")
|
||||||
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志内容(Markdown格式)
|
* 日志内容(Markdown格式)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -43,6 +43,7 @@ class LogServiceTest {
|
|||||||
testLog.setId("log-id-123");
|
testLog.setId("log-id-123");
|
||||||
testLog.setUserId("user-id-123");
|
testLog.setUserId("user-id-123");
|
||||||
testLog.setLogDate(LocalDate.now());
|
testLog.setLogDate(LocalDate.now());
|
||||||
|
testLog.setTitle("测试日志标题");
|
||||||
testLog.setContent("今天完成了xxx任务");
|
testLog.setContent("今天完成了xxx任务");
|
||||||
testLog.setDeleted(0);
|
testLog.setDeleted(0);
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ class LogServiceTest {
|
|||||||
void createLog_success() {
|
void createLog_success() {
|
||||||
// Given
|
// Given
|
||||||
LogCreateDTO dto = new LogCreateDTO();
|
LogCreateDTO dto = new LogCreateDTO();
|
||||||
|
dto.setTitle("新日志标题");
|
||||||
dto.setContent("日志内容");
|
dto.setContent("日志内容");
|
||||||
dto.setLogDate(LocalDate.now());
|
dto.setLogDate(LocalDate.now());
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ class LogServiceTest {
|
|||||||
|
|
||||||
// Then
|
// Then
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
|
assertEquals("新日志标题", result.getTitle());
|
||||||
assertEquals("日志内容", result.getContent());
|
assertEquals("日志内容", result.getContent());
|
||||||
verify(workLogDataService).save(any(WorkLog.class));
|
verify(workLogDataService).save(any(WorkLog.class));
|
||||||
}
|
}
|
||||||
@ -76,6 +79,7 @@ class LogServiceTest {
|
|||||||
void createLog_alreadyExists() {
|
void createLog_alreadyExists() {
|
||||||
// Given
|
// Given
|
||||||
LogCreateDTO dto = new LogCreateDTO();
|
LogCreateDTO dto = new LogCreateDTO();
|
||||||
|
dto.setTitle("新日志标题");
|
||||||
dto.setContent("日志内容");
|
dto.setContent("日志内容");
|
||||||
dto.setLogDate(LocalDate.now());
|
dto.setLogDate(LocalDate.now());
|
||||||
|
|
||||||
@ -91,6 +95,7 @@ class LogServiceTest {
|
|||||||
void updateLog_success_ownLog() {
|
void updateLog_success_ownLog() {
|
||||||
// Given
|
// Given
|
||||||
LogUpdateDTO dto = new LogUpdateDTO();
|
LogUpdateDTO dto = new LogUpdateDTO();
|
||||||
|
dto.setTitle("更新后的标题");
|
||||||
dto.setContent("更新后的内容");
|
dto.setContent("更新后的内容");
|
||||||
|
|
||||||
when(workLogDataService.getById("log-id-123")).thenReturn(testLog);
|
when(workLogDataService.getById("log-id-123")).thenReturn(testLog);
|
||||||
@ -101,6 +106,7 @@ class LogServiceTest {
|
|||||||
|
|
||||||
// Then
|
// Then
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
|
assertEquals("更新后的标题", result.getTitle());
|
||||||
assertEquals("更新后的内容", result.getContent());
|
assertEquals("更新后的内容", result.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +120,7 @@ class LogServiceTest {
|
|||||||
otherUserLog.setLogDate(LocalDate.now());
|
otherUserLog.setLogDate(LocalDate.now());
|
||||||
|
|
||||||
LogUpdateDTO dto = new LogUpdateDTO();
|
LogUpdateDTO dto = new LogUpdateDTO();
|
||||||
|
dto.setTitle("更新后的标题");
|
||||||
dto.setContent("更新后的内容");
|
dto.setContent("更新后的内容");
|
||||||
|
|
||||||
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
|
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
|
||||||
@ -136,6 +143,7 @@ class LogServiceTest {
|
|||||||
otherUserLog.setLogDate(LocalDate.now());
|
otherUserLog.setLogDate(LocalDate.now());
|
||||||
|
|
||||||
LogUpdateDTO dto = new LogUpdateDTO();
|
LogUpdateDTO dto = new LogUpdateDTO();
|
||||||
|
dto.setTitle("管理员更新标题");
|
||||||
dto.setContent("管理员更新内容");
|
dto.setContent("管理员更新内容");
|
||||||
|
|
||||||
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
|
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
|
||||||
@ -146,6 +154,7 @@ class LogServiceTest {
|
|||||||
|
|
||||||
// Then
|
// Then
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
|
assertEquals("管理员更新标题", result.getTitle());
|
||||||
assertEquals("管理员更新内容", result.getContent());
|
assertEquals("管理员更新内容", result.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +198,7 @@ class LogServiceTest {
|
|||||||
|
|
||||||
// Then
|
// Then
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
|
assertEquals("测试日志标题", result.getTitle());
|
||||||
assertEquals("今天完成了xxx任务", result.getContent());
|
assertEquals("今天完成了xxx任务", result.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
worklog-mobile/public/logo.svg
Normal file
6
worklog-mobile/public/logo.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||||
|
<rect x="8" y="6" width="32" height="36" rx="4" fill="white"/>
|
||||||
|
<path d="M14 16h20M14 24h16M14 32h12" stroke="#667eea" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="34" cy="32" r="6" fill="#764ba2"/>
|
||||||
|
<path d="M32 32l2 2 4-4" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 405 B |
@ -5,6 +5,7 @@ import { request } from '@/utils/request'
|
|||||||
export interface Log {
|
export interface Log {
|
||||||
id: string
|
id: string
|
||||||
userId: string
|
userId: string
|
||||||
|
userName: string
|
||||||
logDate: string
|
logDate: string
|
||||||
title: string
|
title: string
|
||||||
content: string
|
content: string
|
||||||
@ -67,3 +68,13 @@ export function updateLog(id: string, data: UpdateLogParams): Promise<Log> {
|
|||||||
export function deleteLog(id: string): Promise<void> {
|
export function deleteLog(id: string): Promise<void> {
|
||||||
return request.delete(`/log/${id}`)
|
return request.delete(`/log/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取日历数据(有日志的日期列表)
|
||||||
|
export function getCalendarData(year: number, month: number): Promise<string[]> {
|
||||||
|
return request.get('/log/calendar', { params: { year, month } })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定日期的日志
|
||||||
|
export function getLogByDate(date: string): Promise<Log | null> {
|
||||||
|
return request.get('/log/by-date', { params: { date } })
|
||||||
|
}
|
||||||
|
|||||||
@ -6,11 +6,12 @@ export interface Template {
|
|||||||
id: string
|
id: string
|
||||||
templateName: string
|
templateName: string
|
||||||
content: string
|
content: string
|
||||||
|
instruction?: string
|
||||||
status: number
|
status: number
|
||||||
createdTime: string
|
createdTime: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取启用的模板列表
|
// 获取启用的模板列表
|
||||||
export function listEnabledTemplates(): Promise<Template[]> {
|
export function listEnabledTemplates(): Promise<Template[]> {
|
||||||
return request.get('/template/enabled')
|
return request.get('/template/list')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/home/index.vue'),
|
component: () => import('@/views/home/index.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/mine',
|
||||||
|
name: 'Mine',
|
||||||
|
component: () => import('@/views/mine/index.vue'),
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/log',
|
path: '/log',
|
||||||
name: 'LogList',
|
name: 'LogList',
|
||||||
@ -42,7 +48,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory('/wlmobile/'),
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -3,78 +3,461 @@
|
|||||||
<van-nav-bar title="工作日志" />
|
<van-nav-bar title="工作日志" />
|
||||||
|
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<!-- 用户信息 -->
|
<!-- 日历 -->
|
||||||
<van-cell-group inset class="user-card">
|
<van-cell-group inset class="calendar-card">
|
||||||
<van-cell :title="userStore.userInfo?.name" :label="userStore.userInfo?.username">
|
<!-- 月份切换 -->
|
||||||
<template #icon>
|
<div class="calendar-header">
|
||||||
<van-icon name="user-o" size="24" style="margin-right: 8px" />
|
<van-icon name="arrow-left" @click="prevMonth" class="nav-icon" />
|
||||||
</template>
|
<span class="month-title">{{ currentYear }}年{{ currentMonth }}月</span>
|
||||||
</van-cell>
|
<van-icon name="arrow" @click="nextMonth" class="nav-icon" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-calendar
|
||||||
|
:show-title="false"
|
||||||
|
:show-subtitle="false"
|
||||||
|
:poppable="false"
|
||||||
|
:show-confirm="false"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:formatter="calendarFormatter"
|
||||||
|
:default-date="defaultDate"
|
||||||
|
@select="onDateSelect"
|
||||||
|
/>
|
||||||
|
<div class="calendar-legend">
|
||||||
|
<span class="legend-item"><span class="dot blue"></span>已记录</span>
|
||||||
|
<span class="legend-item"><span class="dot red"></span>未记录</span>
|
||||||
|
</div>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<!-- 快捷入口 -->
|
<!-- 快捷入口 -->
|
||||||
<van-cell-group inset title="快捷入口" class="menu-group">
|
<van-cell-group inset class="quick-actions">
|
||||||
<van-cell title="我的日志" is-link to="/log">
|
<van-grid :column-num="2" :border="false">
|
||||||
<template #icon>
|
<van-grid-item icon="edit" text="写日志" to="/log/create" />
|
||||||
<van-icon name="notes-o" size="20" style="margin-right: 8px" />
|
<van-grid-item icon="notes-o" text="查日志" to="/log" />
|
||||||
</template>
|
</van-grid>
|
||||||
</van-cell>
|
</van-cell-group>
|
||||||
<van-cell title="新建日志" is-link to="/log/create">
|
</div>
|
||||||
<template #icon>
|
|
||||||
<van-icon name="edit" size="20" style="margin-right: 8px" />
|
<!-- 日志详情/新建弹窗 -->
|
||||||
</template>
|
<van-popup v-model:show="showLogPopup" position="bottom" round :style="isCreateMode ? 'height: 80%;' : 'height: 60%;'">
|
||||||
</van-cell>
|
<div class="log-popup-content">
|
||||||
|
<div class="popup-header">
|
||||||
|
<span class="popup-title">{{ selectedDateStr }}</span>
|
||||||
|
<van-icon name="cross" @click="showLogPopup = false" class="close-icon" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查看已有日志 -->
|
||||||
|
<template v-if="!isCreateMode && selectedLog">
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-cell title="标题" :value="selectedLog.title" />
|
||||||
|
<van-cell title="操作人" :value="selectedLog.userName" />
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<!-- 退出登录 -->
|
<van-cell-group inset title="内容">
|
||||||
<div class="logout-btn">
|
<div class="content-box">
|
||||||
<van-button round block type="danger" @click="handleLogout">退出登录</van-button>
|
<pre>{{ selectedLog.content || '暂无内容' }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="popup-actions">
|
||||||
|
<van-button type="primary" block @click="goEdit">编辑日志</van-button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 新建日志表单 -->
|
||||||
|
<template v-if="isCreateMode">
|
||||||
|
<van-form @submit="handleCreateLog">
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-field
|
||||||
|
v-model="createForm.title"
|
||||||
|
name="title"
|
||||||
|
label="标题"
|
||||||
|
placeholder="请输入标题"
|
||||||
|
:rules="[{ required: true, message: '请输入标题' }]"
|
||||||
|
/>
|
||||||
|
<van-field
|
||||||
|
v-model="createForm.templateId"
|
||||||
|
is-link
|
||||||
|
readonly
|
||||||
|
name="templateId"
|
||||||
|
label="模板"
|
||||||
|
placeholder="选择模板(可选)"
|
||||||
|
@click="showTemplatePicker = true"
|
||||||
|
/>
|
||||||
|
<van-field
|
||||||
|
v-model="createForm.content"
|
||||||
|
name="content"
|
||||||
|
label="内容"
|
||||||
|
type="textarea"
|
||||||
|
rows="6"
|
||||||
|
autosize
|
||||||
|
placeholder="请输入日志内容(支持Markdown)"
|
||||||
|
/>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="popup-actions">
|
||||||
|
<van-button type="primary" block native-type="submit" :loading="createLoading">
|
||||||
|
保存
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
|
||||||
|
<!-- 模板选择器 -->
|
||||||
|
<van-popup v-model:show="showTemplatePicker" position="bottom" round>
|
||||||
|
<van-picker
|
||||||
|
:columns="templateColumns"
|
||||||
|
title="选择模板"
|
||||||
|
@confirm="onTemplateConfirm"
|
||||||
|
@cancel="showTemplatePicker = false"
|
||||||
|
/>
|
||||||
|
</van-popup>
|
||||||
|
|
||||||
<!-- 底部导航 -->
|
<!-- 底部导航 -->
|
||||||
<van-tabbar v-model="active" route>
|
<van-tabbar v-model="active" route>
|
||||||
<van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
|
<van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
|
||||||
<van-tabbar-item icon="notes-o" to="/log">日志</van-tabbar-item>
|
<van-tabbar-item icon="user-o" to="/mine">我的</van-tabbar-item>
|
||||||
</van-tabbar>
|
</van-tabbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showSuccessToast, showConfirmDialog } from 'vant'
|
import { showSuccessToast } from 'vant'
|
||||||
import { useUserStore } from '@/store/user'
|
import { getCalendarData, getLogByDate, createLog } from '@/api/log'
|
||||||
|
import { listEnabledTemplates } from '@/api/template'
|
||||||
|
import type { Log } from '@/api/log'
|
||||||
|
import type { Template } from '@/api/template'
|
||||||
|
import type { CalendarDayItem } from 'vant'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
|
||||||
const active = ref(0)
|
const active = ref(0)
|
||||||
|
|
||||||
async function handleLogout() {
|
const today = new Date()
|
||||||
|
const currentYear = ref(today.getFullYear())
|
||||||
|
const currentMonth = ref(today.getMonth() + 1)
|
||||||
|
|
||||||
|
const minDate = computed(() => new Date(currentYear.value, currentMonth.value - 1, 1))
|
||||||
|
const maxDate = computed(() => new Date(currentYear.value, currentMonth.value, 0))
|
||||||
|
const defaultDate = ref(today)
|
||||||
|
|
||||||
|
const logDates = ref<Set<string>>(new Set())
|
||||||
|
const showLogPopup = ref(false)
|
||||||
|
const selectedLog = ref<Log | null>(null)
|
||||||
|
const selectedDateStr = ref('')
|
||||||
|
const isCreateMode = ref(false)
|
||||||
|
|
||||||
|
// 新建日志表单
|
||||||
|
const createLoading = ref(false)
|
||||||
|
const createForm = reactive({
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
templateId: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模板相关
|
||||||
|
const showTemplatePicker = ref(false)
|
||||||
|
const templates = ref<Template[]>([])
|
||||||
|
const templateColumns = ref<{ text: string; value: string }[]>([])
|
||||||
|
|
||||||
|
// 加载日历数据
|
||||||
|
async function loadCalendarData() {
|
||||||
try {
|
try {
|
||||||
await showConfirmDialog({
|
const dates = await getCalendarData(currentYear.value, currentMonth.value)
|
||||||
title: '提示',
|
logDates.value = new Set(dates)
|
||||||
message: '确定要退出登录吗?'
|
|
||||||
})
|
|
||||||
await userStore.logout()
|
|
||||||
showSuccessToast('已退出登录')
|
|
||||||
router.push('/login')
|
|
||||||
} catch {
|
} catch {
|
||||||
// 取消操作
|
// 忽略错误
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载模板列表
|
||||||
|
async function loadTemplates() {
|
||||||
|
try {
|
||||||
|
templates.value = await listEnabledTemplates()
|
||||||
|
templateColumns.value = templates.value.map(t => ({
|
||||||
|
text: t.templateName,
|
||||||
|
value: t.id
|
||||||
|
}))
|
||||||
|
} catch {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日历日期格式化
|
||||||
|
function calendarFormatter(day: CalendarDayItem): CalendarDayItem {
|
||||||
|
if (!day.date) return day
|
||||||
|
|
||||||
|
const dateStr = formatDate(day.date)
|
||||||
|
const isCurrentMonth = day.date.getMonth() === currentMonth.value - 1
|
||||||
|
|
||||||
|
if (isCurrentMonth) {
|
||||||
|
const hasLog = logDates.value.has(dateStr)
|
||||||
|
if (hasLog) {
|
||||||
|
day.className = 'has-log'
|
||||||
|
day.bottomInfo = '✓'
|
||||||
|
} else {
|
||||||
|
day.className = 'no-log'
|
||||||
|
day.bottomInfo = '○'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return day
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
function formatDate(date: Date): string {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||||
|
const day = date.getDate().toString().padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日期选择 - 统一使用弹窗
|
||||||
|
async function onDateSelect(date: Date) {
|
||||||
|
const dateStr = formatDate(date)
|
||||||
|
selectedDateStr.value = dateStr
|
||||||
|
selectedLog.value = null
|
||||||
|
|
||||||
|
const hasLog = logDates.value.has(dateStr)
|
||||||
|
|
||||||
|
if (hasLog) {
|
||||||
|
// 有日志,加载详情
|
||||||
|
isCreateMode.value = false
|
||||||
|
try {
|
||||||
|
const log = await getLogByDate(dateStr)
|
||||||
|
selectedLog.value = log
|
||||||
|
} catch {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 无日志,进入新建模式
|
||||||
|
isCreateMode.value = true
|
||||||
|
resetCreateForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统一显示弹窗
|
||||||
|
showLogPopup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置新建表单
|
||||||
|
function resetCreateForm() {
|
||||||
|
createForm.title = ''
|
||||||
|
createForm.content = ''
|
||||||
|
createForm.templateId = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模板选择确认
|
||||||
|
function onTemplateConfirm({ selectedValues }: { selectedValues: string[] }) {
|
||||||
|
const templateId = selectedValues[0]
|
||||||
|
createForm.templateId = templateId
|
||||||
|
|
||||||
|
// 填充模板内容
|
||||||
|
const template = templates.value.find(t => t.id === templateId)
|
||||||
|
if (template && template.content) {
|
||||||
|
createForm.content = template.content
|
||||||
|
}
|
||||||
|
|
||||||
|
showTemplatePicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建日志
|
||||||
|
async function handleCreateLog() {
|
||||||
|
createLoading.value = true
|
||||||
|
try {
|
||||||
|
await createLog({
|
||||||
|
logDate: selectedDateStr.value,
|
||||||
|
title: createForm.title,
|
||||||
|
content: createForm.content,
|
||||||
|
templateId: createForm.templateId || undefined
|
||||||
|
})
|
||||||
|
showSuccessToast('创建成功')
|
||||||
|
showLogPopup.value = false
|
||||||
|
// 刷新日历数据
|
||||||
|
await loadCalendarData()
|
||||||
|
} finally {
|
||||||
|
createLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上一个月
|
||||||
|
function prevMonth() {
|
||||||
|
if (currentMonth.value === 1) {
|
||||||
|
currentYear.value--
|
||||||
|
currentMonth.value = 12
|
||||||
|
} else {
|
||||||
|
currentMonth.value--
|
||||||
|
}
|
||||||
|
loadCalendarData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一个月
|
||||||
|
function nextMonth() {
|
||||||
|
if (currentMonth.value === 12) {
|
||||||
|
currentYear.value++
|
||||||
|
currentMonth.value = 1
|
||||||
|
} else {
|
||||||
|
currentMonth.value++
|
||||||
|
}
|
||||||
|
loadCalendarData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑日志
|
||||||
|
function goEdit() {
|
||||||
|
if (selectedLog.value) {
|
||||||
|
showLogPopup.value = false
|
||||||
|
router.push(`/log/edit/${selectedLog.value.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadCalendarData()
|
||||||
|
loadTemplates()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.user-card {
|
.home-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
padding-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-card {
|
||||||
|
margin: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #ebedf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1989fa;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-legend {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid #ebedf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #969799;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.blue {
|
||||||
|
background: #1989fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.red {
|
||||||
|
background: #ee0a24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-group {
|
.log-popup-content {
|
||||||
margin: 16px;
|
padding: 16px;
|
||||||
|
padding-bottom: 60px;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn {
|
.popup-header {
|
||||||
margin: 32px 16px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 0 16px;
|
||||||
|
border-bottom: 1px solid #ebedf0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #969799;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-box {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-box pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-actions {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 日历样式覆盖 - 隐藏自带标题 */
|
||||||
|
.van-calendar__header-title {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-calendar__header-subtitle {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-calendar__day.has-log {
|
||||||
|
color: #1989fa;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-calendar__day.has-log .van-calendar__bottom-info {
|
||||||
|
color: #1989fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-calendar__day.no-log {
|
||||||
|
color: #ee0a24;
|
||||||
|
border: 1px solid #ee0a24 !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-calendar__day.no-log .van-calendar__bottom-info {
|
||||||
|
color: #ee0a24;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -74,13 +74,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { showSuccessToast } from 'vant'
|
import { showSuccessToast } from 'vant'
|
||||||
import { createLog } from '@/api/log'
|
import { createLog } from '@/api/log'
|
||||||
import { listEnabledTemplates } from '@/api/template'
|
import { listEnabledTemplates } from '@/api/template'
|
||||||
import type { Template } from '@/api/template'
|
import type { Template } from '@/api/template'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const showDatePicker = ref(false)
|
const showDatePicker = ref(false)
|
||||||
const showTemplatePicker = ref(false)
|
const showTemplatePicker = ref(false)
|
||||||
@ -96,6 +97,13 @@ const form = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// 检查URL参数中是否有日期
|
||||||
|
const dateParam = route.query.date as string
|
||||||
|
if (dateParam) {
|
||||||
|
form.logDate = dateParam
|
||||||
|
const [year, month, day] = dateParam.split('-')
|
||||||
|
selectedDate.value = [year, month, day]
|
||||||
|
} else {
|
||||||
// 设置默认日期为今天
|
// 设置默认日期为今天
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
const year = today.getFullYear().toString()
|
const year = today.getFullYear().toString()
|
||||||
@ -103,6 +111,7 @@ onMounted(async () => {
|
|||||||
const day = today.getDate().toString().padStart(2, '0')
|
const day = today.getDate().toString().padStart(2, '0')
|
||||||
selectedDate.value = [year, month, day]
|
selectedDate.value = [year, month, day]
|
||||||
form.logDate = `${year}-${month}-${day}`
|
form.logDate = `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
// 加载模板列表
|
// 加载模板列表
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -37,6 +37,7 @@ const route = useRoute()
|
|||||||
const log = ref<Log>({
|
const log = ref<Log>({
|
||||||
id: '',
|
id: '',
|
||||||
userId: '',
|
userId: '',
|
||||||
|
userName: '',
|
||||||
logDate: '',
|
logDate: '',
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: '',
|
||||||
|
|||||||
@ -3,18 +3,6 @@
|
|||||||
<van-nav-bar title="我的日志" />
|
<van-nav-bar title="我的日志" />
|
||||||
|
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<!-- 搜索栏 -->
|
|
||||||
<van-cell-group inset class="search-group">
|
|
||||||
<van-field
|
|
||||||
v-model="dateRange"
|
|
||||||
is-link
|
|
||||||
readonly
|
|
||||||
label="日期范围"
|
|
||||||
placeholder="选择日期范围"
|
|
||||||
@click="showDatePicker = true"
|
|
||||||
/>
|
|
||||||
</van-cell-group>
|
|
||||||
|
|
||||||
<!-- 日志列表 -->
|
<!-- 日志列表 -->
|
||||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||||
<van-list
|
<van-list
|
||||||
@ -23,20 +11,40 @@
|
|||||||
finished-text="没有更多了"
|
finished-text="没有更多了"
|
||||||
@load="onLoad"
|
@load="onLoad"
|
||||||
>
|
>
|
||||||
<van-cell-group inset>
|
<div class="log-list">
|
||||||
<van-cell
|
<div
|
||||||
v-for="item in list"
|
v-for="item in list"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:title="item.title"
|
class="log-card"
|
||||||
:label="item.logDate"
|
|
||||||
is-link
|
|
||||||
@click="goDetail(item.id)"
|
|
||||||
>
|
>
|
||||||
<template #value>
|
<!-- 第一行:标题 -->
|
||||||
|
<div class="log-title">{{ item.title }}</div>
|
||||||
|
|
||||||
|
<!-- 第二行:内容(最多5行) -->
|
||||||
|
<div class="log-content">
|
||||||
|
{{ item.content || '暂无内容' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第三行:操作人和日期 -->
|
||||||
|
<div class="log-footer">
|
||||||
|
<span class="log-user">{{ item.userName || '未知' }}</span>
|
||||||
<span class="log-date">{{ item.logDate }}</span>
|
<span class="log-date">{{ item.logDate }}</span>
|
||||||
</template>
|
</div>
|
||||||
</van-cell>
|
|
||||||
</van-cell-group>
|
<!-- 操作按钮 -->
|
||||||
|
<div class="log-actions">
|
||||||
|
<span class="action-btn" @click="goDetail(item.id)">查看</span>
|
||||||
|
<span class="action-divider">|</span>
|
||||||
|
<span class="action-btn" @click="goEdit(item.id)">编辑</span>
|
||||||
|
<span class="action-divider">|</span>
|
||||||
|
<span class="action-btn action-delete" @click="handleDelete(item)">删除</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="list.length === 0 && !loading" class="empty-tip">
|
||||||
|
暂无日志记录
|
||||||
|
</div>
|
||||||
</van-list>
|
</van-list>
|
||||||
</van-pull-refresh>
|
</van-pull-refresh>
|
||||||
|
|
||||||
@ -44,21 +52,10 @@
|
|||||||
<van-floating-bubble icon="plus" @click="goCreate" />
|
<van-floating-bubble icon="plus" @click="goCreate" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 日期选择器 -->
|
|
||||||
<van-popup v-model:show="showDatePicker" position="bottom" round>
|
|
||||||
<van-date-picker
|
|
||||||
v-model="selectedDate"
|
|
||||||
type="daterange"
|
|
||||||
title="选择日期范围"
|
|
||||||
@confirm="onDateConfirm"
|
|
||||||
@cancel="showDatePicker = false"
|
|
||||||
/>
|
|
||||||
</van-popup>
|
|
||||||
|
|
||||||
<!-- 底部导航 -->
|
<!-- 底部导航 -->
|
||||||
<van-tabbar v-model="active" route>
|
<van-tabbar v-model="active" route>
|
||||||
<van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
|
<van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
|
||||||
<van-tabbar-item icon="notes-o" to="/log">日志</van-tabbar-item>
|
<van-tabbar-item icon="user-o" to="/mine">我的</van-tabbar-item>
|
||||||
</van-tabbar>
|
</van-tabbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -66,17 +63,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { pageMyLogs } from '@/api/log'
|
import { showSuccessToast, showConfirmDialog } from 'vant'
|
||||||
|
import { pageMyLogs, deleteLog } from '@/api/log'
|
||||||
import type { Log } from '@/api/log'
|
import type { Log } from '@/api/log'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const active = ref(1)
|
const active = ref(0)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const refreshing = ref(false)
|
const refreshing = ref(false)
|
||||||
const finished = ref(false)
|
const finished = ref(false)
|
||||||
const showDatePicker = ref(false)
|
|
||||||
const dateRange = ref('')
|
|
||||||
const selectedDate = ref<string[]>([])
|
|
||||||
|
|
||||||
const list = ref<Log[]>([])
|
const list = ref<Log[]>([])
|
||||||
const pageNum = ref(1)
|
const pageNum = ref(1)
|
||||||
@ -118,29 +113,125 @@ async function onRefresh() {
|
|||||||
finished.value = false
|
finished.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDateConfirm() {
|
|
||||||
showDatePicker.value = false
|
|
||||||
// 重新加载数据
|
|
||||||
pageNum.value = 1
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
function goDetail(id: string) {
|
function goDetail(id: string) {
|
||||||
router.push(`/log/detail/${id}`)
|
router.push(`/log/detail/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goEdit(id: string) {
|
||||||
|
router.push(`/log/edit/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
function goCreate() {
|
function goCreate() {
|
||||||
router.push('/log/create')
|
router.push('/log/create')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleDelete(item: Log) {
|
||||||
|
try {
|
||||||
|
await showConfirmDialog({
|
||||||
|
title: '提示',
|
||||||
|
message: `确定要删除日志"${item.title}"吗?`
|
||||||
|
})
|
||||||
|
await deleteLog(item.id)
|
||||||
|
showSuccessToast('删除成功')
|
||||||
|
pageNum.value = 1
|
||||||
|
await loadData()
|
||||||
|
} catch {
|
||||||
|
// 取消操作
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.search-group {
|
.log-list-page {
|
||||||
margin: 16px;
|
min-height: 100vh;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
padding: 16px;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 5;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-user {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-date {
|
.log-date {
|
||||||
color: #969799;
|
font-size: 13px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1989fa;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-delete {
|
||||||
|
color: #ee0a24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-divider {
|
||||||
|
color: #ddd;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #969799;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,34 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-page">
|
<div class="login-page">
|
||||||
<van-nav-bar title="工作日志" />
|
<!-- 背景装饰 -->
|
||||||
|
<div class="bg-decoration">
|
||||||
|
<div class="bg-circle bg-circle-1"></div>
|
||||||
|
<div class="bg-circle bg-circle-2"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-header">
|
||||||
|
<div class="logo-wrapper">
|
||||||
|
<img src="/logo.svg" alt="工作日志" class="logo" />
|
||||||
|
</div>
|
||||||
|
<p class="welcome-text">您好,欢迎使用</p>
|
||||||
|
<h1 class="app-title">工作日志平台~</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="login-content">
|
<div class="login-content">
|
||||||
<van-form @submit="handleLogin">
|
<van-form @submit="handleLogin">
|
||||||
<van-cell-group inset>
|
<div class="form-card">
|
||||||
<van-field
|
<van-field
|
||||||
v-model="form.username"
|
v-model="form.username"
|
||||||
name="username"
|
name="username"
|
||||||
label="用户名"
|
|
||||||
placeholder="请输入用户名"
|
placeholder="请输入用户名"
|
||||||
|
left-icon="user-o"
|
||||||
:rules="[{ required: true, message: '请输入用户名' }]"
|
:rules="[{ required: true, message: '请输入用户名' }]"
|
||||||
/>
|
/>
|
||||||
<van-field
|
<van-field
|
||||||
v-model="form.password"
|
v-model="form.password"
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
label="密码"
|
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
|
left-icon="lock"
|
||||||
:rules="[{ required: true, message: '请输入密码' }]"
|
:rules="[{ required: true, message: '请输入密码' }]"
|
||||||
/>
|
/>
|
||||||
</van-cell-group>
|
</div>
|
||||||
|
|
||||||
<div class="login-btn">
|
<div class="login-btn">
|
||||||
<van-button round block type="primary" native-type="submit" :loading="loading">
|
<van-button round block native-type="submit" :loading="loading">
|
||||||
登录
|
登录
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
</van-form>
|
</van-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="login-footer">
|
||||||
|
<p>登录即代表已阅读并同意<span class="link">《用户协议》</span></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -63,14 +79,135 @@ async function handleLogin() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.login-page {
|
.login-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(180deg, #f0f5ff 0%, #ffffff 50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景装饰 */
|
||||||
|
.bg-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-circle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-circle-1 {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background: #1989fa;
|
||||||
|
top: -50px;
|
||||||
|
right: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-circle-2 {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
background: #36cfc9;
|
||||||
|
top: 80px;
|
||||||
|
right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
padding: 60px 24px 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-wrapper {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
background: linear-gradient(135deg, #1989fa 0%, #36cfc9 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 8px 20px rgba(25, 137, 250, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-content {
|
.login-content {
|
||||||
padding: 60px 16px;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 20px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card .van-field {
|
||||||
|
padding: 18px 16px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card .van-field::after {
|
||||||
|
border-color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card .van-field :deep(.van-field__control) {
|
||||||
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-btn {
|
.login-btn {
|
||||||
margin: 24px 16px;
|
margin: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn .van-button {
|
||||||
|
background: linear-gradient(135deg, #1989fa 0%, #36cfc9 100%);
|
||||||
|
border: none;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer {
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer p {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer .link {
|
||||||
|
color: #1989fa;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
87
worklog-mobile/src/views/mine/index.vue
Normal file
87
worklog-mobile/src/views/mine/index.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mine-page">
|
||||||
|
<van-nav-bar title="我的" />
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<!-- 用户信息 -->
|
||||||
|
<van-cell-group inset class="user-card">
|
||||||
|
<van-cell :title="userStore.userInfo?.name" :label="userStore.userInfo?.username">
|
||||||
|
<template #icon>
|
||||||
|
<van-icon name="user-o" size="24" style="margin-right: 8px" />
|
||||||
|
</template>
|
||||||
|
<template #value>
|
||||||
|
<van-tag type="primary">{{ userStore.userInfo?.role === 'ADMIN' ? '管理员' : '普通用户' }}</van-tag>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<!-- 功能列表 -->
|
||||||
|
<van-cell-group inset title="功能" class="menu-group">
|
||||||
|
<van-cell title="我的日志" is-link to="/log">
|
||||||
|
<template #icon>
|
||||||
|
<van-icon name="notes-o" size="20" style="margin-right: 8px" />
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<!-- 退出登录 -->
|
||||||
|
<div class="logout-btn">
|
||||||
|
<van-button round block type="danger" @click="handleLogout">退出登录</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部导航 -->
|
||||||
|
<van-tabbar v-model="active" route>
|
||||||
|
<van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
|
||||||
|
<van-tabbar-item icon="user-o" to="/mine">我的</van-tabbar-item>
|
||||||
|
</van-tabbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { showSuccessToast, showConfirmDialog } from 'vant'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const active = ref(1)
|
||||||
|
|
||||||
|
async function handleLogout() {
|
||||||
|
try {
|
||||||
|
await showConfirmDialog({
|
||||||
|
title: '提示',
|
||||||
|
message: '确定要退出登录吗?'
|
||||||
|
})
|
||||||
|
await userStore.logout()
|
||||||
|
showSuccessToast('已退出登录')
|
||||||
|
router.push('/login')
|
||||||
|
} catch {
|
||||||
|
// 取消操作
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mine-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
padding-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-group {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
margin: 32px 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -38,7 +38,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory('/wladmin/'),
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user