Commit 3b7116f6 by yuwei

Feature: 新增酷屏功能模块

Update: 统一修改图表默认背景色,适配酷屏功能
Bugfix: 修复一些已知小问题
parent f31d29a2
package cn.datax.service.data.visual.api.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class ScreenAttr implements Serializable {
private static final long serialVersionUID=1L;
private String id;
private String chartName;
private Integer milliseconds;
private String border;
private String backgroundColor;
}
package cn.datax.service.data.visual.api.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.List;
@Data
public class ScreenConfig implements Serializable {
private static final long serialVersionUID=1L;
private Integer width;
private Integer height;
private Integer scale;
private String backgroundImage;
@NotEmpty(message = "布局不能为空")
private List<ScreenItem> layout;
private List<ScreenAttr> property;
}
package cn.datax.service.data.visual.api.dto;
import cn.datax.common.validate.ValidationGroups;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* <p>
* 可视化酷屏配置信息表 实体DTO
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@ApiModel(value = "可视化酷屏配置信息表Model")
@Data
public class ScreenDto implements Serializable {
private static final long serialVersionUID=1L;
@ApiModelProperty(value = "主键ID")
@NotBlank(message = "主键ID不能为空", groups = {ValidationGroups.Update.class})
private String id;
@ApiModelProperty(value = "酷屏名称")
private String screenName;
@ApiModelProperty(value = "酷屏缩略图(图片base64)")
private String screenThumbnail;
@ApiModelProperty(value = "酷屏配置")
@Valid
private ScreenConfig screenConfig;
}
package cn.datax.service.data.visual.api.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class ScreenItem implements Serializable {
private static final long serialVersionUID=1L;
private Integer x;
private Integer y;
private Integer w;
private Integer h;
private String i;
}
package cn.datax.service.data.visual.api.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import cn.datax.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <p>
* 可视化酷屏和图表关联表
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Data
@Accessors(chain = true)
@TableName("visual_screen_chart")
public class ScreenChartEntity implements Serializable {
private static final long serialVersionUID=1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
/**
* 酷屏ID
*/
private String screenId;
/**
* 图表ID
*/
private String chartId;
}
package cn.datax.service.data.visual.api.entity;
import cn.datax.common.base.DataScopeBaseEntity;
import cn.datax.service.data.visual.api.dto.ScreenConfig;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.List;
/**
* <p>
* 可视化酷屏配置信息表
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName(value = "visual_screen", autoResultMap = true)
public class ScreenEntity extends DataScopeBaseEntity {
private static final long serialVersionUID=1L;
/**
* 酷屏名称
*/
private String screenName;
/**
* 酷屏缩略图(图片base64)
*/
private String screenThumbnail;
/**
* 酷屏配置
*/
@TableField(value = "screen_json", typeHandler = JacksonTypeHandler.class)
private ScreenConfig screenConfig;
@TableField(exist = false)
private List<ChartEntity> charts;
}
package cn.datax.service.data.visual.api.query;
import cn.datax.common.base.BaseQueryParams;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 可视化酷屏配置信息表 查询实体
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ScreenQuery extends BaseQueryParams {
private static final long serialVersionUID=1L;
private String screenName;
}
package cn.datax.service.data.visual.api.vo;
import cn.datax.service.data.visual.api.dto.ScreenConfig;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* <p>
* 可视化酷屏配置信息表 实体VO
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Data
public class ScreenVo implements Serializable {
private static final long serialVersionUID=1L;
private String id;
private String status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
private String remark;
private String screenName;
private String screenThumbnail;
private ScreenConfig screenConfig;
private List<ChartVo> charts;
}
package cn.datax.service.data.visual.controller;
import cn.datax.common.core.JsonPage;
import cn.datax.common.core.R;
import cn.datax.common.validate.ValidationGroups;
import cn.datax.service.data.visual.api.dto.BoardDto;
import cn.datax.service.data.visual.api.dto.ScreenDto;
import cn.datax.service.data.visual.api.entity.ScreenEntity;
import cn.datax.service.data.visual.api.vo.ScreenVo;
import cn.datax.service.data.visual.api.query.ScreenQuery;
import cn.datax.service.data.visual.mapstruct.ScreenMapper;
import cn.datax.service.data.visual.service.ScreenService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.datax.common.base.BaseController;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* 可视化酷屏配置信息表 前端控制器
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Api(tags = {"可视化酷屏配置信息表"})
@RestController
@RequestMapping("/screens")
public class ScreenController extends BaseController {
@Autowired
private ScreenService screenService;
@Autowired
private ScreenMapper screenMapper;
/**
* 通过ID查询信息
*
* @param id
* @return
*/
@ApiOperation(value = "获取详细信息", notes = "根据url的id来获取详细信息")
@ApiImplicitParam(name = "id", value = "ID", required = true, dataType = "String", paramType = "path")
@GetMapping("/{id}")
public R getScreenById(@PathVariable String id) {
ScreenEntity screenEntity = screenService.getScreenById(id);
return R.ok().setData(screenMapper.toVO(screenEntity));
}
/**
* 分页查询信息
*
* @param screenQuery
* @return
*/
@ApiOperation(value = "分页查询", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(name = "screenQuery", value = "查询实体screenQuery", required = true, dataTypeClass = ScreenQuery.class)
})
@GetMapping("/page")
public R getScreenPage(ScreenQuery screenQuery) {
QueryWrapper<ScreenEntity> queryWrapper = new QueryWrapper<>();
IPage<ScreenEntity> page = screenService.page(new Page<>(screenQuery.getPageNum(), screenQuery.getPageSize()), queryWrapper);
List<ScreenVo> collect = page.getRecords().stream().map(screenMapper::toVO).collect(Collectors.toList());
JsonPage<ScreenVo> jsonPage = new JsonPage<>(page.getCurrent(), page.getSize(), page.getTotal(), collect);
return R.ok().setData(jsonPage);
}
/**
* 添加
* @param screen
* @return
*/
@ApiOperation(value = "添加信息", notes = "根据screen对象添加信息")
@ApiImplicitParam(name = "screen", value = "详细实体screen", required = true, dataType = "ScreenDto")
@PostMapping()
public R saveScreen(@RequestBody @Validated({ValidationGroups.Insert.class}) ScreenDto screen) {
ScreenEntity screenEntity = screenService.saveScreen(screen);
return R.ok().setData(screenMapper.toVO(screenEntity));
}
/**
* 修改
* @param screen
* @return
*/
@ApiOperation(value = "修改信息", notes = "根据url的id来指定修改对象,并根据传过来的信息来修改详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "screen", value = "详细实体screen", required = true, dataType = "ScreenDto")
})
@PutMapping("/{id}")
public R updateScreen(@PathVariable String id, @RequestBody @Validated({ValidationGroups.Update.class}) ScreenDto screen) {
ScreenEntity screenEntity = screenService.updateScreen(screen);
return R.ok().setData(screenMapper.toVO(screenEntity));
}
/**
* 删除
* @param id
* @return
*/
@ApiOperation(value = "删除", notes = "根据url的id来指定删除对象")
@ApiImplicitParam(name = "id", value = "ID", required = true, dataType = "String", paramType = "path")
@DeleteMapping("/{id}")
public R deleteScreenById(@PathVariable String id) {
screenService.deleteScreenById(id);
return R.ok();
}
/**
* 批量删除
* @param ids
* @return
*/
@ApiOperation(value = "批量删除", notes = "根据url的ids来批量删除对象")
@ApiImplicitParam(name = "ids", value = "ID集合", required = true, dataType = "List", paramType = "path")
@DeleteMapping("/batch/{ids}")
public R deleteScreenBatch(@PathVariable List<String> ids) {
screenService.deleteScreenBatch(ids);
return R.ok();
}
@PostMapping("/copy/{id}")
public R copyScreen(@PathVariable String id) {
screenService.copyScreen(id);
return R.ok();
}
@PutMapping("/build/{id}")
public R buildScreen(@PathVariable String id, @RequestBody ScreenDto screen) {
screenService.buildScreen(screen);
return R.ok();
}
}
package cn.datax.service.data.visual.dao;
import cn.datax.common.base.BaseDao;
import cn.datax.service.data.visual.api.entity.ScreenChartEntity;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* <p>
* 可视化酷屏和图表关联表 Mapper 接口
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Mapper
public interface ScreenChartDao extends BaseDao<ScreenChartEntity> {
void insertBatch(List<ScreenChartEntity> list);
}
package cn.datax.service.data.visual.dao;
import cn.datax.common.base.BaseDao;
import cn.datax.service.data.visual.api.entity.ScreenEntity;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
/**
* <p>
* 可视化酷屏配置信息表 Mapper 接口
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Mapper
public interface ScreenDao extends BaseDao<ScreenEntity> {
@Override
ScreenEntity selectById(Serializable id);
@Override
<E extends IPage<ScreenEntity>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<ScreenEntity> queryWrapper);
}
package cn.datax.service.data.visual.mapstruct;
import cn.datax.common.mapstruct.EntityMapper;
import cn.datax.service.data.visual.api.dto.ScreenDto;
import cn.datax.service.data.visual.api.entity.ScreenEntity;
import cn.datax.service.data.visual.api.vo.ScreenVo;
import org.mapstruct.Mapper;
/**
* <p>
* 可视化酷屏配置信息表 Mapper 实体映射
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Mapper(componentModel = "spring")
public interface ScreenMapper extends EntityMapper<ScreenDto, ScreenEntity, ScreenVo> {
}
package cn.datax.service.data.visual.service;
import cn.datax.service.data.visual.api.entity.ScreenEntity;
import cn.datax.service.data.visual.api.dto.ScreenDto;
import cn.datax.common.base.BaseService;
import java.util.List;
/**
* <p>
* 可视化酷屏配置信息表 服务类
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
public interface ScreenService extends BaseService<ScreenEntity> {
ScreenEntity saveScreen(ScreenDto screen);
ScreenEntity updateScreen(ScreenDto screen);
ScreenEntity getScreenById(String id);
void deleteScreenById(String id);
void deleteScreenBatch(List<String> ids);
void copyScreen(String id);
void buildScreen(ScreenDto screen);
}
...@@ -80,6 +80,7 @@ public class BoardServiceImpl extends BaseServiceImpl<BoardDao, BoardEntity> imp ...@@ -80,6 +80,7 @@ public class BoardServiceImpl extends BaseServiceImpl<BoardDao, BoardEntity> imp
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void copyBoard(String id) { public void copyBoard(String id) {
BoardEntity boardEntity = Optional.ofNullable(super.getById(id)).orElseThrow(() -> new DataException("获取失败")); BoardEntity boardEntity = Optional.ofNullable(super.getById(id)).orElseThrow(() -> new DataException("获取失败"));
BoardEntity copy = new BoardEntity(); BoardEntity copy = new BoardEntity();
......
...@@ -93,6 +93,7 @@ public class ChartServiceImpl extends BaseServiceImpl<ChartDao, ChartEntity> imp ...@@ -93,6 +93,7 @@ public class ChartServiceImpl extends BaseServiceImpl<ChartDao, ChartEntity> imp
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void copyChart(String id) { public void copyChart(String id) {
ChartEntity chartEntity = Optional.ofNullable(super.getById(id)).orElseThrow(() -> new DataException("获取失败")); ChartEntity chartEntity = Optional.ofNullable(super.getById(id)).orElseThrow(() -> new DataException("获取失败"));
ChartEntity copy = new ChartEntity(); ChartEntity copy = new ChartEntity();
...@@ -104,6 +105,7 @@ public class ChartServiceImpl extends BaseServiceImpl<ChartDao, ChartEntity> imp ...@@ -104,6 +105,7 @@ public class ChartServiceImpl extends BaseServiceImpl<ChartDao, ChartEntity> imp
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void buildChart(ChartDto chartDto) { public void buildChart(ChartDto chartDto) {
ChartEntity chart = chartMapper.toEntity(chartDto); ChartEntity chart = chartMapper.toEntity(chartDto);
chartDao.updateById(chart); chartDao.updateById(chart);
......
package cn.datax.service.data.visual.service.impl;
import cn.datax.common.core.DataConstant;
import cn.datax.common.exception.DataException;
import cn.datax.service.data.visual.api.entity.ScreenChartEntity;
import cn.datax.service.data.visual.api.entity.ScreenEntity;
import cn.datax.service.data.visual.api.dto.ScreenDto;
import cn.datax.service.data.visual.dao.ScreenChartDao;
import cn.datax.service.data.visual.service.ScreenService;
import cn.datax.service.data.visual.mapstruct.ScreenMapper;
import cn.datax.service.data.visual.dao.ScreenDao;
import cn.datax.common.base.BaseServiceImpl;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* <p>
* 可视化酷屏配置信息表 服务实现类
* </p>
*
* @author yuwei
* @since 2020-12-15
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class ScreenServiceImpl extends BaseServiceImpl<ScreenDao, ScreenEntity> implements ScreenService {
@Autowired
private ScreenDao screenDao;
@Autowired
private ScreenMapper screenMapper;
@Autowired
private ScreenChartDao screenChartDao;
@Override
@Transactional(rollbackFor = Exception.class)
public ScreenEntity saveScreen(ScreenDto screenDto) {
ScreenEntity screen = screenMapper.toEntity(screenDto);
screenDao.insert(screen);
return screen;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ScreenEntity updateScreen(ScreenDto screenDto) {
ScreenEntity screen = screenMapper.toEntity(screenDto);
screenDao.updateById(screen);
return screen;
}
@Override
public ScreenEntity getScreenById(String id) {
ScreenEntity screenEntity = super.getById(id);
return screenEntity;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteScreenById(String id) {
screenDao.deleteById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteScreenBatch(List<String> ids) {
screenDao.deleteBatchIds(ids);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void copyScreen(String id) {
ScreenEntity screenEntity = Optional.ofNullable(super.getById(id)).orElseThrow(() -> new DataException("获取失败"));
ScreenEntity copy = new ScreenEntity();
copy.setScreenName(screenEntity.getScreenName() + "_副本" + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN));
copy.setScreenThumbnail(screenEntity.getScreenThumbnail());
copy.setScreenConfig(screenEntity.getScreenConfig());
copy.setStatus(DataConstant.EnableState.ENABLE.getKey());
screenDao.insert(copy);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void buildScreen(ScreenDto screenDto) {
ScreenEntity screen = screenMapper.toEntity(screenDto);
screenDao.updateById(screen);
screenChartDao.delete(Wrappers.<ScreenChartEntity>lambdaQuery()
.eq(ScreenChartEntity::getScreenId, screenDto.getId()));
List<ScreenChartEntity> screenChartEntityList = Optional.ofNullable(screen.getScreenConfig().getLayout()).orElse(new ArrayList<>())
.stream().map(s -> {
ScreenChartEntity screenChartEntity = new ScreenChartEntity();
screenChartEntity.setScreenId(screenDto.getId());
screenChartEntity.setChartId(s.getI());
return screenChartEntity;
}).collect(Collectors.toList());
screenChartDao.insertBatch(screenChartEntityList);
}
}
<?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="cn.datax.service.data.visual.dao.ScreenChartDao">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.datax.service.data.visual.api.entity.ScreenChartEntity">
<result column="id" property="id" />
<result column="screen_id" property="screenId" />
<result column="chart_id" property="chartId" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id,
screen_id, chart_id
</sql>
<insert id="insertBatch" parameterType="java.util.List">
INSERT INTO visual_screen_chart
(id, screen_id, chart_id)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.id}, #{item.screenId}, #{item.chartId})
</foreach>
</insert>
</mapper>
<?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="cn.datax.service.data.visual.dao.ScreenDao">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.datax.service.data.visual.api.entity.ScreenEntity">
<result column="id" property="id" />
<result column="status" property="status" />
<result column="create_by" property="createBy" />
<result column="create_time" property="createTime" />
<result column="update_by" property="updateBy" />
<result column="update_time" property="updateTime" />
<result column="remark" property="remark" />
<result column="create_dept" property="createDept" />
<result column="screen_name" property="screenName" />
<result column="screen_thumbnail" property="screenThumbnail" />
</resultMap>
<resultMap id="ExtendResultMap" type="cn.datax.service.data.visual.api.entity.ScreenEntity" extends="BaseResultMap">
<result column="screen_json" property="screenConfig" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
<collection property="charts" column="{screenId=id}" select="getChartList"></collection>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id,
status,
create_by,
create_time,
update_by,
update_time,
remark,
create_dept, screen_name, screen_thumbnail
</sql>
<sql id="Extend_Column_List">
id,
status,
create_by,
create_time,
update_by,
update_time,
remark,
create_dept, screen_name, screen_thumbnail, screen_json
</sql>
<select id="selectById" resultMap="ExtendResultMap">
SELECT
<include refid="Extend_Column_List"></include>
FROM visual_screen
WHERE 1=1 AND id = #{id}
</select>
<select id="getChartList" resultMap="cn.datax.service.data.visual.dao.ChartDao.ExtendResultMap">
SELECT
<include refid="cn.datax.service.data.visual.dao.ChartDao.Chart_Column_List">
<property name="alias" value="c"/>
</include>
FROM visual_chart c
LEFT JOIN visual_screen_chart sc ON sc.chart_id = c.id
WHERE 1 = 1
<if test="screenId != null and screenId != ''">
AND sc.screen_id = #{screenId}
</if>
</select>
<select id="selectPage" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"></include>
FROM visual_screen
${ew.customSqlSegment}
</select>
</mapper>
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
"echarts-wordcloud": "^1.1.3", "echarts-wordcloud": "^1.1.3",
"element-ui": "2.13.2", "element-ui": "2.13.2",
"good-storage": "^1.1.1", "good-storage": "^1.1.1",
"html2canvas": "^1.0.0-rc.7",
"jsplumb": "^2.14.6", "jsplumb": "^2.14.6",
"moment": "^2.29.1", "moment": "^2.29.1",
"normalize.css": "7.0.0", "normalize.css": "7.0.0",
...@@ -27,6 +26,7 @@ ...@@ -27,6 +26,7 @@
"sql-formatter": "^2.3.3", "sql-formatter": "^2.3.3",
"vue": "2.6.10", "vue": "2.6.10",
"vue-codemirror": "^4.0.6", "vue-codemirror": "^4.0.6",
"vue-draggable-resizable-gorkys": "^2.4.4",
"vue-grid-layout": "^2.3.11", "vue-grid-layout": "^2.3.11",
"vue-router": "3.0.6", "vue-router": "3.0.6",
"vuedraggable": "^2.24.0", "vuedraggable": "^2.24.0",
......
import request from '@/utils/request'
export function pageDataScreen(data) {
return request({
url: '/data/visual/screens/page',
method: 'get',
params: data
})
}
export function getDataScreen(id) {
return request({
url: '/data/visual/screens/' + id,
method: 'get'
})
}
export function delDataScreen(id) {
return request({
url: '/data/visual/screens/' + id,
method: 'delete'
})
}
export function addDataScreen(data) {
return request({
url: '/data/visual/screens',
method: 'post',
data: data
})
}
export function updateDataScreen(data) {
return request({
url: '/data/visual/screens/' + data.id,
method: 'put',
data: data
})
}
export function copyDataScreen(id) {
return request({
url: '/data/visual/screens/copy/' + id,
method: 'post'
})
}
export function buildDataScreen(data) {
return request({
url: '/data/visual/screens/build/' + data.id,
method: 'put',
data: data
})
}
...@@ -519,12 +519,6 @@ table { ...@@ -519,12 +519,6 @@ table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
border: none; border: none;
thead {
background-color: rgba(255, 255, 255, 0.1);
}
tbody {
background-color: rgba(255, 255, 255, 0.1);
}
td, th { td, th {
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 0; padding: 0;
...@@ -543,7 +537,6 @@ table { ...@@ -543,7 +537,6 @@ table {
min-height: 36px; min-height: 36px;
cursor: default; cursor: default;
&.col-corner-bg { &.col-corner-bg {
background-color: rgba(255, 255, 255, 0.1);
} }
} }
} }
......
...@@ -85,6 +85,18 @@ export const constantRoutes = [ ...@@ -85,6 +85,18 @@ export const constantRoutes = [
}, },
{ {
path: '/visual/screen/build/:id',
component: () => import('@/views/visual/datascreen/DataScreenBuild'),
hidden: true
},
{
path: '/visual/screen/view/:id',
component: () => import('@/views/visual/datascreen/DataScreenView'),
hidden: true
},
{
path: '/', path: '/',
component: Layout, component: Layout,
redirect: '/dashboard', redirect: '/dashboard',
......
// 压缩图片
export function compressImg(file, config) {
// let fileSize = parseFloat(parseInt(file['size']) / 1024 / 1024).toFixed(2)
const read = new FileReader()
read.readAsDataURL(file)
return new Promise(function(resolve, reject) {
read.onload = function(e) {
const img = new Image()
img.src = e.target.result
img.onload = function() {
// 默认按比例压缩
const w = config.width
const h = config.height
// 生成canvas
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
let base64
// 创建属性节点
canvas.setAttribute('width', w)
canvas.setAttribute('height', h)
ctx.drawImage(this, 0, 0, w, h)
// eslint-disable-next-line prefer-const
base64 = canvas.toDataURL(file['type'], config.quality)
// 回调函数返回file的值(将base64编码转成file)
// const files = dataURLtoFile(base64) // 如果后台接收类型为base64的话这一步可以省略
// 回调函数返回file的值(将base64转为二进制)
// const fileBinary = dataURLtoBlob(base64)
resolve(base64)
}
}
})
}
// 将base64转为二进制
export function dataURLtoBlob(dataurl) {
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
// base64转码(将base64编码转回file文件)
export function dataURLtoFile(dataurl) {
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], { type: mime })
}
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="widget-center-wrapper" id="boardWrapper"> <div class="widget-center-wrapper">
<grid-layout <grid-layout
:layout.sync="layout" :layout.sync="layout"
:col-num="24" :col-num="24"
...@@ -83,12 +83,20 @@ ...@@ -83,12 +83,20 @@
<el-input v-model="dataBoard.boardName" size="mini" :disabled="true" /> <el-input v-model="dataBoard.boardName" size="mini" :disabled="true" />
</el-form-item> </el-form-item>
<el-form-item label="缩略图"> <el-form-item label="缩略图">
<el-button type="primary" size="mini" plain style="margin-bottom: 10px;" @click="generatorImage">点击生成</el-button> <el-upload
<el-image :src="dataBoard.boardThumbnail ? dataBoard.boardThumbnail : ''"> action="#"
<div slot="error" class="image-slot"> accept=".png, .jpg, .jpeg"
<i class="el-icon-picture-outline" /> list-type="picture-card"
</div> :limit="1"
</el-image> :auto-upload="false"
:before-upload="beforeUpload"
:on-change="handleChange"
:on-remove="handleRemove"
:file-list="fileList"
:class="{ hideUpload: hideUpload }"
>
<i slot="default" class="el-icon-plus" />
</el-upload>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
...@@ -101,7 +109,7 @@ import { getDataBoard, buildDataBoard } from '@/api/visual/databoard' ...@@ -101,7 +109,7 @@ import { getDataBoard, buildDataBoard } from '@/api/visual/databoard'
import { listDataChart, getDataChart, dataParser } from '@/api/visual/datachart' import { listDataChart, getDataChart, dataParser } from '@/api/visual/datachart'
import VueGridLayout from 'vue-grid-layout' import VueGridLayout from 'vue-grid-layout'
import ChartPanel from '../datachart/components/ChartPanel' import ChartPanel from '../datachart/components/ChartPanel'
import html2canvas from 'html2canvas' import { compressImg, dataURLtoBlob } from '@/utils/compressImage'
export default { export default {
name: 'DataBoardBuild', name: 'DataBoardBuild',
...@@ -117,7 +125,10 @@ export default { ...@@ -117,7 +125,10 @@ export default {
layout: [], layout: [],
interval: [], interval: [],
charts: [], charts: [],
drawer: false drawer: false,
// 文件上传
fileList: [],
hideUpload: false
} }
}, },
created() { created() {
...@@ -129,6 +140,12 @@ export default { ...@@ -129,6 +140,12 @@ export default {
getDataBoard(id).then(response => { getDataBoard(id).then(response => {
if (response.success) { if (response.success) {
this.dataBoard = response.data this.dataBoard = response.data
if (this.dataBoard.boardThumbnail) {
const blob = dataURLtoBlob(this.dataBoard.boardThumbnail)
const fileUrl = URL.createObjectURL(blob)
this.fileList.push({ url: fileUrl })
this.hideUpload = true
}
this.layout = this.dataBoard.boardConfig ? JSON.parse(JSON.stringify(this.dataBoard.boardConfig.layout)) : [] this.layout = this.dataBoard.boardConfig ? JSON.parse(JSON.stringify(this.dataBoard.boardConfig.layout)) : []
this.interval = this.dataBoard.boardConfig ? JSON.parse(JSON.stringify(this.dataBoard.boardConfig.interval)) : [] this.interval = this.dataBoard.boardConfig ? JSON.parse(JSON.stringify(this.dataBoard.boardConfig.interval)) : []
const charts = this.dataBoard.charts ? JSON.parse(JSON.stringify(this.dataBoard.charts)) : [] const charts = this.dataBoard.charts ? JSON.parse(JSON.stringify(this.dataBoard.charts)) : []
...@@ -216,6 +233,7 @@ export default { ...@@ -216,6 +233,7 @@ export default {
}, },
handleReset() { handleReset() {
this.layout = this.dataBoard.boardConfig ? JSON.parse(JSON.stringify(this.dataBoard.boardConfig.layout)) : [] this.layout = this.dataBoard.boardConfig ? JSON.parse(JSON.stringify(this.dataBoard.boardConfig.layout)) : []
this.interval = this.dataBoard.boardConfig ? JSON.parse(JSON.stringify(this.dataBoard.boardConfig.interval)) : []
const charts = this.dataBoard.charts ? JSON.parse(JSON.stringify(this.dataBoard.charts)) : [] const charts = this.dataBoard.charts ? JSON.parse(JSON.stringify(this.dataBoard.charts)) : []
charts.forEach((item, index, arr) => { charts.forEach((item, index, arr) => {
this.parserChart(item) this.parserChart(item)
...@@ -253,11 +271,28 @@ export default { ...@@ -253,11 +271,28 @@ export default {
this.$refs[`charts${i}`][0].$children[0].$emit('resized') this.$refs[`charts${i}`][0].$children[0].$emit('resized')
} }
}, },
generatorImage() { beforeUpload(file) {
html2canvas(document.getElementById('boardWrapper')).then(canvas => { const isLt2M = file.size / 1024 / 1024 < 2
const dataURL = canvas.toDataURL('image/png') if (!isLt2M) {
this.$set(this.dataBoard, 'boardThumbnail', dataURL) this.$message.error('上传图片大小不能超过 2MB')
}
return isLt2M
},
handleChange(file, fileList) {
this.hideUpload = fileList.length >= 1
const config = {
width: 250, // 压缩后图片的宽
height: 150, // 压缩后图片的高
quality: 0.8 // 压缩后图片的清晰度,取值0-1,值越小,所绘制出的图像越模糊
}
compressImg(file.raw, config).then(result => {
// result 为压缩后二进制文件
this.$set(this.dataBoard, 'boardThumbnail', result)
}) })
},
handleRemove(file, fileList) {
this.hideUpload = fileList.length >= 1
this.$set(this.dataBoard, 'boardThumbnail', '')
} }
} }
} }
...@@ -324,7 +359,7 @@ export default { ...@@ -324,7 +359,7 @@ export default {
} }
} }
.widget-center-container { .widget-center-container {
width: calc(100% - 550px); width: calc(100% - 250px);
flex: 1; flex: 1;
flex-basis: auto; flex-basis: auto;
box-sizing: border-box; box-sizing: border-box;
...@@ -371,18 +406,15 @@ export default { ...@@ -371,18 +406,15 @@ export default {
} }
.widget-board-form { .widget-board-form {
padding: 20px; padding: 20px;
.el-image{ .hideUpload ::v-deep {
width: 250px; .el-upload--picture-card {
height: 150px; display: none;
::v-deep .image-slot { }
display: flex; }
justify-content: center; ::v-deep {
align-items: center; .el-upload-list__item {
width: 100%; width: 250px;
height: 100%; height: 150px;
background: #f5f7fa;
color: #909399;
font-size: 30px;
} }
} }
} }
......
<template> <template>
<div class="chart-container"> <div class="board-container">
<el-row> <el-row>
<el-col> <el-col>
<el-pagination <el-pagination
...@@ -138,7 +138,7 @@ export default { ...@@ -138,7 +138,7 @@ export default {
}) })
}, },
handleCopy(data) { handleCopy(data) {
this.$confirm('确认拷贝当前图表, 是否继续?', '提示', { this.$confirm('确认拷贝当前看板, 是否继续?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</div> </div>
<div class="widget-center-container"> <div class="widget-center-container">
<div class="widget-center-header"> <div class="widget-center-header">
<div class="widget-center-header-collapse" @click="handleCollapse"><i :class="{'is-active': isCollapse}" class="el-icon-d-arrow-right"></i></div> <div class="widget-center-header-collapse" @click="handleCollapse"><i :class="{'is-active': isCollapse}" class="el-icon-d-arrow-right" /></div>
<div class="widget-center-header-button"> <div class="widget-center-header-button">
<el-button icon="el-icon-view" type="text" @click="handlePreview"> <el-button icon="el-icon-view" type="text" @click="handlePreview">
预览 预览
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
<el-button icon="el-icon-delete" type="text" @click="handleReset"> <el-button icon="el-icon-delete" type="text" @click="handleReset">
重置 重置
</el-button> </el-button>
<el-button icon="el-icon-plus" type="text" v-hasPerm="['visual:chart:build']" @click="handleSubmit"> <el-button v-hasPerm="['visual:chart:build']" icon="el-icon-plus" type="text" @click="handleSubmit">
保存 保存
</el-button> </el-button>
<el-button icon="el-icon-close" type="text" @click="handleCancel"> <el-button icon="el-icon-close" type="text" @click="handleCancel">
...@@ -108,12 +108,20 @@ ...@@ -108,12 +108,20 @@
<el-input v-model="dataChart.chartName" :disabled="true" /> <el-input v-model="dataChart.chartName" :disabled="true" />
</el-form-item> </el-form-item>
<el-form-item label="缩略图"> <el-form-item label="缩略图">
<el-button type="primary" plain style="margin-bottom: 10px;" @click="generatorImage">点击生成</el-button> <el-upload
<el-image :src="dataChart.chartThumbnail ? dataChart.chartThumbnail : ''"> action="#"
<div slot="error" class="image-slot"> accept=".png, .jpg, .jpeg"
<i class="el-icon-picture-outline" /> list-type="picture-card"
</div> :limit="1"
</el-image> :auto-upload="false"
:before-upload="beforeUpload"
:on-change="handleChange"
:on-remove="handleRemove"
:file-list="fileList"
:class="{ hideUpload: hideUpload }"
>
<i slot="default" class="el-icon-plus" />
</el-upload>
</el-form-item> </el-form-item>
<el-form-item label="图表类型"> <el-form-item label="图表类型">
<div class="chart-type-list"> <div class="chart-type-list">
...@@ -182,8 +190,8 @@ ...@@ -182,8 +190,8 @@
</el-form-item> </el-form-item>
<el-form-item label="图例的类型"> <el-form-item label="图例的类型">
<el-select v-model="widget.options.legend.type"> <el-select v-model="widget.options.legend.type">
<el-option label="普通图例" value="plain"></el-option> <el-option label="普通图例" value="plain" />
<el-option label="可滚动翻页的图例" value="scroll"></el-option> <el-option label="可滚动翻页的图例" value="scroll" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="离左侧的距离"> <el-form-item label="离左侧的距离">
...@@ -194,8 +202,8 @@ ...@@ -194,8 +202,8 @@
</el-form-item> </el-form-item>
<el-form-item label="列表的布局朝向"> <el-form-item label="列表的布局朝向">
<el-select v-model="widget.options.legend.orient"> <el-select v-model="widget.options.legend.orient">
<el-option label="横向" value="horizontal"></el-option> <el-option label="横向" value="horizontal" />
<el-option label="纵向" value="vertical"></el-option> <el-option label="纵向" value="vertical" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -228,7 +236,7 @@ import { getDataSet, listDataSet } from '@/api/visual/dataset' ...@@ -228,7 +236,7 @@ import { getDataSet, listDataSet } from '@/api/visual/dataset'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { chartTypes, chartThemes, chartSeriesTypes, chartOptions } from '@/utils/visual-chart' import { chartTypes, chartThemes, chartSeriesTypes, chartOptions } from '@/utils/visual-chart'
import ChartPanel from './components/ChartPanel' import ChartPanel from './components/ChartPanel'
import html2canvas from 'html2canvas' import { compressImg, dataURLtoBlob } from '@/utils/compressImage'
export default { export default {
name: 'DataChartBuild', name: 'DataChartBuild',
...@@ -267,7 +275,10 @@ export default { ...@@ -267,7 +275,10 @@ export default {
}, },
visible: false, visible: false,
isCollapse: false, isCollapse: false,
collapsActiveNames: '1' collapsActiveNames: '1',
// 文件上传
fileList: [],
hideUpload: false
} }
}, },
created() { created() {
...@@ -279,6 +290,12 @@ export default { ...@@ -279,6 +290,12 @@ export default {
getDataChart(id).then(response => { getDataChart(id).then(response => {
if (response.success) { if (response.success) {
this.dataChart = response.data this.dataChart = response.data
if (this.dataChart.chartThumbnail) {
const blob = dataURLtoBlob(this.dataChart.chartThumbnail)
const fileUrl = URL.createObjectURL(blob)
this.fileList.push({ url: fileUrl })
this.hideUpload = true
}
if (this.dataChart.chartConfig) { if (this.dataChart.chartConfig) {
const chartConfig = JSON.parse(this.dataChart.chartConfig) const chartConfig = JSON.parse(this.dataChart.chartConfig)
getDataSet(chartConfig.dataSetId).then(response => { getDataSet(chartConfig.dataSetId).then(response => {
...@@ -375,21 +392,28 @@ export default { ...@@ -375,21 +392,28 @@ export default {
this.widget.chartType = chart this.widget.chartType = chart
this.widget.seriesType = ((this.chartSeriesTypes[this.widget.chartType] || [])[0] || {}).value || undefined this.widget.seriesType = ((this.chartSeriesTypes[this.widget.chartType] || [])[0] || {}).value || undefined
}, },
generatorImage() { beforeUpload(file) {
html2canvas(document.getElementById('chartPanel')).then(canvas => { const isLt2M = file.size / 1024 / 1024 < 2
const image = new Image() if (!isLt2M) {
image.src = canvas.toDataURL('image/png') this.$message.error('上传图片大小不能超过 2MB')
const dataURL = this.compressImg(image, 250, 150, 0.5) }
this.$set(this.dataChart, 'chartThumbnail', dataURL) return isLt2M
},
handleChange(file, fileList) {
this.hideUpload = fileList.length >= 1
const config = {
width: 250, // 压缩后图片的宽
height: 150, // 压缩后图片的高
quality: 0.8 // 压缩后图片的清晰度,取值0-1,值越小,所绘制出的图像越模糊
}
compressImg(file.raw, config).then(result => {
// result 为压缩后二进制文件
this.$set(this.dataChart, 'chartThumbnail', result)
}) })
}, },
compressImg(img, width, height, rate) { handleRemove(file, fileList) {
const canvas = document.createElement('canvas') this.hideUpload = fileList.length >= 1
const ctx = canvas.getContext('2d') this.$set(this.dataChart, 'chartThumbnail', '')
canvas.width = width
canvas.height = height
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, width, height)
return canvas.toDataURL('image/png', rate)
} }
} }
} }
...@@ -567,18 +591,15 @@ export default { ...@@ -567,18 +591,15 @@ export default {
height: calc(100vh - 40px); height: calc(100vh - 40px);
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
.el-image{ .hideUpload ::v-deep {
width: 250px; .el-upload--picture-card {
height: 150px; display: none;
::v-deep .image-slot { }
display: flex; }
justify-content: center; ::v-deep {
align-items: center; .el-upload-list__item {
width: 100%; width: 250px;
height: 100%; height: 150px;
background: #f5f7fa;
color: #909399;
font-size: 30px;
} }
} }
.chart-type-list { .chart-type-list {
......
...@@ -68,9 +68,22 @@ export default { ...@@ -68,9 +68,22 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)', xAxis: {
xAxis: { type: 'category' }, type: 'category',
yAxis: { type: 'value' }, axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 1)'
}
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 1)'
}
}
},
tooltip: { trigger: 'axis' } tooltip: { trigger: 'axis' }
}, },
calcData: { calcData: {
......
...@@ -68,7 +68,6 @@ export default { ...@@ -68,7 +68,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
</template> </template>
<script> <script>
import echarts from "echarts" import echarts from 'echarts'
import { SEPARATOR } from '@/utils/visual-chart' import { SEPARATOR } from '@/utils/visual-chart'
export default { export default {
...@@ -62,7 +62,6 @@ export default { ...@@ -62,7 +62,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -5,9 +5,6 @@ ...@@ -5,9 +5,6 @@
<div class="card-panel-num">{{ calcData.val }}</div> <div class="card-panel-num">{{ calcData.val }}</div>
<div class="card-panel-text">{{ calcData.key }}</div> <div class="card-panel-text">{{ calcData.key }}</div>
</div> </div>
<div class="card-panel-icon-wrapper icon-people">
<i class="el-icon-s-data"></i>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -217,29 +214,20 @@ export default { ...@@ -217,29 +214,20 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.card-panel { .card-panel {
display: flex;
align-items: center; /*垂直居中*/ align-items: center; /*垂直居中*/
justify-content: center; /*水平居中*/ justify-content: center; /*水平居中*/
background-color: #00c0ef;
.card-panel-description { .card-panel-description {
flex:1;
text-align: center; text-align: center;
.card-panel-num { .card-panel-num {
font-size: 38px; font-size: 38px;
font-weight: bold; font-weight: bold;
color: #fff; color: rgba(0, 0, 0, 1);
} }
.card-panel-text { .card-panel-text {
color: rgba(0,0,0,.45); color: rgba(0, 0, 0, 0.8);
font-size: 16px; font-size: 16px;
margin-top: 12px; margin-top: 12px;
} }
} }
.card-panel-icon-wrapper {
i {
font-size: 90px;
color: rgba(0, 0, 0, 0.15);
}
}
} }
</style> </style>
...@@ -68,9 +68,22 @@ export default { ...@@ -68,9 +68,22 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)', xAxis: {
xAxis: { type: 'category' }, type: 'category',
yAxis: { type: 'value' }, axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 1)'
}
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 1)'
}
}
},
tooltip: { trigger: 'axis' } tooltip: { trigger: 'axis' }
}, },
calcData: { calcData: {
......
...@@ -69,7 +69,6 @@ export default { ...@@ -69,7 +69,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -68,7 +68,6 @@ export default { ...@@ -68,7 +68,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -68,7 +68,6 @@ export default { ...@@ -68,7 +68,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -68,8 +68,14 @@ export default { ...@@ -68,8 +68,14 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)', radar: {
radar: { indicator: [] }, indicator: [],
axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 1)'
}
}
},
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -63,7 +63,6 @@ export default { ...@@ -63,7 +63,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -63,9 +63,20 @@ export default { ...@@ -63,9 +63,20 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)', xAxis: {
xAxis: {}, axisLine: {
yAxis: {}, lineStyle: {
color: 'rgba(0, 0, 0, 1)'
}
}
},
yAxis: {
axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 1)'
}
}
},
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
...@@ -63,7 +63,6 @@ export default { ...@@ -63,7 +63,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b} : {c}' formatter: '{b} : {c}'
......
...@@ -64,7 +64,6 @@ export default { ...@@ -64,7 +64,6 @@ export default {
connector: '-', connector: '-',
chart: null, chart: null,
calcOption: { calcOption: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
tooltip: { trigger: 'item' } tooltip: { trigger: 'item' }
}, },
calcData: { calcData: {
......
<template>
<div class="screen-container">
<el-row>
<el-col>
<el-pagination
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="queryParams.pageNum"
:page-size.sync="queryParams.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-col>
</el-row>
<el-divider />
<el-row :gutter="20">
<el-col :span="6" class="box-card-col" v-hasPerm="['visual:screen:add']">
<el-card :body-style="{ padding: '0px' }" class="box-card-item">
<div class="box-card-item-add" @click="handleAdd">
<div class="icon-block">
<i class="el-icon-plus" />
</div>
</div>
</el-card>
</el-col>
<el-col v-for="(item, index) in tableDataList" :key="item.id" :span="6" class="box-card-col">
<el-card :body-style="{ padding: '0px' }" class="box-card-item">
<div class="box-card-item-body" @mouseenter="mouseEnter(item)" @mouseleave="mouseLeave(item)">
<el-image :src="item.screenThumbnail ? item.screenThumbnail : ''">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline" />
</div>
</el-image>
<div class="box-card-item-edit" :style="{display: (item.show ? 'block' : 'none')}">
<el-button type="primary" v-hasPerm="['visual:screen:build']" @click="handleConfig(item)">编辑</el-button>
</div>
</div>
<div class="box-card-item-footer">
<div class="box-card-item-footer-text">{{ item.screenName }}</div>
<div class="clearfix">
<i class="el-icon-edit-outline" v-hasPerm="['visual:screen:edit']" @click="handleEdit(item)" />
<i class="el-icon-view" v-hasPerm="['visual:screen:preview']" @click="handleView(item)" />
<i class="el-icon-delete" v-hasPerm="['visual:screen:remove']" @click="handleDelete(item)" />
<i class="el-icon-copy-document" v-hasPerm="['visual:screen:copy']" @click="handleCopy(item)" />
</div>
</div>
</el-card>
</el-col>
</el-row>
<screen-form v-if="dialogFormVisible" :visible.sync="dialogFormVisible" :data="currentScreen" @handleScreenFormFinished="getList" />
</div>
</template>
<script>
import { pageDataScreen, delDataScreen, copyDataScreen } from '@/api/visual/datascreen'
import ScreenForm from './components/ScreenForm'
export default {
name: 'DataScreenList',
components: { ScreenForm },
data() {
return {
// 表格数据
tableDataList: [],
// 总数据条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10
},
dialogFormVisible: false,
currentScreen: {}
}
},
created() {
this.getList()
},
methods: {
/** 查询数据列表 */
getList() {
pageDataScreen(this.queryParams).then(response => {
if (response.success) {
const { data } = response
this.tableDataList = data.data
this.total = data.total
}
})
},
handleSizeChange(val) {
console.log(`每页 ${val} 条`)
this.queryParams.pageNum = 1
this.queryParams.pageSize = val
this.getList()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.queryParams.pageNum = val
this.getList()
},
mouseEnter(data) {
this.$set(data, 'show', true)
},
mouseLeave(data) {
this.$set(data, 'show', false)
},
handleAdd() {
this.dialogFormVisible = true
this.currentScreen = {}
},
handleConfig(data) {
const route = this.$router.resolve({ path: `/visual/screen/build/${data.id}` })
window.open(route.href, '_blank')
},
handleEdit(data) {
this.dialogFormVisible = true
this.currentScreen = Object.assign({}, data)
},
handleView(data) {
const route = this.$router.resolve({ path: `/visual/screen/view/${data.id}` })
window.open(route.href, '_blank')
},
handleDelete(data) {
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataScreen(data.id).then(response => {
if (response.success) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
handleCopy(data) {
this.$confirm('确认拷贝当前酷屏, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
copyDataScreen(data.id).then(response => {
if (response.success) {
this.$message.success('拷贝成功')
this.getList()
}
})
}).catch(() => {
})
}
}
}
</script>
<style lang="scss" scoped>
.el-pagination {
text-align: center;
}
.box-card-col {
width: 260px;
height: 185px;
padding-left: 0px;
padding-right: 0px;
margin-right: 10px;
margin-bottom: 10px;
.box-card-item {
width: 260px;
height: 185px;
.box-card-item-body {
display: flex;
justify-content: center;
align-items: center;
.box-card-item-edit {
width: 260px;
height: 150px;
text-align: center;
position: absolute;
background: rgba(4, 11, 28, 0.7);
opacity: 0.8;
button {
margin-top: 55px;
}
}
}
.el-image{
width: 260px;
height: 150px;
display: block;
::v-deep .image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
}
}
.box-card-item-add {
width: 260px;
height: 185px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
i {
font-size: 30px;
}
}
.box-card-item-footer {
padding: 8px 5px;
background-color: #dcdcdc;
display: flex;
justify-content: space-between;
.box-card-item-footer-text {
width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
i {
margin-right: 5px;
cursor: pointer;
}
}
}
}
</style>
<template>
<div class="screen-container">
<div class="widget-center-wrapper" :style="{ background: 'url('+ publicPath + widget.backgroundImage +') 0% 0% / 100% 100%' }">
<vdr
v-for="item in widget.layout"
:key="item.i"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:parent="true"
:draggable="false"
:resizable="false"
:is-conflict-check="true"
:grid=[20,20]
>
<div v-loading="getChartItem(item.i).loading" :style="{backgroundColor: `${getChartProperty(item.i) ? getChartProperty(item.i).backgroundColor : 'rgba(255, 255, 255, 0.1)'}`}">
<chart-panel v-if="getChartItem(item.i).visible" :key="item.i" :ref="`charts${item.i}`" :chart-schema="getChartItem(item.i).chartSchema" :chart-data="getChartItem(item.i).data" :chart-style="{ height: `${item.h}px`, width: `${item.w}px` }" />
<div v-else :style="{height: `${item.h}px`, width: `${item.w}px` }" />
</div>
</vdr>
</div>
</div>
</template>
<script>
import { getDataScreen } from '@/api/visual/datascreen'
import { dataParser } from '@/api/visual/datachart'
import vdr from 'vue-draggable-resizable-gorkys'
import 'vue-draggable-resizable-gorkys/dist/VueDraggableResizable.css'
import ChartPanel from '../datachart/components/ChartPanel'
export default {
name: 'DataScreenView',
components: {
vdr,
ChartPanel
},
data() {
return {
dataScreen: {},
widget: {
width: 1920,
height: 1080,
scale: 100,
backgroundImage: 'images/bg8.jpg',
layout: [],
property: []
},
charts: [],
timers: [],
publicPath: process.env.BASE_URL
}
},
created() {
this.getDataScreen(this.$route.params.id)
},
methods: {
getDataScreen(id) {
getDataScreen(id).then(response => {
if (response.success) {
this.dataScreen = response.data
this.widget.layout = this.dataScreen.screenConfig ? JSON.parse(JSON.stringify(this.dataScreen.screenConfig.layout)) : []
this.widget.property = this.dataScreen.screenConfig ? JSON.parse(JSON.stringify(this.dataScreen.screenConfig.property)) : []
const charts = this.dataScreen.charts ? JSON.parse(JSON.stringify(this.dataScreen.charts)) : []
charts.forEach((item, index, arr) => {
this.parserChart(item)
})
this.charts = charts
this.$nextTick(() => {
this.initTimer()
})
}
})
},
parserChart(chart) {
this.$set(chart, 'loading', true)
if (chart.chartConfig) {
dataParser(JSON.parse(chart.chartConfig)).then(response => {
if (response.success) {
this.$set(chart, 'data', response.data.data)
this.$set(chart, 'chartSchema', JSON.parse(chart.chartConfig))
this.$set(chart, 'loading', false)
this.$set(chart, 'visible', true)
}
})
} else {
this.$set(chart, 'loading', false)
}
},
getChartItem(id) {
return this.charts.find(chart => chart.id === id)
},
getChartProperty(id) {
return this.widget.property.find(property => property.id === id)
},
initTimer() {
this.widget.property.forEach((item, index) => {
if (item.milliseconds && item.milliseconds > 0) {
const timer = setInterval(() => {
const chart = this.charts.find(chart => chart.id === item.id)
if (chart.chartConfig) {
dataParser(JSON.parse(chart.chartConfig)).then(response => {
if (response.success) {
this.$set(chart, 'data', response.data.data)
}
})
}
}, item.milliseconds)
this.timers.push(timer)
}
})
}
},
beforeDestroy() {
this.timers.map((item) => {
clearInterval(item)
})
}
}
</script>
<style lang="scss" scoped>
.screen-container {
height: 100vh;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
::-webkit-scrollbar {
width: 3px;
height: 5px;
}
::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 10px;
}
.widget-center-wrapper {
height: 100%;
overflow: auto;
::v-deep .vdr {
border: none;
}
}
}
</style>
<template>
<el-dialog title="酷屏" width="50%" :visible.sync="dialogVisible" :show-close="false" :close-on-click-modal="false">
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="酷屏名称" prop="screenName">
<el-input v-model="form.screenName" placeholder="请输入酷屏名称" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确定</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
</span>
</el-dialog>
</template>
<script>
import { addDataScreen, updateDataScreen } from '@/api/visual/datascreen'
export default {
name: 'ScreenForm',
props: {
visible: {
type: Boolean,
default: function() {
return false
}
},
data: {
type: Object,
default: function() {
return {}
}
}
},
data() {
return {
form: {
screenName: undefined
},
rules: {
screenName: [
{ required: true, message: '酷屏名称不能为空', trigger: 'blur' }
]
}
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
created() {
this.form = this.data
},
methods: {
submitForm() {
this.$refs['form'].validate(valid => {
if (valid) {
if (this.form.id) {
updateDataScreen(this.form).then(response => {
if (response.success) {
this.$message.success('保存成功')
this.dialogVisible = false
this.$emit('handleScreenFormFinished')
}
}).catch(error => {
this.$message.error(error.msg || '保存失败')
})
} else {
addDataScreen(this.form).then(response => {
if (response.success) {
this.$message.success('保存成功')
this.dialogVisible = false
this.$emit('handleScreenFormFinished')
}
}).catch(error => {
this.$message.error(error.msg || '保存失败')
})
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="app-container">
<transition name="el-zoom-in-center">
<data-screen-list v-if="options.showList" @showCard="showCard" />
</transition>
</div>
</template>
<script>
import DataScreenList from './DataScreenList'
export default {
name: 'DataScreen',
components: { DataScreenList },
data() {
return {
options: {
data: {},
showList: true
}
}
},
methods: {
showCard(data) {
Object.assign(this.options, data)
}
}
}
</script>
<style lang="scss" scoped>
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment