Merge branch 'dev' into pr@dev@refactor_watermark

This commit is contained in:
王嘉豪 2022-11-17 16:25:26 +08:00 committed by GitHub
commit 7f1728dfa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 441 additions and 68 deletions

View File

@ -1,26 +1,99 @@
package io.dataease.auth.filter;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import cn.hutool.core.util.ArrayUtil;
import io.dataease.auth.entity.SysUserEntity;
import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.service.AuthUserService;
import io.dataease.auth.util.JWTUtils;
import io.dataease.commons.license.DefaultLicenseService;
import io.dataease.commons.license.F2CLicenseResponse;
import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.commons.utils.LogUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.filter.AccessControlFilter;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import static io.dataease.commons.license.F2CLicenseResponse.Status;
public class F2CDocFilter extends AccessControlFilter {
private static final String RESULT_URI_KEY = "result_uri_key";
private static final String NOLIC_PAGE = "nolic.html";
private static final String NO_LOGIN_PAGE = "/nologin.html";
private static final String DEFAULT_FAILED_PAGE = "/";
public class F2CDocFilter extends AnonymousFilter {
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest req = (HttpServletRequest) request;
String path = "/deApi";
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
try {
req.getRequestDispatcher(path).forward(req, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
DefaultLicenseService defaultLicenseService = CommonBeanFactory.getBean(DefaultLicenseService.class);
F2CLicenseResponse f2CLicenseResponse = defaultLicenseService.validateLicense();
Status status = f2CLicenseResponse.getStatus();
if (status != Status.valid) {
request.setAttribute(RESULT_URI_KEY, NOLIC_PAGE);
return false;
}
} catch (Exception e) {
request.setAttribute(RESULT_URI_KEY, NOLIC_PAGE);
LogUtil.error(e.getMessage(), e);
return false;
}
try {
Boolean isLogin = validateLogin(request);
if (!isLogin) {
request.setAttribute(RESULT_URI_KEY, NO_LOGIN_PAGE);
return false;
}
} catch (Exception e) {
request.setAttribute(RESULT_URI_KEY, NO_LOGIN_PAGE);
LogUtil.error(e.getMessage(), e);
return false;
}
return true;
}
private Boolean validateLogin(HttpServletRequest request) throws Exception{
String authorization = request.getHeader("Authorization");
if (StringUtils.isBlank(authorization)) {
Cookie[] cookies = request.getCookies();
if (ArrayUtil.isNotEmpty(cookies)) {
Cookie cookie = Arrays.stream(cookies).filter(item -> StringUtils.equals(item.getName(), "Authorization")).findFirst().orElse(null);
if (ObjectUtils.isNotEmpty(cookie) && StringUtils.isNotBlank(cookie.getValue())) {
authorization = cookie.getValue();
}
}
}
if (StringUtils.isBlank(authorization)) {
return false;
}
TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(authorization);
AuthUserService authUserService = CommonBeanFactory.getBean(AuthUserService.class);
SysUserEntity user = authUserService.getUserById(tokenInfo.getUserId());
if (user == null) {
return false;
}
String password = user.getPassword();
boolean verify = JWTUtils.verify(authorization, tokenInfo, password);
return verify;
}
@Override
protected boolean onAccessDenied(ServletRequest req, ServletResponse res) throws Exception {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
Object attribute = request.getAttribute(RESULT_URI_KEY);
String path = ObjectUtils.isNotEmpty(attribute) ? attribute.toString() : DEFAULT_FAILED_PAGE;
request.getRequestDispatcher(path).forward(request, response);
return false;
}
}

View File

@ -1,5 +1,6 @@
package io.dataease.auth.filter;
import cn.hutool.core.util.URLUtil;
import com.auth0.jwt.algorithms.Algorithm;
import io.dataease.auth.entity.ASKToken;
import io.dataease.auth.entity.JWTToken;
@ -23,8 +24,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.Charset;
public class JWTFilter extends BasicHttpAuthenticationFilter {
@ -158,4 +161,18 @@ public class JWTFilter extends BasicHttpAuthenticationFilter {
httpServletResponse.setHeader("authentication-status", "login_expire");
}
@Override
protected boolean onAccessDenied(ServletRequest req, ServletResponse res, Object mappedValue) throws Exception {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
String requestURI = request.getRequestURI();
String msg = requestURI + " has been denied";
String encode = URLUtil.encode(msg, Charset.forName("UTF-8"));
Cookie cookie_error = new Cookie("onAccessDeniedMsg", encode);
cookie_error.setPath("/");
response.addCookie(cookie_error);
response.sendRedirect("/");
return false;
}
}

View File

@ -11,6 +11,7 @@ import java.util.Map;
public class ShiroServiceImpl implements ShiroService {
private final static String ANON = "anon";
private final static String DOC = "doc";
@Override
public Map<String, String> loadFilterChainDefinitionMap() {
@ -20,15 +21,18 @@ public class ShiroServiceImpl implements ShiroService {
// ----------------------------------------------------------
// 放行Swagger2页面需要放行这些
filterChainDefinitionMap.put("/doc.html**", "doc");
filterChainDefinitionMap.put("/deApi**", ANON);
filterChainDefinitionMap.put("/doc.html**", DOC);
filterChainDefinitionMap.put("/deApi**", DOC);
filterChainDefinitionMap.put("/swagger-ui.html", ANON);
filterChainDefinitionMap.put("/swagger-ui/**", ANON);
filterChainDefinitionMap.put("/swagger/**", ANON);
filterChainDefinitionMap.put("/webjars/**", ANON);
filterChainDefinitionMap.put("/swagger-resources/**", ANON);
filterChainDefinitionMap.put("/v2/**", ANON);
filterChainDefinitionMap.put("/v3/**", ANON);
filterChainDefinitionMap.put("/swagger-resources/**", DOC);
filterChainDefinitionMap.put("/v2/**", DOC);
filterChainDefinitionMap.put("/v3/**", DOC);
filterChainDefinitionMap.put("/**.gif", ANON);
filterChainDefinitionMap.put("/**.png", ANON);
filterChainDefinitionMap.put("/static/**", ANON);
filterChainDefinitionMap.put("/css/**", ANON);
@ -108,6 +112,8 @@ public class ShiroServiceImpl implements ShiroService {
filterChainDefinitionMap.put("/plugin/larksuite/getQrParam", ANON);
filterChainDefinitionMap.put("/cas/reset/**", ANON);
filterChainDefinitionMap.put("/cas/loginPage", ANON);
filterChainDefinitionMap.put("/pdf-template/queryAll", ANON);
filterChainDefinitionMap.put("/unauth", ANON);
filterChainDefinitionMap.put("/display/**", ANON);
@ -122,6 +128,7 @@ public class ShiroServiceImpl implements ShiroService {
filterChainDefinitionMap.put("/panel/group/exportDetails", ANON);
filterChainDefinitionMap.put("/dataset/field/linkMultFieldValues", "link");
filterChainDefinitionMap.put("/dataset/field/linkMappingFieldValues", "link");
filterChainDefinitionMap.put("/systemInfo/proxyUserLoginInfo/**", ANON);
filterChainDefinitionMap.put("/**", "authc");

View File

@ -2,17 +2,16 @@ package io.dataease.controller;
import io.dataease.commons.exception.DEException;
import io.dataease.commons.license.DefaultLicenseService;
import io.dataease.commons.license.F2CLicenseResponse;
import io.dataease.commons.utils.CodingUtil;
import io.dataease.commons.utils.LogUtil;
import io.dataease.commons.utils.ServletUtils;
import io.dataease.service.panel.PanelLinkService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@ -42,13 +41,7 @@ public class IndexController {
@GetMapping("/deApi")
public String deApi() {
F2CLicenseResponse f2CLicenseResponse = defaultLicenseService.validateLicense();
switch (f2CLicenseResponse.getStatus()) {
case valid:
return "doc.html";
default:
return "nolic.html";
}
return "doc.html";
}
@GetMapping("/link/{index}")
@ -64,8 +57,8 @@ public class IndexController {
// TODO 增加仪表板外部参数
HttpServletRequest request = ServletUtils.request();
String attachParams = request.getParameter("attachParams");
if(StringUtils.isNotEmpty(attachParams)){
url = url+"&attachParams="+attachParams;
if (StringUtils.isNotEmpty(attachParams)) {
url = url + "&attachParams=" + attachParams;
}
response.sendRedirect(url);
} catch (IOException e) {

View File

@ -3,6 +3,7 @@ package io.dataease.controller.sys;
import io.dataease.dto.UserLoginInfoDTO;
import io.dataease.service.SystemInfoService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
@ -19,6 +20,11 @@ public class SystemInfoController {
@GetMapping("userLoginInfo")
public UserLoginInfoDTO userLoginInfo() throws IOException {
return systemInfoService.getUserLoginInfo();
return systemInfoService.getUserLoginInfo(null);
}
@GetMapping("proxyUserLoginInfo/{userId}")
public UserLoginInfoDTO proxyUserLoginInfo(@PathVariable String userId) throws IOException {
return systemInfoService.getUserLoginInfo(userId);
}
}

View File

@ -1,15 +1,30 @@
package io.dataease.service;
import io.dataease.auth.api.dto.CurrentUserDto;
import io.dataease.commons.utils.AuthUtils;
import io.dataease.commons.utils.BeanUtils;
import io.dataease.commons.utils.IPUtils;
import io.dataease.dto.UserLoginInfoDTO;
import io.dataease.plugins.common.base.domain.SysUser;
import io.dataease.plugins.common.base.mapper.SysUserMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class SystemInfoService {
@Resource
private SysUserMapper sysUserMapper;
public UserLoginInfoDTO getUserLoginInfo() {
public UserLoginInfoDTO getUserLoginInfo(String userId) {
if (StringUtils.isNotEmpty(userId)) {
SysUser userInfo = sysUserMapper.selectByPrimaryKey(Long.parseLong(userId));
CurrentUserDto userDto = new CurrentUserDto();
BeanUtils.copyBean(userDto, userInfo);
return new UserLoginInfoDTO(userDto, IPUtils.get());
}
return new UserLoginInfoDTO(AuthUtils.getUser(), IPUtils.get());
}

View File

@ -908,7 +908,7 @@ public class ChartViewService {
if (StringUtils.equalsIgnoreCase(table.getType(), DatasetType.DB.name())) {
datasourceRequest.setTable(dataTableInfoDTO.getTable());
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "text", "gauge", "liquid")) {
datasourceRequest.setQuery(qp.getSQLSummary(dataTableInfoDTO.getTable(), yAxis, fieldCustomFilter, rowPermissionsTree, extFilterList, view, ds));
querySql = qp.getSQLSummary(dataTableInfoDTO.getTable(), yAxis, fieldCustomFilter, rowPermissionsTree, extFilterList, view, ds);
} else if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {
querySql = qp.getSQLStack(dataTableInfoDTO.getTable(), xAxis, yAxis, fieldCustomFilter, rowPermissionsTree, extFilterList, extStack, ds, view);
} else if (StringUtils.containsIgnoreCase(view.getType(), "scatter")) {

BIN
frontend/public/dynamic.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

BIN
frontend/public/lic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,13 +1,46 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>DataEase</title>
<style>
html,
body {
margin: 0 !important;
height: 100%;
}
.no-login-dynamic {
height: 100%;
background: url(./lic.png) no-repeat;
background-size: cover;
text-align: center;
}
span {
color: #000;
font-size: 25px;
font-weight: 500;
position: relative;
top: 130px;
}
</style>
</head>
<body style="height: 100%;">
<div>缺少许可</div>
<div class="no-login-dynamic">
<span>缺少许可</span>
</div>
</body>
</html>
<script>
document.getElementsByTagName("body")
</script>
</html>

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>DataEase</title>
<style>
html,
body {
margin: 0 !important;
height: 100%;
}
.no-login-dynamic {
height: 100%;
background: url(./dynamic.gif) no-repeat;
background-size: cover;
text-align: center;
}
span {
color: #fff;
font-size: 25px;
font-weight: 500;
position: relative;
top: 30px;
}
</style>
</head>
<body style="height: 100%;">
<div id="de-nologin-div" class="no-login-dynamic">
<span>请先登录,即将跳转!</span>
</div>
</body>
<script>
const timer = setTimeout(() => {
window.location.href = "/";
}, 3500)
</script>
</html>

View File

@ -8,6 +8,15 @@ export function userLoginInfo() {
})
}
export default {
userLoginInfo
export function proxyUserLoginInfo(userId) {
return request({
url: '/systemInfo/proxyUserLoginInfo/' + userId,
method: 'get',
loading: false
})
}
export default {
userLoginInfo,
proxyUserLoginInfo
}

View File

@ -1,18 +1,35 @@
<template>
<div
v-show="existLinkage"
class="bar-main"
:class="containerClass"
>
<div
v-show="isPublicLink && !isNewBlank"
class="bar-main-left"
v-if="isPublicLink"
class="function-div"
>
<el-button
size="mini"
@click="back2Last"
><i class="icon iconfont el-icon-back" />{{ $t('chart.back') }}</el-button>
<el-button-group size="mini">
<el-button
v-if="!isNewBlank"
size="mini"
@click="back2Last"
><i class="icon iconfont el-icon-back" />{{ $t('chart.back') }}</el-button>
<el-button
v-if="existLinkage"
size="mini"
@click="clearAllLinkage"
><i class="icon iconfont icon-quxiaoliandong" />{{ $t('panel.remove_all_linkage') }}</el-button>
<el-button
size="mini"
@click="exportPDF"
><i class="icon iconfont el-icon-download" />{{ $t('panel.down') }}</el-button>
</el-button-group>
</div>
<div class="bar-main-right">
<div
v-else-if="existLinkage"
class="bar-main-right"
>
<el-button
size="mini"
type="warning"
@ -43,6 +60,9 @@ export default {
isNewBlank() {
return window.history.length === 1
},
containerClass() {
return this.isPublicLink ? 'trans-pc' : 'bar-main'
},
...mapState([
'componentData'
])
@ -54,6 +74,9 @@ export default {
},
back2Last() {
this.$router.back(-1)
},
exportPDF() {
this.$emit('link-export-pdf')
}
}
}
@ -86,4 +109,31 @@ export default {
}
}
.trans-pc {
position: absolute;
width: 60px;
right: 0;
top: 0;
border-top: 60px solid rgba(245, 74, 69, 0.2);
border-left: 60px solid transparent;
cursor: pointer;
z-index: 999;
.function-div {
display: none;
position: absolute;
right: 10px;
top: -50px;
width: max-content;
text-align: end;
z-index: 999;
}
&:hover {
border-top: 60px solid rgba(245, 74, 69, 0.8);;
.function-div {
display: block;
}
}
}
</style>

View File

@ -137,7 +137,7 @@
:target="curComponent.hyperlinks.openMode "
:href="curComponent.hyperlinks.content "
>
<i class="icon iconfont icon-com-jump" />
<i class="icon iconfont icon-com-jump"/>
</a>
</span>
@ -167,6 +167,7 @@
<el-dialog
:visible.sync="boardSetVisible"
width="750px"
top="5vh"
class="dialog-css"
:close-on-click-modal="false"
:show-close="false"

View File

@ -1,11 +1,15 @@
<template>
<div
:id="previewMainDomId"
v-loading="dataLoading"
:element-loading-text="$t('panel.data_loading')"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(220,220,220,1)"
class="bg"
:style="customStyle"
@scroll="canvasScroll"
>
<canvas-opt-bar />
<canvas-opt-bar @link-export-pdf="downloadAsPDF" />
<div
:id="previewDomId"
:ref="previewRefId"
@ -57,6 +61,40 @@
/>
</div>
</div>
<el-dialog
v-if="pdfExportShow"
:title="'['+panelInfo.name+']'+'PDF导出'"
:visible.sync="pdfExportShow"
width="80%"
:top="'8vh'"
:destroy-on-close="true"
class="dialog-css2"
>
<span style="position: absolute;right: 70px;top:15px">
<svg-icon
icon-class="PDF"
class="ds-icon-pdf"
/>
<el-select
v-model="pdfTemplateSelectedIndex"
:placeholder="'切换PDF模板'"
@change="changePdfTemplate()"
>
<el-option
v-for="(item, index) in pdfTemplateAll"
:key="index"
:label="item.name"
:value="index"
/>
</el-select>
</span>
<PDFPreExport
:snapshot="snapshotInfo"
:panel-name="panelInfo.name"
:template-content="pdfTemplateContent"
@closePreExport="closePreExport"
/>
</el-dialog>
</div>
</template>
@ -74,12 +112,13 @@ import bus from '@/utils/bus'
import { buildFilterMap, buildViewKeyMap, formatCondition, valueValid, viewIdMatch } from '@/utils/conditionUtil'
import { hasDataPermission } from '@/utils/permission'
import { activeWatermark } from '@/components/canvas/tools/watermark'
import { userLoginInfo } from '@/api/systemInfo/userLogin'
import { proxyUserLoginInfo, userLoginInfo } from '@/api/systemInfo/userLogin'
import html2canvas from 'html2canvasde'
import { queryAll } from '@/api/panel/pdfTemplate'
const erd = elementResizeDetectorMaker()
import PDFPreExport from '@/views/panel/export/PDFPreExport'
export default {
components: { ComponentWrapper, CanvasOptBar },
components: { ComponentWrapper, CanvasOptBar, PDFPreExport },
model: {
prop: 'show',
event: 'change'
@ -140,10 +179,15 @@ export default {
type: String,
require: false,
default: 'canvas-main'
},
userId: {
type: String,
require: false
}
},
data() {
return {
canvasInfoTemp: 'preview-temp-canvas-main',
previewMainDomId: 'preview-main-' + this.canvasId,
previewDomId: 'preview-' + this.canvasId,
previewRefId: 'preview-ref-' + this.canvasId,
@ -171,7 +215,15 @@ export default {
searchCount: 0,
// 1.pc pc 2.mobile
terminal: 'pc',
buttonFilterMap: null
buttonFilterMap: null,
pdfExportShow: false,
dataLoading: false,
exporting: false,
snapshotInfo: '',
pdfTemplateSelectedIndex: 0,
pdfTemplateContent: '',
templateInfo: {},
pdfTemplateAll: []
}
},
computed: {
@ -309,6 +361,7 @@ export default {
}
bus.$on('trigger-search-button', this.triggerSearchButton)
bus.$on('trigger-reset-button', this.triggerResetButton)
this.initPdfTemplate()
},
beforeDestroy() {
erd.uninstall(this.$refs[this.previewTempRefId])
@ -544,6 +597,36 @@ export default {
})
}
}, 1500)
},
downloadAsPDF() {
this.dataLoading = true
const domId = this.canvasInfoTemp
setTimeout(() => {
this.exporting = true
setTimeout(() => {
html2canvas(document.getElementById(domId)).then(canvas => {
const snapshot = canvas.toDataURL('image/jpeg', 1) //
this.dataLoading = false
this.exporting = false
if (snapshot !== '') {
this.snapshotInfo = snapshot
this.pdfExportShow = true
}
})
}, 1500)
}, 500)
},
closePreExport() {
this.pdfExportShow = false
},
changePdfTemplate() {
this.pdfTemplateContent = this.pdfTemplateAll[this.pdfTemplateSelectedIndex] ? this.pdfTemplateAll[this.pdfTemplateSelectedIndex].templateContent : ''
},
initPdfTemplate() {
queryAll().then(res => {
this.pdfTemplateAll = res.data
this.changePdfTemplate()
})
}
}
}

View File

@ -640,7 +640,7 @@ export default {
},
viewInCache(param) {
this.view = param.view
if (this.view.customAttr) {
if (this.view && this.view.customAttr) {
this.currentPage.pageSize = parseInt(JSON.parse(this.view.customAttr).size.tablePageSize)
}
param.viewId && param.viewId === this.element.propValue.viewId && this.getDataEdit(param)
@ -703,7 +703,7 @@ export default {
requestInfo.proxy = { userId: this.panelInfo.proxy }
}
// table-info
if (this.view.customAttr) {
if (this.view && this.view.customAttr) {
const attrSize = JSON.parse(this.view.customAttr).size
if (this.chart.type === 'table-info' && this.view.datasetMode === 0 && (!attrSize.tablePageMode || attrSize.tablePageMode === 'page')) {
requestInfo.goPage = this.currentPage.page
@ -1162,7 +1162,7 @@ export default {
queryFrom: 'panel'
}
// table-info
if (this.view.customAttr) {
if (this.view && this.view.customAttr) {
const attrSize = JSON.parse(this.view.customAttr).size
if (this.chart.type === 'table-info' && this.view.datasetMode === 0 && (!attrSize.tablePageMode || attrSize.tablePageMode === 'page')) {
requestInfo.goPage = this.currentPage.page

View File

@ -1865,8 +1865,12 @@ export default {
sure_bt: 'Confirm'
},
panel: {
down: 'Down',
mobile_style_setting: 'Style setting',
mobile_style_setting_tips: 'Customize the mobile background',
board: 'Border',
text: 'Text',
board_background: 'Background',

View File

@ -1865,8 +1865,12 @@ export default {
sure_bt: '確定'
},
panel: {
down: '下載',
mobile_style_setting: '樣式設置',
mobile_style_setting_tips: '自定義移動端背景',
board: '邊框',
text: '文字',
board_background: '背景',

View File

@ -1865,8 +1865,12 @@ export default {
sure_bt: '确定'
},
panel: {
down: '下载',
mobile_style_setting: '样式设置',
mobile_style_setting_tips: '自定义移动端背景',
board: '边框',
text: '文字',
board_background: '背景',

View File

@ -314,6 +314,16 @@ export const isSameVueObj = (source, target) => {
return false
}
export const isSameArr = (source, target) => {
if (!source && !target) return true
if (source?.length && target?.length && source.length === target.length) {
const sortSource = source.sort()
const sortTarget = target.sort()
return JSON.stringify(sortSource) === JSON.stringify(sortTarget)
}
return false
}
export const changeFavicon = link => {
let $favicon = document.querySelector('link[rel="icon"]')
if ($favicon !== null) {

View File

@ -77,7 +77,7 @@ export default {
.testcase-template {
display: inline-block;
margin: 10px 0px;
margin: 5px 0px;
width: 90px;
}

View File

@ -88,7 +88,7 @@
v-if="curComponent.commonBackground.enable"
style="padding-left: 10px"
>
<el-row style="height: 80px;margin-top:10px;margin-bottom:20px;overflow: hidden">
<el-row style="height: 80px;margin-top:0px;margin-bottom:20px;overflow: hidden">
<el-col
:span="4"
style="padding-left: 10px"
@ -112,7 +112,7 @@
:http-request="upload"
:file-list="fileList"
>
<i class="el-icon-plus" />
<i class="el-icon-plus"/>
</el-upload>
<el-dialog
top="25vh"
@ -164,7 +164,6 @@
:span="6"
>
<background-item
:style="itemStyle"
:template="item"
/>
</el-col>
@ -377,7 +376,7 @@ export default {
.main-row {
padding-left: 10px;
height: 140px;
height: 250px;
overflow-y: auto;
}

View File

@ -5,6 +5,7 @@
:component-data="mainCanvasComponentData"
:canvas-style-data="canvasStyleData"
:panel-info="panelInfo"
:user-id="user"
/>
</div>
</template>

View File

@ -412,10 +412,6 @@ export default {
this.myAttrs.fieldId = null
this.myAttrs.activeName = null
}
if (this.myAttrs.sort?.sort === 'custom') {
this.myAttrs.sort.list = []
}
this.enableSureButton()
},

View File

@ -52,7 +52,7 @@
</span>
<span
v-if="widget.isCustomSortWidget && widget.isCustomSortWidget()"
v-if="widget.isSortWidget && widget.isSortWidget()"
style="padding-left: 10px;"
>

View File

@ -95,6 +95,7 @@
</el-dropdown-item>
<el-dropdown-item
v-if="isCustomSortWidget"
:command="beforeClickItem('custom')"
>
<span
@ -146,6 +147,7 @@
<script>
import { fieldListWithPermission } from '@/api/dataset/dataset'
import FilterCustomSort from './FilterCustomSort'
import { isSameArr } from '@/utils'
export default {
name: 'FilterSort',
components: { FilterCustomSort },
@ -175,7 +177,7 @@ export default {
},
computed: {
fieldIds() {
return this.element.options.attrs.fieldId || []
return this.element.options.attrs.fieldId
},
isSortWidget() {
return this.widget && this.widget.isSortWidget && this.widget.isSortWidget()
@ -195,8 +197,24 @@ export default {
watch: {
firstTableId(val, old) {
if (val !== old) {
if (this.isSortWidget && (this.sortNode?.sort === 'asc' || this.sortNode?.sort === 'desc')) {
this.sortNode = { sort: 'none' }
this.$emit('sort-change', this.sortNode)
}
this.loadFields()
}
},
fieldIds(val, old) {
if (val !== old) {
if (this.isCustomSortWidget && this.sortNode.sort === 'custom') {
const valArr = val?.length ? val.split(',') : null
const oldArr = old?.length ? old.split(',') : null
if (!isSameArr(valArr, oldArr)) {
this.sortNode = { sort: 'none' }
this.$emit('sort-change', this.sortNode)
}
}
}
}
},
@ -210,12 +228,12 @@ export default {
this.customSortList = JSON.parse(JSON.stringify(this.sortNode.list))
}
}
if (!this.sortNode) {
this.sortNode = JSON.parse(JSON.stringify(this.defaultSortProp))
}
this.loadFields()
}
if (!this.sortNode) {
this.sortNode = JSON.parse(JSON.stringify(this.defaultSortProp))
}
},
methods: {
customSortChange(list) {