Compare commits
5 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
561a373e40 | 2 months ago |
|
|
b0eda7277d | 2 months ago |
|
|
03d33118ce | 2 months ago |
|
|
1dd39bbda2 | 2 months ago |
|
|
48fb8045a0 | 2 months ago |
26 changed files with 2779 additions and 262 deletions
@ -0,0 +1,81 @@ |
|||||
|
package com.ruoyi.web.controller.system; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||
|
import org.springframework.web.bind.annotation.DeleteMapping; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.PathVariable; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.PutMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
import com.ruoyi.common.annotation.Log; |
||||
|
import com.ruoyi.common.core.controller.BaseController; |
||||
|
import com.ruoyi.common.core.domain.AjaxResult; |
||||
|
import com.ruoyi.common.core.domain.entity.SysTimelineSegment; |
||||
|
import com.ruoyi.common.enums.BusinessType; |
||||
|
import com.ruoyi.system.service.ISysTimelineSegmentService; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("/system/timeline") |
||||
|
public class SysTimelineSegmentController extends BaseController |
||||
|
{ |
||||
|
@Autowired |
||||
|
private ISysTimelineSegmentService segmentService; |
||||
|
|
||||
|
@PreAuthorize("@ss.hasPermi('system:timeline:list')") |
||||
|
@GetMapping("/list") |
||||
|
public AjaxResult list(SysTimelineSegment segment) |
||||
|
{ |
||||
|
List<SysTimelineSegment> list = segmentService.selectSegmentList(segment); |
||||
|
return success(list); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/listByRoomId/{roomId}") |
||||
|
public AjaxResult listByRoomId(@PathVariable Long roomId) |
||||
|
{ |
||||
|
List<SysTimelineSegment> list = segmentService.selectSegmentListByRoomId(roomId); |
||||
|
return success(list); |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("@ss.hasPermi('system:timeline:query')") |
||||
|
@GetMapping(value = "/{segmentId}") |
||||
|
public AjaxResult getInfo(@PathVariable Long segmentId) |
||||
|
{ |
||||
|
return success(segmentService.selectSegmentById(segmentId)); |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("@ss.hasPermi('system:timeline:add')") |
||||
|
@Log(title = "时间轴管理", businessType = BusinessType.INSERT) |
||||
|
@PostMapping |
||||
|
public AjaxResult add(@RequestBody SysTimelineSegment segment) |
||||
|
{ |
||||
|
return toAjax(segmentService.insertSegment(segment)); |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("@ss.hasPermi('system:timeline:edit')") |
||||
|
@Log(title = "时间轴管理", businessType = BusinessType.UPDATE) |
||||
|
@PutMapping |
||||
|
public AjaxResult edit(@RequestBody SysTimelineSegment segment) |
||||
|
{ |
||||
|
return toAjax(segmentService.updateSegment(segment)); |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("@ss.hasPermi('system:timeline:remove')") |
||||
|
@Log(title = "时间轴管理", businessType = BusinessType.DELETE) |
||||
|
@DeleteMapping("/{segmentId}") |
||||
|
public AjaxResult remove(@PathVariable Long segmentId) |
||||
|
{ |
||||
|
return toAjax(segmentService.deleteSegmentById(segmentId)); |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("@ss.hasPermi('system:timeline:remove')") |
||||
|
@Log(title = "时间轴管理", businessType = BusinessType.DELETE) |
||||
|
@DeleteMapping("/room/{roomId}") |
||||
|
public AjaxResult removeByRoomId(@PathVariable Long roomId) |
||||
|
{ |
||||
|
return toAjax(segmentService.deleteSegmentByRoomId(roomId)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
package com.ruoyi.common.core.domain.entity; |
||||
|
|
||||
|
import javax.validation.constraints.NotBlank; |
||||
|
import javax.validation.constraints.NotNull; |
||||
|
import javax.validation.constraints.Size; |
||||
|
import org.apache.commons.lang3.builder.ToStringBuilder; |
||||
|
import org.apache.commons.lang3.builder.ToStringStyle; |
||||
|
|
||||
|
public class SysTimelineSegment |
||||
|
{ |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
private Long segmentId; |
||||
|
|
||||
|
private Long roomId; |
||||
|
|
||||
|
@NotBlank(message = "时间点不能为空") |
||||
|
@Size(min = 0, max = 8, message = "时间点长度不能超过8个字符") |
||||
|
private String segmentTime; |
||||
|
|
||||
|
@NotBlank(message = "时间段名称不能为空") |
||||
|
@Size(min = 0, max = 50, message = "时间段名称长度不能超过50个字符") |
||||
|
private String segmentName; |
||||
|
|
||||
|
@Size(max = 500, message = "时间段描述长度不能超过500个字符") |
||||
|
private String segmentDesc; |
||||
|
|
||||
|
public Long getSegmentId() |
||||
|
{ |
||||
|
return segmentId; |
||||
|
} |
||||
|
|
||||
|
public void setSegmentId(Long segmentId) |
||||
|
{ |
||||
|
this.segmentId = segmentId; |
||||
|
} |
||||
|
|
||||
|
@NotNull(message = "房间ID不能为空") |
||||
|
public Long getRoomId() |
||||
|
{ |
||||
|
return roomId; |
||||
|
} |
||||
|
|
||||
|
public void setRoomId(Long roomId) |
||||
|
{ |
||||
|
this.roomId = roomId; |
||||
|
} |
||||
|
|
||||
|
public String getSegmentTime() |
||||
|
{ |
||||
|
return segmentTime; |
||||
|
} |
||||
|
|
||||
|
public void setSegmentTime(String segmentTime) |
||||
|
{ |
||||
|
this.segmentTime = segmentTime; |
||||
|
} |
||||
|
|
||||
|
public String getSegmentName() |
||||
|
{ |
||||
|
return segmentName; |
||||
|
} |
||||
|
|
||||
|
public void setSegmentName(String segmentName) |
||||
|
{ |
||||
|
this.segmentName = segmentName; |
||||
|
} |
||||
|
|
||||
|
public String getSegmentDesc() |
||||
|
{ |
||||
|
return segmentDesc; |
||||
|
} |
||||
|
|
||||
|
public void setSegmentDesc(String segmentDesc) |
||||
|
{ |
||||
|
this.segmentDesc = segmentDesc; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) |
||||
|
.append("segmentId", getSegmentId()) |
||||
|
.append("roomId", getRoomId()) |
||||
|
.append("segmentTime", getSegmentTime()) |
||||
|
.append("segmentName", getSegmentName()) |
||||
|
.append("segmentDesc", getSegmentDesc()) |
||||
|
.toString(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
package com.ruoyi.system.mapper; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import org.apache.ibatis.annotations.Param; |
||||
|
import com.ruoyi.common.core.domain.entity.SysTimelineSegment; |
||||
|
|
||||
|
public interface SysTimelineSegmentMapper |
||||
|
{ |
||||
|
public List<SysTimelineSegment> selectSegmentList(SysTimelineSegment segment); |
||||
|
|
||||
|
public List<SysTimelineSegment> selectSegmentListByRoomId(@Param("roomId") Long roomId); |
||||
|
|
||||
|
public SysTimelineSegment selectSegmentById(Long segmentId); |
||||
|
|
||||
|
public int insertSegment(SysTimelineSegment segment); |
||||
|
|
||||
|
public int updateSegment(SysTimelineSegment segment); |
||||
|
|
||||
|
public int deleteSegmentById(Long segmentId); |
||||
|
|
||||
|
public int deleteSegmentByRoomId(@Param("roomId") Long roomId); |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
package com.ruoyi.system.service; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import com.ruoyi.common.core.domain.entity.SysTimelineSegment; |
||||
|
|
||||
|
public interface ISysTimelineSegmentService |
||||
|
{ |
||||
|
public List<SysTimelineSegment> selectSegmentList(SysTimelineSegment segment); |
||||
|
|
||||
|
public List<SysTimelineSegment> selectSegmentListByRoomId(Long roomId); |
||||
|
|
||||
|
public SysTimelineSegment selectSegmentById(Long segmentId); |
||||
|
|
||||
|
public int insertSegment(SysTimelineSegment segment); |
||||
|
|
||||
|
public int updateSegment(SysTimelineSegment segment); |
||||
|
|
||||
|
public int deleteSegmentById(Long segmentId); |
||||
|
|
||||
|
public int deleteSegmentByRoomId(Long roomId); |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
package com.ruoyi.system.service.impl; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import com.ruoyi.common.core.domain.entity.SysTimelineSegment; |
||||
|
import com.ruoyi.system.mapper.SysTimelineSegmentMapper; |
||||
|
import com.ruoyi.system.service.ISysTimelineSegmentService; |
||||
|
|
||||
|
@Service |
||||
|
public class SysTimelineSegmentServiceImpl implements ISysTimelineSegmentService |
||||
|
{ |
||||
|
@Autowired |
||||
|
private SysTimelineSegmentMapper segmentMapper; |
||||
|
|
||||
|
@Override |
||||
|
public List<SysTimelineSegment> selectSegmentList(SysTimelineSegment segment) |
||||
|
{ |
||||
|
return segmentMapper.selectSegmentList(segment); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<SysTimelineSegment> selectSegmentListByRoomId(Long roomId) |
||||
|
{ |
||||
|
return segmentMapper.selectSegmentListByRoomId(roomId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SysTimelineSegment selectSegmentById(Long segmentId) |
||||
|
{ |
||||
|
return segmentMapper.selectSegmentById(segmentId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int insertSegment(SysTimelineSegment segment) |
||||
|
{ |
||||
|
return segmentMapper.insertSegment(segment); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int updateSegment(SysTimelineSegment segment) |
||||
|
{ |
||||
|
return segmentMapper.updateSegment(segment); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int deleteSegmentById(Long segmentId) |
||||
|
{ |
||||
|
return segmentMapper.deleteSegmentById(segmentId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int deleteSegmentByRoomId(Long roomId) |
||||
|
{ |
||||
|
return segmentMapper.deleteSegmentByRoomId(roomId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<!DOCTYPE mapper |
||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.ruoyi.system.mapper.SysTimelineSegmentMapper"> |
||||
|
|
||||
|
<resultMap type="SysTimelineSegment" id="SysTimelineSegmentResult"> |
||||
|
<id property="segmentId" column="segment_id" /> |
||||
|
<result property="roomId" column="room_id" /> |
||||
|
<result property="segmentTime" column="segment_time" /> |
||||
|
<result property="segmentName" column="segment_name" /> |
||||
|
<result property="segmentDesc" column="segment_desc" /> |
||||
|
</resultMap> |
||||
|
|
||||
|
<sql id="selectSegmentVo"> |
||||
|
select s.segment_id, s.room_id, s.segment_time, s.segment_name, s.segment_desc |
||||
|
from sys_timeline_segment s |
||||
|
</sql> |
||||
|
|
||||
|
<select id="selectSegmentList" parameterType="SysTimelineSegment" resultMap="SysTimelineSegmentResult"> |
||||
|
<include refid="selectSegmentVo"/> |
||||
|
where 1=1 |
||||
|
<if test="roomId != null and roomId != 0"> |
||||
|
AND room_id = #{roomId} |
||||
|
</if> |
||||
|
<if test="segmentName != null and segmentName != ''"> |
||||
|
AND segment_name like concat('%', #{segmentName}, '%') |
||||
|
</if> |
||||
|
order by s.segment_time |
||||
|
</select> |
||||
|
|
||||
|
<select id="selectSegmentListByRoomId" parameterType="Long" resultMap="SysTimelineSegmentResult"> |
||||
|
<include refid="selectSegmentVo"/> |
||||
|
where room_id = #{roomId} |
||||
|
order by s.segment_time |
||||
|
</select> |
||||
|
|
||||
|
<select id="selectSegmentById" parameterType="Long" resultMap="SysTimelineSegmentResult"> |
||||
|
<include refid="selectSegmentVo"/> |
||||
|
where segment_id = #{segmentId} |
||||
|
</select> |
||||
|
|
||||
|
<insert id="insertSegment" parameterType="SysTimelineSegment"> |
||||
|
insert into sys_timeline_segment( |
||||
|
room_id, |
||||
|
segment_time, |
||||
|
segment_name, |
||||
|
<if test="segmentDesc != null and segmentDesc != ''">segment_desc,</if> |
||||
|
segment_id |
||||
|
)values( |
||||
|
#{roomId}, |
||||
|
#{segmentTime}, |
||||
|
#{segmentName}, |
||||
|
<if test="segmentDesc != null and segmentDesc != ''">#{segmentDesc},</if> |
||||
|
#{segmentId} |
||||
|
) |
||||
|
</insert> |
||||
|
|
||||
|
<update id="updateSegment" parameterType="SysTimelineSegment"> |
||||
|
update sys_timeline_segment |
||||
|
<set> |
||||
|
<if test="segmentTime != null and segmentTime != ''">segment_time = #{segmentTime},</if> |
||||
|
<if test="segmentName != null and segmentName != ''">segment_name = #{segmentName},</if> |
||||
|
<if test="segmentDesc != null">segment_desc = #{segmentDesc},</if> |
||||
|
</set> |
||||
|
where segment_id = #{segmentId} |
||||
|
</update> |
||||
|
|
||||
|
<delete id="deleteSegmentById" parameterType="Long"> |
||||
|
delete from sys_timeline_segment where segment_id = #{segmentId} |
||||
|
</delete> |
||||
|
|
||||
|
<delete id="deleteSegmentByRoomId" parameterType="Long"> |
||||
|
delete from sys_timeline_segment where room_id = #{roomId} |
||||
|
</delete> |
||||
|
|
||||
|
</mapper> |
||||
@ -1,12 +1,12 @@ |
|||||
module.exports = { |
module.exports = { |
||||
presets: [ |
presets: [ |
||||
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
|
|
||||
'@vue/cli-plugin-babel/preset' |
'@vue/cli-plugin-babel/preset' |
||||
], |
], |
||||
|
plugins: [ |
||||
|
'@babel/plugin-proposal-object-rest-spread' |
||||
|
], |
||||
'env': { |
'env': { |
||||
'development': { |
'development': { |
||||
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
|
|
||||
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
|
|
||||
'plugins': ['dynamic-import-node'] |
'plugins': ['dynamic-import-node'] |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,691 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="zh-CN"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>数据卡</title> |
||||
|
<link rel="stylesheet" href="./element-ui.css"> |
||||
|
<style> |
||||
|
* { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
||||
|
background: #fafafa; |
||||
|
} |
||||
|
|
||||
|
#app { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.data-card-container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100vh; |
||||
|
background: #fafafa; |
||||
|
} |
||||
|
|
||||
|
.data-card-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 0 32px; |
||||
|
height: 56px; |
||||
|
background: #fff; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.header-left { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 16px; |
||||
|
} |
||||
|
|
||||
|
.back-btn { |
||||
|
padding: 8px 12px; |
||||
|
font-size: 14px; |
||||
|
color: #666; |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 4px; |
||||
|
} |
||||
|
|
||||
|
.back-btn:hover { |
||||
|
color: #409EFF; |
||||
|
} |
||||
|
|
||||
|
.data-card-title { |
||||
|
font-size: 18px; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.header-right { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
.data-card-toolbar { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
padding: 16px 32px; |
||||
|
background: #fff; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.toolbar-group { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
} |
||||
|
|
||||
|
.toolbar-label { |
||||
|
font-size: 14px; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.data-card-content { |
||||
|
flex: 1; |
||||
|
padding: 24px 32px; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
|
||||
|
.table-container { |
||||
|
background: #fff; |
||||
|
border-radius: 8px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
||||
|
padding: 24px; |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
.data-table { |
||||
|
border-collapse: collapse; |
||||
|
table-layout: fixed; |
||||
|
} |
||||
|
|
||||
|
.data-table td { |
||||
|
border: 1px solid #ddd; |
||||
|
min-width: 60px; |
||||
|
height: 30px; |
||||
|
position: relative; |
||||
|
cursor: cell; |
||||
|
transition: background-color 0.2s; |
||||
|
} |
||||
|
|
||||
|
.data-table td:hover { |
||||
|
background-color: #f5f7fa; |
||||
|
} |
||||
|
|
||||
|
.data-table td.selected { |
||||
|
background-color: #ecf5ff; |
||||
|
border-color: #409EFF; |
||||
|
} |
||||
|
|
||||
|
.resize-handle-col { |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
top: 0; |
||||
|
width: 5px; |
||||
|
height: 100%; |
||||
|
cursor: col-resize; |
||||
|
background: transparent; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.resize-handle-col:hover { |
||||
|
background: #409EFF; |
||||
|
} |
||||
|
|
||||
|
.resize-handle-row { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 5px; |
||||
|
cursor: row-resize; |
||||
|
background: transparent; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.resize-handle-row:hover { |
||||
|
background: #409EFF; |
||||
|
} |
||||
|
|
||||
|
.cell-input { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
border: none; |
||||
|
outline: none; |
||||
|
padding: 4px 8px; |
||||
|
font-size: 14px; |
||||
|
background: transparent; |
||||
|
} |
||||
|
|
||||
|
.color-picker-panel { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 4px; |
||||
|
width: 180px; |
||||
|
} |
||||
|
|
||||
|
.color-option { |
||||
|
width: 24px; |
||||
|
height: 24px; |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
border: 2px solid transparent; |
||||
|
} |
||||
|
|
||||
|
.color-option:hover { |
||||
|
border-color: #409EFF; |
||||
|
} |
||||
|
|
||||
|
.color-option.selected { |
||||
|
border-color: #409EFF; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="app"> |
||||
|
<div class="data-card-container"> |
||||
|
<div class="data-card-header"> |
||||
|
<div class="header-left"> |
||||
|
<div class="back-btn" @click="goBack"> |
||||
|
<i class="el-icon-arrow-left"></i> |
||||
|
返回 |
||||
|
</div> |
||||
|
<div class="data-card-title">数据卡</div> |
||||
|
</div> |
||||
|
<div class="header-right"> |
||||
|
<el-button type="primary" size="small" @click="exportImage"> |
||||
|
<i class="el-icon-picture-outline"></i> |
||||
|
导出图片 |
||||
|
</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="data-card-toolbar"> |
||||
|
<div class="toolbar-group"> |
||||
|
<span class="toolbar-label">行数:</span> |
||||
|
<el-input-number v-model="rows" :min="1" :max="50" size="small" @change="updateTableSize"></el-input-number> |
||||
|
</div> |
||||
|
<div class="toolbar-group"> |
||||
|
<span class="toolbar-label">列数:</span> |
||||
|
<el-input-number v-model="cols" :min="1" :max="20" size="small" @change="updateTableSize"></el-input-number> |
||||
|
</div> |
||||
|
<el-divider direction="vertical"></el-divider> |
||||
|
<div class="toolbar-group"> |
||||
|
<span class="toolbar-label">单元格背景色:</span> |
||||
|
<el-popover |
||||
|
placement="bottom" |
||||
|
width="200" |
||||
|
trigger="click" |
||||
|
v-model="colorPopoverVisible"> |
||||
|
<div class="color-picker-panel"> |
||||
|
<div |
||||
|
v-for="color in colors" |
||||
|
:key="color" |
||||
|
class="color-option" |
||||
|
:style="{ backgroundColor: color }" |
||||
|
:class="{ selected: selectedColor === color }" |
||||
|
@click="selectColor(color)"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<el-button slot="reference" size="small"> |
||||
|
<span :style="{ display: 'inline-block', width: '16px', height: '16px', backgroundColor: selectedColor, marginRight: '8px', verticalAlign: 'middle', borderRadius: '2px' }"></span> |
||||
|
选择颜色 |
||||
|
</el-button> |
||||
|
</el-popover> |
||||
|
</div> |
||||
|
<el-divider direction="vertical"></el-divider> |
||||
|
<div class="toolbar-group"> |
||||
|
<el-button size="small" @click="undo">撤回</el-button> |
||||
|
<el-button size="small" @click="mergeCells">合并单元格</el-button> |
||||
|
<el-button type="primary" size="small" @click="saveTable">保存</el-button> |
||||
|
<el-button type="danger" size="small" @click="clearCell">清空选中</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="data-card-content"> |
||||
|
<div class="table-container" ref="tableContainer"> |
||||
|
<table class="data-table" ref="dataTable"> |
||||
|
<tbody> |
||||
|
<tr v-for="(row, rowIndex) in tableData" :key="rowIndex"> |
||||
|
<td |
||||
|
v-for="(cell, colIndex) in row" |
||||
|
:key="colIndex" |
||||
|
:class="{ selected: selectedCells.some(c => c.row === rowIndex && c.col === colIndex) }" |
||||
|
:rowspan="getRowspan(rowIndex, colIndex)" |
||||
|
:colspan="getColspan(rowIndex, colIndex)" |
||||
|
:style="getCellStyle(rowIndex, colIndex)" |
||||
|
@click="editCell(rowIndex, colIndex)" |
||||
|
@mousedown="startSelect($event, rowIndex, colIndex)" |
||||
|
@mouseover="continueSelect($event, rowIndex, colIndex)" |
||||
|
@mouseup="endSelect"> |
||||
|
<div class="resize-handle-col" @mousedown.stop="startResizeCol($event, colIndex)"></div> |
||||
|
<div class="resize-handle-row" @mousedown.stop="startResizeRow($event, rowIndex)"></div> |
||||
|
<span v-if="!editingCell || editingCell.row !== rowIndex || editingCell.col !== colIndex">{{ cell.content }}</span> |
||||
|
<input |
||||
|
v-else |
||||
|
class="cell-input" |
||||
|
v-model="tableData[rowIndex][colIndex].content" |
||||
|
@blur="finishEditing" |
||||
|
@keyup.enter="finishEditing" |
||||
|
ref="cellInput" |
||||
|
autofocus> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script> |
||||
|
<script src="https://unpkg.com/element-ui/lib/index.js"></script> |
||||
|
<script src="https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js"></script> |
||||
|
<script> |
||||
|
new Vue({ |
||||
|
el: '#app', |
||||
|
data() { |
||||
|
return { |
||||
|
rows: 10, |
||||
|
cols: 8, |
||||
|
tableData: [], |
||||
|
cellWidths: [], |
||||
|
cellHeights: [], |
||||
|
selectedCells: [], |
||||
|
isSelecting: false, |
||||
|
startCell: null, |
||||
|
editingCell: null, |
||||
|
selectedColor: '#ffffff', |
||||
|
colorPopoverVisible: false, |
||||
|
colors: [ |
||||
|
'#ffffff', '#f5f7fa', '#e6a23c', '#67c23a', '#409eff', |
||||
|
'#909399', '#f0f9eb', '#fdf6ec', '#ecf5ff', '#f4f4f5', |
||||
|
'#ff6b6b', '#51cf66', '#339af0', '#cc5de8', '#ff922b', |
||||
|
'#ffd43b', '#20c997', '#22b8cf', '#748ffc', '#be4bdb' |
||||
|
], |
||||
|
resizingCol: null, |
||||
|
resizingRow: null, |
||||
|
startX: 0, |
||||
|
startY: 0, |
||||
|
startWidth: 0, |
||||
|
startHeight: 0, |
||||
|
mergedCells: [], |
||||
|
history: [], |
||||
|
historyIndex: -1 |
||||
|
}; |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.loadTable(); |
||||
|
if (this.tableData.length === 0) { |
||||
|
this.generateTable(); |
||||
|
// 初始化历史记录 |
||||
|
this.saveState(); |
||||
|
} |
||||
|
document.addEventListener('mouseup', this.endSelect); |
||||
|
document.addEventListener('mousemove', this.handleResize); |
||||
|
document.addEventListener('mouseup', this.endResize); |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
document.removeEventListener('mouseup', this.endSelect); |
||||
|
document.removeEventListener('mousemove', this.handleResize); |
||||
|
document.removeEventListener('mouseup', this.endResize); |
||||
|
}, |
||||
|
methods: { |
||||
|
goBack() { |
||||
|
window.close(); |
||||
|
}, |
||||
|
generateTable() { |
||||
|
this.tableData = []; |
||||
|
this.cellWidths = []; |
||||
|
this.cellHeights = []; |
||||
|
this.mergedCells = []; |
||||
|
for (let i = 0; i < this.rows; i++) { |
||||
|
const row = []; |
||||
|
for (let j = 0; j < this.cols; j++) { |
||||
|
row.push({ content: '', bgColor: '#ffffff' }); |
||||
|
} |
||||
|
this.tableData.push(row); |
||||
|
this.cellHeights.push(30); |
||||
|
} |
||||
|
for (let j = 0; j < this.cols; j++) { |
||||
|
this.cellWidths.push(100); |
||||
|
} |
||||
|
this.selectedCells = []; |
||||
|
// 重置历史记录 |
||||
|
this.history = []; |
||||
|
this.historyIndex = -1; |
||||
|
}, |
||||
|
|
||||
|
updateTableSize() { |
||||
|
const oldRows = this.tableData.length; |
||||
|
const oldCols = oldRows > 0 ? this.tableData[0].length : 0; |
||||
|
|
||||
|
if (this.rows !== oldRows || this.cols !== oldCols) { |
||||
|
// 保存当前状态 |
||||
|
this.saveState(); |
||||
|
|
||||
|
// 处理行的增减 |
||||
|
if (this.rows > oldRows) { |
||||
|
// 增加行 |
||||
|
for (let i = oldRows; i < this.rows; i++) { |
||||
|
const row = []; |
||||
|
for (let j = 0; j < this.cols; j++) { |
||||
|
row.push({ content: '', bgColor: '#ffffff' }); |
||||
|
} |
||||
|
this.tableData.push(row); |
||||
|
this.cellHeights.push(30); |
||||
|
} |
||||
|
} else if (this.rows < oldRows) { |
||||
|
// 减少行 |
||||
|
this.tableData.splice(this.rows); |
||||
|
this.cellHeights.splice(this.rows); |
||||
|
} |
||||
|
|
||||
|
// 处理列的增减 |
||||
|
if (this.cols > oldCols) { |
||||
|
// 增加列 |
||||
|
for (let i = 0; i < this.tableData.length; i++) { |
||||
|
for (let j = oldCols; j < this.cols; j++) { |
||||
|
this.tableData[i].push({ content: '', bgColor: '#ffffff' }); |
||||
|
} |
||||
|
} |
||||
|
for (let j = oldCols; j < this.cols; j++) { |
||||
|
this.cellWidths.push(100); |
||||
|
} |
||||
|
} else if (this.cols < oldCols) { |
||||
|
// 减少列 |
||||
|
for (let i = 0; i < this.tableData.length; i++) { |
||||
|
this.tableData[i].splice(this.cols); |
||||
|
} |
||||
|
this.cellWidths.splice(this.cols); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
mergeCells() { |
||||
|
if (this.selectedCells.length < 2) { |
||||
|
this.$message.warning('请至少选择两个单元格进行合并'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 保存当前状态 |
||||
|
this.saveState(); |
||||
|
|
||||
|
// 计算合并区域的最小和最大行、列 |
||||
|
const minRow = Math.min(...this.selectedCells.map(c => c.row)); |
||||
|
const maxRow = Math.max(...this.selectedCells.map(c => c.row)); |
||||
|
const minCol = Math.min(...this.selectedCells.map(c => c.col)); |
||||
|
const maxCol = Math.max(...this.selectedCells.map(c => c.col)); |
||||
|
|
||||
|
// 获取左上角单元格的内容和背景色 |
||||
|
const firstCell = this.tableData[minRow][minCol]; |
||||
|
const content = firstCell.content; |
||||
|
const bgColor = firstCell.bgColor; |
||||
|
|
||||
|
// 清除其他单元格的内容和背景色 |
||||
|
for (let r = minRow; r <= maxRow; r++) { |
||||
|
for (let c = minCol; c <= maxCol; c++) { |
||||
|
if (r !== minRow || c !== minCol) { |
||||
|
this.tableData[r][c].content = ''; |
||||
|
this.tableData[r][c].bgColor = '#ffffff'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 记录合并信息 |
||||
|
this.mergedCells.push({ |
||||
|
startRow: minRow, |
||||
|
startCol: minCol, |
||||
|
endRow: maxRow, |
||||
|
endCol: maxCol |
||||
|
}); |
||||
|
|
||||
|
this.$message.success('单元格合并成功'); |
||||
|
}, |
||||
|
|
||||
|
saveTable() { |
||||
|
const tableData = { |
||||
|
rows: this.rows, |
||||
|
cols: this.cols, |
||||
|
tableData: this.tableData, |
||||
|
cellWidths: this.cellWidths, |
||||
|
cellHeights: this.cellHeights, |
||||
|
mergedCells: this.mergedCells |
||||
|
}; |
||||
|
localStorage.setItem('dataCardTable', JSON.stringify(tableData)); |
||||
|
this.$message.success('表格保存成功!'); |
||||
|
}, |
||||
|
|
||||
|
loadTable() { |
||||
|
const savedData = localStorage.getItem('dataCardTable'); |
||||
|
if (savedData) { |
||||
|
try { |
||||
|
const data = JSON.parse(savedData); |
||||
|
this.rows = data.rows; |
||||
|
this.cols = data.cols; |
||||
|
this.tableData = data.tableData; |
||||
|
this.cellWidths = data.cellWidths; |
||||
|
this.cellHeights = data.cellHeights; |
||||
|
this.mergedCells = data.mergedCells || []; |
||||
|
this.$message.success('表格加载成功!'); |
||||
|
// 初始化历史记录 |
||||
|
this.saveState(); |
||||
|
} catch (e) { |
||||
|
console.error('加载表格失败:', e); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
saveState() { |
||||
|
// 保存当前状态到历史记录 |
||||
|
const state = { |
||||
|
rows: this.rows, |
||||
|
cols: this.cols, |
||||
|
tableData: JSON.parse(JSON.stringify(this.tableData)), |
||||
|
cellWidths: [...this.cellWidths], |
||||
|
cellHeights: [...this.cellHeights], |
||||
|
mergedCells: JSON.parse(JSON.stringify(this.mergedCells)) |
||||
|
}; |
||||
|
|
||||
|
// 如果当前不是在历史记录的最后,删除后面的历史记录 |
||||
|
if (this.historyIndex < this.history.length - 1) { |
||||
|
this.history = this.history.slice(0, this.historyIndex + 1); |
||||
|
} |
||||
|
|
||||
|
// 添加新状态到历史记录 |
||||
|
this.history.push(state); |
||||
|
|
||||
|
// 限制历史记录长度,防止内存占用过大 |
||||
|
if (this.history.length > 50) { |
||||
|
this.history.shift(); |
||||
|
// 调整历史记录索引 |
||||
|
if (this.historyIndex > 0) { |
||||
|
this.historyIndex--; |
||||
|
} |
||||
|
} else { |
||||
|
this.historyIndex++; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
undo() { |
||||
|
if (this.historyIndex > 0) { |
||||
|
this.historyIndex--; |
||||
|
const state = this.history[this.historyIndex]; |
||||
|
this.rows = state.rows; |
||||
|
this.cols = state.cols; |
||||
|
this.tableData = state.tableData; |
||||
|
this.cellWidths = state.cellWidths; |
||||
|
this.cellHeights = state.cellHeights; |
||||
|
this.mergedCells = state.mergedCells; |
||||
|
this.$message.success('操作已撤回'); |
||||
|
} else { |
||||
|
this.$message.info('没有可撤回的操作'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
startSelect(event, row, col) { |
||||
|
if (event.button !== 0) return; |
||||
|
// 只有在按下鼠标并移动时才开始选择 |
||||
|
this.isSelecting = true; |
||||
|
this.startCell = { row, col }; |
||||
|
this.selectedCells = [{ row, col }]; |
||||
|
}, |
||||
|
continueSelect(event, row, col) { |
||||
|
if (!this.isSelecting || !this.startCell) return; |
||||
|
const minRow = Math.min(this.startCell.row, row); |
||||
|
const maxRow = Math.max(this.startCell.row, row); |
||||
|
const minCol = Math.min(this.startCell.col, col); |
||||
|
const maxCol = Math.max(this.startCell.col, col); |
||||
|
this.selectedCells = []; |
||||
|
for (let r = minRow; r <= maxRow; r++) { |
||||
|
for (let c = minCol; c <= maxCol; c++) { |
||||
|
this.selectedCells.push({ row: r, col: c }); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
endSelect() { |
||||
|
this.isSelecting = false; |
||||
|
}, |
||||
|
editCell(row, col) { |
||||
|
// 停止任何正在进行的选择 |
||||
|
this.isSelecting = false; |
||||
|
this.selectedCells = [{ row, col }]; |
||||
|
|
||||
|
// 进入编辑模式 |
||||
|
this.editingCell = { row, col }; |
||||
|
this.$nextTick(() => { |
||||
|
const cellInputs = this.$refs.cellInput; |
||||
|
if (cellInputs) { |
||||
|
// 确保输入框获得焦点 |
||||
|
if (Array.isArray(cellInputs)) { |
||||
|
cellInputs.forEach(input => { |
||||
|
if (input) input.focus(); |
||||
|
}); |
||||
|
} else if (cellInputs) { |
||||
|
cellInputs.focus(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
finishEditing() { |
||||
|
// 保存当前状态 |
||||
|
this.saveState(); |
||||
|
this.editingCell = null; |
||||
|
}, |
||||
|
selectColor(color) { |
||||
|
if (this.selectedCells.length === 0) return; |
||||
|
|
||||
|
// 保存当前状态 |
||||
|
this.saveState(); |
||||
|
|
||||
|
this.selectedColor = color; |
||||
|
this.colorPopoverVisible = false; |
||||
|
this.selectedCells.forEach(cell => { |
||||
|
this.tableData[cell.row][cell.col].bgColor = color; |
||||
|
}); |
||||
|
}, |
||||
|
clearCell() { |
||||
|
if (this.selectedCells.length === 0) return; |
||||
|
|
||||
|
// 保存当前状态 |
||||
|
this.saveState(); |
||||
|
|
||||
|
this.selectedCells.forEach(cell => { |
||||
|
this.tableData[cell.row][cell.col].content = ''; |
||||
|
this.tableData[cell.row][cell.col].bgColor = '#ffffff'; |
||||
|
}); |
||||
|
}, |
||||
|
startResizeCol(event, col) { |
||||
|
this.resizingCol = col; |
||||
|
this.startX = event.clientX; |
||||
|
this.startWidth = this.cellWidths[col]; |
||||
|
}, |
||||
|
startResizeRow(event, row) { |
||||
|
this.resizingRow = row; |
||||
|
this.startY = event.clientY; |
||||
|
this.startHeight = this.cellHeights[row]; |
||||
|
}, |
||||
|
handleResize(event) { |
||||
|
if (this.resizingCol !== null) { |
||||
|
const deltaX = event.clientX - this.startX; |
||||
|
const newWidth = Math.max(60, this.startWidth + deltaX); |
||||
|
this.$set(this.cellWidths, this.resizingCol, newWidth); |
||||
|
} |
||||
|
if (this.resizingRow !== null) { |
||||
|
const deltaY = event.clientY - this.startY; |
||||
|
const newHeight = Math.max(30, this.startHeight + deltaY); |
||||
|
this.$set(this.cellHeights, this.resizingRow, newHeight); |
||||
|
} |
||||
|
}, |
||||
|
endResize() { |
||||
|
this.resizingCol = null; |
||||
|
this.resizingRow = null; |
||||
|
}, |
||||
|
getRowspan(row, col) { |
||||
|
// 检查当前单元格是否是合并单元格的起始单元格 |
||||
|
const mergedCell = this.mergedCells.find(mc => mc.startRow === row && mc.startCol === col); |
||||
|
return mergedCell ? mergedCell.endRow - mergedCell.startRow + 1 : 1; |
||||
|
}, |
||||
|
|
||||
|
getColspan(row, col) { |
||||
|
// 检查当前单元格是否是合并单元格的起始单元格 |
||||
|
const mergedCell = this.mergedCells.find(mc => mc.startRow === row && mc.startCol === col); |
||||
|
return mergedCell ? mergedCell.endCol - mergedCell.startCol + 1 : 1; |
||||
|
}, |
||||
|
|
||||
|
getCellStyle(row, col) { |
||||
|
// 检查当前单元格是否是合并单元格的一部分 |
||||
|
const mergedCell = this.mergedCells.find(mc => |
||||
|
row >= mc.startRow && row <= mc.endRow && |
||||
|
col >= mc.startCol && col <= mc.endCol |
||||
|
); |
||||
|
|
||||
|
// 如果是合并单元格的非起始单元格,隐藏它 |
||||
|
if (mergedCell && (row !== mergedCell.startRow || col !== mergedCell.startCol)) { |
||||
|
return { |
||||
|
display: 'none', |
||||
|
width: this.cellWidths[col] + 'px', |
||||
|
height: this.cellHeights[row] + 'px' |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 正常单元格的样式 |
||||
|
return { |
||||
|
width: this.cellWidths[col] + 'px', |
||||
|
height: this.cellHeights[row] + 'px', |
||||
|
backgroundColor: this.tableData[row][col].bgColor || '#fff' |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
exportImage() { |
||||
|
const container = this.$refs.tableContainer; |
||||
|
html2canvas(container, { |
||||
|
backgroundColor: '#ffffff', |
||||
|
scale: 2, |
||||
|
useCORS: true |
||||
|
}).then(canvas => { |
||||
|
const link = document.createElement('a'); |
||||
|
link.download = '数据卡.png'; |
||||
|
link.href = canvas.toDataURL('image/png'); |
||||
|
link.click(); |
||||
|
this.$message.success('图片导出成功!'); |
||||
|
}).catch(err => { |
||||
|
this.$message.error('图片导出失败:' + err.message); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
</body> |
||||
|
</html> |
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 28 KiB |
@ -0,0 +1,53 @@ |
|||||
|
import request from '@/utils/request' |
||||
|
|
||||
|
export function listTimelineSegments(query) { |
||||
|
return request({ |
||||
|
url: '/system/timeline/list', |
||||
|
method: 'get', |
||||
|
params: query |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export function getTimelineSegmentsByRoomId(roomId) { |
||||
|
return request({ |
||||
|
url: '/system/timeline/listByRoomId/' + roomId, |
||||
|
method: 'get' |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export function getTimelineSegment(id) { |
||||
|
return request({ |
||||
|
url: '/system/timeline/' + id, |
||||
|
method: 'get' |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export function addTimelineSegment(data) { |
||||
|
return request({ |
||||
|
url: '/system/timeline', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export function updateTimelineSegment(data) { |
||||
|
return request({ |
||||
|
url: '/system/timeline', |
||||
|
method: 'put', |
||||
|
data: data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export function delTimelineSegment(id) { |
||||
|
return request({ |
||||
|
url: '/system/timeline/' + id, |
||||
|
method: 'delete' |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export function delTimelineSegmentsByRoomId(roomId) { |
||||
|
return request({ |
||||
|
url: '/system/timeline/room/' + roomId, |
||||
|
method: 'delete' |
||||
|
}) |
||||
|
} |
||||
|
After Width: | Height: | Size: 1.3 KiB |
File diff suppressed because it is too large
@ -0,0 +1,22 @@ |
|||||
|
-- ---------------------------- |
||||
|
-- 时间轴信息表 |
||||
|
-- ---------------------------- |
||||
|
drop table if exists sys_timeline_segment; |
||||
|
create table sys_timeline_segment ( |
||||
|
segment_id bigint(20) not null auto_increment comment '时间段ID', |
||||
|
room_id bigint(20) not null comment '房间ID', |
||||
|
segment_time varchar(8) not null comment '时间点(HH:mm:ss)', |
||||
|
segment_name varchar(50) not null comment '时间段名称', |
||||
|
segment_desc varchar(500) default '' comment '时间段描述', |
||||
|
primary key (segment_id), |
||||
|
key idx_room_id (room_id) |
||||
|
) engine=innodb auto_increment=100 comment = '时间轴信息表'; |
||||
|
|
||||
|
-- ---------------------------- |
||||
|
-- 初始化时间轴信息表数据(示例数据) |
||||
|
-- ---------------------------- |
||||
|
insert into sys_timeline_segment values(1, 1, '08:00:00', '任务准备', '开始准备任务所需的资源和设备'); |
||||
|
insert into sys_timeline_segment values(2, 1, '10:00:00', '资源调配', '完成资源的调配和分配'); |
||||
|
insert into sys_timeline_segment values(3, 1, '12:00:00', '任务执行', '开始执行主要任务'); |
||||
|
insert into sys_timeline_segment values(4, 1, '14:00:00', '任务监控', '监控任务执行进度'); |
||||
|
insert into sys_timeline_segment values(5, 1, '16:00:00', '任务完成', '任务完成,进行总结'); |
||||
Loading…
Reference in new issue