导入导出

Raod 3 years ago
parent ccaeabfb5c
commit 17c2265082

@ -11,6 +11,7 @@ import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -72,21 +73,24 @@ public class ReportDashboardController {
* @param reportCode
* @return
*/
@GetMapping("/export/{reportCode}")
@GetMapping("/export")
@Permission(code = "view", name = "导出大屏")
public ResponseEntity<byte[]> exportDashboard(HttpServletRequest request, HttpServletResponse response, @PathVariable("reportCode") String reportCode) {
return reportDashboardService.exportDashboard(request, response, reportCode);
public ResponseEntity<byte[]> exportDashboard(HttpServletRequest request, HttpServletResponse response,
@RequestParam("reportCode") String reportCode, @RequestParam(value = "showDataSet",required = false, defaultValue = "1") Integer showDataSet) {
return reportDashboardService.exportDashboard(request, response, reportCode, showDataSet);
}
/**
*
* @param dto
* @param file zip
* @param reportCode
* @return
*/
@PostMapping("/import")
@PostMapping("/import/{reportCode}")
@Permission(code = "design", name = "导入大屏")
public ResponseBean importDashboard(@RequestBody ChartDto dto) {
return ResponseBean.builder().data(reportDashboardService.getChartData(dto)).build();
public ResponseBean importDashboard(@RequestParam("file") MultipartFile file, @PathVariable("reportCode") String reportCode) {
reportDashboardService.importDashboard(file, reportCode);
return ResponseBean.builder().build();
}
}

@ -7,6 +7,7 @@ import com.anjiplus.template.gaea.business.modules.dashboard.controller.dto.Repo
import com.anjiplus.template.gaea.business.modules.dashboard.controller.param.ReportDashboardParam;
import com.anjiplus.template.gaea.business.modules.dashboard.dao.entity.ReportDashboard;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -48,5 +49,13 @@ public interface ReportDashboardService extends GaeaBaseService<ReportDashboardP
* @param reportCode
* @return
*/
ResponseEntity<byte[]> exportDashboard(HttpServletRequest request, HttpServletResponse response, String reportCode);
ResponseEntity<byte[]> exportDashboard(HttpServletRequest request, HttpServletResponse response, String reportCode, Integer showDataSet);
/**
* zip
* @param file
* @param reportCode
* @return
*/
void importDashboard(MultipartFile file, String reportCode);
}

@ -43,6 +43,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -133,7 +134,7 @@ public class ReportDashboardServiceImpl implements ReportDashboardService, Initi
* @param dto
*/
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public void insertDashboard(ReportDashboardObjectDto dto) {
String reportCode = dto.getReportCode();
GaeaAssert.notEmpty(reportCode, ResponseCode.PARAM_IS_NULL, "reportCode");
@ -149,6 +150,7 @@ public class ReportDashboardServiceImpl implements ReportDashboardService, Initi
} else {
//更新
dashboard.setId(reportDashboard.getId());
dashboard.setVersion(null);
this.update(dashboard);
}
@ -202,7 +204,7 @@ public class ReportDashboardServiceImpl implements ReportDashboardService, Initi
* @return
*/
@Override
public ResponseEntity<byte[]> exportDashboard(HttpServletRequest request, HttpServletResponse response, String reportCode) {
public ResponseEntity<byte[]> exportDashboard(HttpServletRequest request, HttpServletResponse response, String reportCode, Integer showDataSet) {
String userAgent = request.getHeader("User-Agent");
boolean isIeBrowser = userAgent.indexOf("MSIE") > 0;
@ -225,6 +227,16 @@ public class ReportDashboardServiceImpl implements ReportDashboardService, Initi
zipLoadImage(imageAddress, path);
});
//showDataSet == 0 代表不包含数据集
if (0 == showDataSet) {
detail.getWidgets().forEach(reportDashboardWidgetDto -> {
ReportDashboardWidgetValueDto value = reportDashboardWidgetDto.getValue();
JSONObject data = value.getData();
if (null != data && data.containsKey("dataType")) {
reportDashboardWidgetDto.getValue().getData().put("dataType", "staticData");
}
});
}
//2.将大屏设计到的json文件保存
@ -248,13 +260,101 @@ public class ReportDashboardServiceImpl implements ReportDashboardService, Initi
builder.header("Content-Disposition", "attacher; filename*=UTF-8''" + reportCode + ".zip");
}
ResponseEntity<byte[]> body = builder.body(FileUtils.readFileToByteArray(file));
//删除zip文件
file.delete();
//删除path临时文件夹
FileUtil.delete(path);
log.info("删除临时文件:{}{}", zipPath, path);
return builder.body(FileUtils.readFileToByteArray(file));
return body;
}
/**
* zip
*
* @param file
* @param reportCode
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void importDashboard(MultipartFile file, String reportCode) {
log.info("导入开始,{}", reportCode);
//1.组装临时目录,/app/disk/upload/zip/临时文件夹
String path = dictPath + ZIP_PATH + UuidUtil.generateShortUuid();
//2.解压
FileUtil.decompress(file, path);
// path/uuid/
File parentPath = new File(path);
//获取打包的第一层目录
File firstFile = parentPath.listFiles()[0];
File[] files = firstFile.listFiles();
//定义map
Map<String, String> fileMap = new HashMap<>();
String content = "";
for (int i = 0; i < files.length; i++) {
File childFile = files[i];
if (JSON_PATH.equals(childFile.getName())) {
//json文件
content = FileUtil.readFile(childFile);
} else if ("image".equals(childFile.getName())) {
File[] imageFiles = childFile.listFiles();
//所有需要上传的图片
for (File imageFile : imageFiles) {
//查看是否存在此image
String name = imageFile.getName();
String fileName = imageFile.getName().split("\\.")[0];
//根据fileId从gaea_file中读出filePath
LambdaQueryWrapper<GaeaFile> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(GaeaFile::getFileId, fileName);
GaeaFile gaeaFile = gaeaFileService.selectOne(queryWrapper);
if (null == gaeaFile) {
GaeaFile upload = gaeaFileService.upload(null, imageFile, fileName);
fileMap.put(fileName, upload.getUrlPath());
}
}
}
}
//解析cotent
ReportDashboardObjectDto detail = JSONObject.parseObject(content, ReportDashboardObjectDto.class);
//将涉及到的图片路径替换1.背景图2.组件为图片的)
String backgroundImage = detail.getDashboard().getBackgroundImage();
detail.getDashboard().setBackgroundImage(replaceUrl(backgroundImage, fileMap));
detail.getWidgets().stream()
.filter(reportDashboardWidgetDto -> "widget-image".equals(reportDashboardWidgetDto.getType()))
.forEach(reportDashboardWidgetDto -> {
String imageAddress = reportDashboardWidgetDto.getValue().getSetup().getString("imageAdress");
String address = replaceUrl(imageAddress, fileMap);
reportDashboardWidgetDto.getValue().getSetup().put("imageAdress", address);
reportDashboardWidgetDto.getOptions().getJSONArray("setup").getJSONObject(4).put("value", address);
});
//将新的大屏编码赋值
detail.setReportCode(reportCode);
//解析结束,删除临时文件夹
FileUtil.delete(path);
log.info("解析成功,开始存入数据库...");
insertDashboard(detail);
}
private String replaceUrl(String imageAddress, Map<String, String> fileMap) {
String fileId = imageAddress.substring(imageAddress.trim().length() - 36);
String orDefault = fileMap.getOrDefault(fileId, null);
if (StringUtils.isBlank(orDefault)) {
return imageAddress;
}
return orDefault;
}
/**

@ -33,7 +33,7 @@ public class GaeaFileController extends BaseController<GaeaFileParam, GaeaFile,
@PostMapping("/upload")
@Permission(code = "upload", name = "文件上传")
public ResponseBean upload(@RequestParam("file") MultipartFile file) {
return ResponseBean.builder().message("success").data((gaeaFileService.upload(file))).build();
return ResponseBean.builder().message("success").data((gaeaFileService.upload(file, null, null))).build();
}
@GetMapping(value = "/download/{fileId}")

@ -8,6 +8,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
/**
* (GaeaFile)Service
@ -17,15 +18,13 @@ import javax.servlet.http.HttpServletResponse;
*/
public interface GaeaFileService extends GaeaBaseService<GaeaFileParam, GaeaFile> {
/**
*
*
* @param file
* @return 访
*/
GaeaFile upload(MultipartFile file);
GaeaFile upload(MultipartFile multipartFile, File file, String customFileName);
/**
* fileId
*

@ -10,6 +10,7 @@ import com.anjiplus.template.gaea.business.modules.file.entity.GaeaFile;
import com.anjiplus.template.gaea.business.modules.file.service.GaeaFileService;
import com.anjiplus.template.gaea.business.modules.file.util.FileUtils;
import com.anjiplus.template.gaea.business.modules.file.util.StringPatternUtil;
import com.anjiplus.template.gaea.business.util.FileUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
@ -64,11 +65,18 @@ public class GaeaFileServiceImpl implements GaeaFileService {
return gaeaFileMapper;
}
@Override
@Transactional(rollbackFor = Exception.class)
public GaeaFile upload(MultipartFile file) {
public GaeaFile upload(MultipartFile multipartFile, File file, String customFileName) {
try {
String fileName = file.getOriginalFilename();
String fileName = "";
if (null != multipartFile) {
fileName = multipartFile.getOriginalFilename();
}else {
fileName = file.getName();
}
if (StringUtils.isBlank(fileName)) {
throw BusinessExceptionBuilder.build(ResponseCode.FILE_EMPTY_FILENAME);
}
@ -82,7 +90,12 @@ public class GaeaFileServiceImpl implements GaeaFileService {
throw BusinessExceptionBuilder.build(ResponseCode.FILE_SUFFIX_UNSUPPORTED);
}
// 生成文件唯一性标识
String fileId = UUID.randomUUID().toString();
String fileId;
if (StringUtils.isBlank(customFileName)) {
fileId = UUID.randomUUID().toString();
} else {
fileId = customFileName;
}
String newFileName = fileId + suffixName;
// 本地文件保存路径
String filePath = dictPath + newFileName;
@ -102,7 +115,11 @@ public class GaeaFileServiceImpl implements GaeaFileService {
if (!parentFile.exists()) {
parentFile.mkdirs();
}
file.transferTo(dest);
if (null != multipartFile) {
multipartFile.transferTo(dest);
}else {
FileUtil.copyFileUsingFileChannels(file, dest);
}
// 将完整的http访问路径返回
return gaeaFile;
} catch (Exception e) {

@ -3,6 +3,7 @@ package com.anjiplus.template.gaea.business.util;
import com.anji.plus.gaea.code.ResponseCode;
import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.URL;
@ -90,6 +91,11 @@ public class FileUtil {
public static void WriteStringToFile(String filePath, String content) {
try {
//不存在创建文件
File file = new File(filePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileWriter fw = new FileWriter(filePath);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(content);
@ -241,23 +247,45 @@ public class FileUtil {
}
}
public static void decompress(String zipFile, String dstPath) {
try {
decompress(new ZipFile(zipFile), dstPath);
} catch (IOException e) {
log.error("", e);
throw BusinessExceptionBuilder.build(ResponseCode.FAIL_CODE, e.getMessage());
}
}
public static void decompress(MultipartFile zipFile, String dstPath) {
try {
File file = new File(dstPath + File.separator + zipFile.getOriginalFilename());
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
zipFile.transferTo(file);
decompress(new ZipFile(file), dstPath);
//解压完删除
file.delete();
} catch (IOException e) {
log.error("", e);
throw BusinessExceptionBuilder.build(ResponseCode.FAIL_CODE, e.getMessage());
}
}
/**
* zip
*
* @param zipFile
* @param zip
* @param dstPath
* @throws IOException
*/
public static void decompress(String zipFile, String dstPath) {
log.info("解压zip{},临时目录:{}", zipFile, dstPath);
public static void decompress(ZipFile zip, String dstPath) {
log.info("解压zip{},临时目录:{}", zip.getName(), dstPath);
File pathFile = new File(dstPath);
if (!pathFile.exists()) {
pathFile.mkdirs();
}
ZipFile zip = null;
try {
zip = new ZipFile(zipFile);
for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
ZipEntry entry = (ZipEntry) entries.nextElement();
@ -314,8 +342,30 @@ public class FileUtil {
}
}
/**
*
* @param ins
* @param file
*/
private static void inputStreamToFile(InputStream ins, File file) {
try {
OutputStream os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
ins.close();
} catch (Exception e) {
log.error("获取流文件失败", e);
throw BusinessExceptionBuilder.build(ResponseCode.FAIL_CODE, e.getMessage());
}
}
/**
*
*
* @param path
*/
public static void delete(String path) {
@ -357,7 +407,6 @@ public class FileUtil {
// FileUtil.downloadPicture("http://10.108.26.197:9095/file/download/fd20d563-00aa-45e2-b5db-aff951f814ec", "D:\\abc.png");
// delete("D:\\aa");
}

@ -52,7 +52,7 @@ export function exportDashboard(data) {
return new Promise((resolve) =>{
axios({
method:'get',
url: process.env.BASE_API + '/reportDashboard/export/' + data,
url: process.env.BASE_API + '/reportDashboard/export',
headers: { 'Authorization': getToken() },
params:data,
responseType:'blob'

@ -59,7 +59,7 @@
:style="{ width: widthLeftForToolsHideButton + 'px' }"
@click="toolIsShow = !toolIsShow"
>
<i class="el-icon-arrow-right" />
<i class="el-icon-arrow-right"/>
</div>
<div
@ -94,18 +94,49 @@
content="导入"
placement="bottom"
>
<i class="iconfont iconyulan" @click="importDashboard"></i>
<el-upload class="el-upload"
ref="upload"
:action="uploadUrl"
:headers="headers"
accept=".zip"
:on-success="handleUpload"
:on-error="handleError"
:show-file-list="false"
:limit="1">
<i class="iconfont iconxiazai"></i>
</el-upload>
</el-tooltip>
</span>
<span class="btn">
<span class="btn border-left">
<ul class="nav">
<li>
<i class="iconfont iconfenxiang_2"></i><i class="el-icon-arrow-down"></i>
<ul>
<li>
<el-tooltip
class="item"
effect="dark"
content="导出"
placement="bottom"
content="适合当前系统"
placement="right"
>
<div @click="exportDashboard(1)">()</div>
</el-tooltip>
</li>
<li>
<el-tooltip
class="item"
effect="dark"
content="适合跨系统"
placement="right"
>
<i class="iconfont iconyulan" @click="exportDashboard"></i>
<div @click="exportDashboard(0)">()</div>
</el-tooltip>
</li>
</ul>
</li>
</ul>
</span>
<!-- <span class="btn border-left">
<ul class="nav">
@ -136,7 +167,7 @@
</ul>
</li>
</ul>
</span> -->
</span>-->
</div>
<div
class="workbench-container"
@ -246,13 +277,15 @@
</template>
<script>
import { insertDashboard, detailDashboard, importDashboard, exportDashboard } from "@/api/bigscreen";
import { widgetTools, getToolByCode } from "./tools";
import {insertDashboard, detailDashboard, importDashboard, exportDashboard} from "@/api/bigscreen";
import {widgetTools, getToolByCode} from "./tools";
import widget from "./widget/widget.vue";
import dynamicForm from "./form/dynamicForm.vue";
import draggable from "vuedraggable";
import VueRulerTool from "vue-ruler-tool"; //
import contentMenu from "./form/contentMenu";
import {getToken} from "@/utils/auth";
export default {
name: "Login",
components: {
@ -264,6 +297,7 @@ export default {
},
data() {
return {
uploadUrl: process.env.BASE_API + '/reportDashboard/import/' + this.$route.query.reportCode,
grade: false,
layerWidget: [],
widgetTools: widgetTools, // js
@ -330,6 +364,11 @@ export default {
};
},
computed: {
headers() {
return {
Authorization: getToken(), // token
}
},
//
middleWidth() {
var widthLeftAndRight = 0;
@ -402,7 +441,7 @@ export default {
},
async initEchartData() {
const reportCode = this.$route.query.reportCode;
const { code, data } = await detailDashboard(reportCode);
const {code, data} = await detailDashboard(reportCode);
if (code != 200) return;
const processData = this.handleInitEchartsData(data);
const screenData = this.handleBigScreen(data.dashboard);
@ -502,7 +541,7 @@ export default {
},
widgets: this.widgets
};
const { code, data } = await insertDashboard(screenData);
const {code, data} = await insertDashboard(screenData);
if (code == "200") {
this.$message.success("保存成功!");
}
@ -511,14 +550,19 @@ export default {
viewScreen() {
var routeUrl = this.$router.resolve({
path: "/bigscreen/viewer",
query: { reportCode: this.$route.query.reportCode }
query: {reportCode: this.$route.query.reportCode}
});
window.open(routeUrl.href, "_blank");
},
//
async exportDashboard() {
async exportDashboard(val) {
const fileName = this.$route.query.reportCode + ".zip"
exportDashboard(this.$route.query.reportCode).then(res=>{
const param = {
reportCode:this.$route.query.reportCode,
showDataSet:val
}
exportDashboard(param).then(res => {
const blob = new Blob([res], {type: "application/octet-stream"});
if (window.navigator.msSaveOrOpenBlob) {//msSaveOrOpenBlobbool
navigator.msSaveBlob(blob, fileName);//
@ -532,11 +576,24 @@ export default {
})
},
//
importDashboard() {
alert("导入,开发中")
//
handleUpload(response, file, fileList) {
//el-upload
this.$refs.upload.clearFiles();
//
this.initEchartData();
this.$message({
message: '导入成功!',
type: 'success',
})
},
handleError() {
this.$message({
message: '上传失败!',
type: 'error',
})
},
//
getPXUnderScale(px) {
return this.bigscreenScaleInWorkbench * px;
@ -792,22 +849,28 @@ export default {
.mr10 {
margin-right: 10px;
}
.ml20 {
margin-left: 20px;
}
.border-right {
border-right: 1px solid #273b4d;
}
.border-left {
border-left: 1px solid #273b4d;
}
.el-icon-arrow-down {
font-size: 10px;
}
.is-active {
background: #31455d !important;
color: #bfcbd9 !important;
}
.layout {
display: -webkit-box;
display: -ms-flexbox;
@ -817,6 +880,7 @@ export default {
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
.layout-left {
display: inline-block;
height: 100%;
@ -849,6 +913,7 @@ export default {
border: 1px solid #3a4659;
background: #282a30;
}
.tools-item-text {
}
}
@ -865,6 +930,7 @@ export default {
background-color: #242a30;
cursor: pointer;
padding-top: 26%;
i {
font-size: 18px;
width: 18px;
@ -886,18 +952,21 @@ export default {
align-items: center;
vertical-align: middle;
text-align: center;
.top-button {
display: flex;
flex-direction: row;
height: 40px;
line-height: 40px;
margin-left: 9px;
.btn {
color: #788994;
width: 55px;
text-align: center;
display: block;
cursor: pointer;
.el-icon-arrow-down {
transform: rotate(0deg);
-ms-transform: rotate(0deg); /* IE 9 */
@ -906,8 +975,10 @@ export default {
-o-transform: rotate(0deg); /* Opera */
transition: all 0.4s ease-in-out;
}
&:hover {
background: rgb(25, 29, 34);
.el-icon-arrow-down {
transform: rotate(180deg);
-ms-transform: rotate(180deg); /* IE 9 */
@ -919,6 +990,7 @@ export default {
}
}
}
.workbench-container {
position: relative;
-webkit-transform-origin: 0 0;
@ -927,10 +999,12 @@ export default {
box-sizing: border-box;
margin: 0;
padding: 0;
.vueRuler {
width: 100%;
padding: 18px 0px 0px 18px;
}
.workbench {
background-color: #1e1e1e;
position: relative;
@ -943,6 +1017,7 @@ export default {
margin: 0;
padding: 0;
}
.bg-grid {
position: absolute;
top: 0;
@ -958,6 +1033,7 @@ export default {
// z-index: 2;
}
}
.bottom-text {
width: 100%;
color: #a0a0a0;
@ -972,36 +1048,43 @@ export default {
height: 100%;
}
/deep/.el-tabs--border-card {
/deep/ .el-tabs--border-card {
border: 0;
.el-tabs__header {
.el-tabs__nav {
.el-tabs__item {
background-color: #242f3b;
border: 0px;
}
.el-tabs__item.is-active {
background-color: #31455d;
}
}
}
.el-tabs__content {
background-color: #242a30;
height: calc(100vh - 39px);
overflow-x: hidden;
overflow-y: auto;
.el-tab-pane {
color: #bfcbd9;
}
&::-webkit-scrollbar {
width: 5px;
height: 14px;
}
&::-webkit-scrollbar-track,
&::-webkit-scrollbar-thumb {
border-radius: 1px;
border: 0 solid transparent;
}
&::-webkit-scrollbar-track-piece {
/*修改滚动条的背景和圆角*/
background: #29405c;
@ -1011,14 +1094,17 @@ export default {
&::-webkit-scrollbar-track {
box-shadow: 1px 1px 5px rgba(116, 148, 170, 0.5) inset;
}
&::-webkit-scrollbar-thumb {
min-height: 20px;
background-clip: content-box;
box-shadow: 0 0 0 5px rgba(116, 148, 170, 0.5) inset;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
/*修改垂直滚动条的样式*/
&::-webkit-scrollbar-thumb:vertical {
background-color: #00113a;
@ -1033,34 +1119,41 @@ export default {
}
}
}
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
.nav {
width: 40px;
padding: 0;
list-style: none;
/* overflow: hidden; */
}
.nav {
zoom: 1;
}
.nav:before,
.nav:after {
content: "";
display: table;
}
.nav:after {
clear: both;
}
.nav li {
width: 55px;
text-align: center;
position: relative;
}
.nav li a {
float: left;
padding: 12px 30px;
@ -1068,9 +1161,11 @@ li {
font: bold 12px;
text-decoration: none;
}
.nav li:hover {
color: #788994;
}
.nav li ul {
visibility: hidden;
position: absolute;
@ -1085,14 +1180,17 @@ li {
width: 120px;
transition: all 0.2s ease-in-out;
}
.nav li:hover > ul {
opacity: 1;
visibility: visible;
margin: 0;
li:hover {
background-color: rgb(25, 29, 34);
}
}
.nav ul li {
float: left;
display: block;
@ -1100,6 +1198,7 @@ li {
width: 100%;
font-size: 12px;
}
.nav ul a {
padding: 10px;
width: 100%;
@ -1110,12 +1209,15 @@ li {
background-color: rgb(25, 29, 34);
transition: all 0.2s ease-in-out;
}
.nav ul a:hover {
border: 1px solid #3c5e88;
}
.nav ul li:first-child > a:hover:before {
border-bottom-color: #04acec;
}
.nav ul ul {
top: 0;
left: 120px;
@ -1125,6 +1227,7 @@ li {
padding: 10px;
_margin: 0;
}
.nav ul ul li {
width: 120px;
height: 120px;
@ -1132,10 +1235,12 @@ li {
display: block;
float: left;
}
/deep/.vue-ruler-h {
/deep/ .vue-ruler-h {
opacity: 0.3;
}
/deep/.vue-ruler-v {
/deep/ .vue-ruler-v {
opacity: 0.3;
}
</style>

Loading…
Cancel
Save