tour
This commit is contained in:
RB 2021-11-10 00:31:16 +08:00 committed by GitHub
parent 6032b9e2e5
commit 80253cc974
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 297 additions and 27 deletions

1
.gitignore vendored
View file

@ -45,5 +45,4 @@ test.*
# Build
**/public/h5app/
/.deploy/build/

View file

@ -7,10 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.user.signup;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.commons.RegexUtils;
import cn.devezhao.commons.*;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.commons.web.WebUtils;
import cn.devezhao.persist4j.Record;
@ -46,10 +43,7 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* @author zhaofang123@gmail.com
@ -63,6 +57,7 @@ public class LoginController extends BaseController {
public static final String CK_AUTOLOGIN = "rb.alt";
public static final String SK_USER_THEME = "currentUseTheme";
private static final String SK_NEED_VCODE = "needLoginVCode";
private static final String SK_START_TOUR = "needStartTour";
@GetMapping("login")
public ModelAndView checkLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
@ -221,12 +216,22 @@ public class LoginController extends BaseController {
ServletUtils.removeCookie(request, response, CK_AUTOLOGIN);
}
createLoginLog(request, user);
ThreadPool.exec(() -> createLoginLog(request, user));
ServletUtils.setSessionAttribute(request, WebUtils.CURRENT_USER, user);
ServletUtils.setSessionAttribute(request, SK_USER_THEME,
KVStorage.getCustomValue("THEME." + user));
ServletUtils.setSessionAttribute(request, SK_USER_THEME, KVStorage.getCustomValue("THEME." + user));
Application.getSessionStore().storeLoginSuccessed(request);
// TODO Tour 显示规则
Object[] initLoginTime = Application.createQueryNoFilter(
"select min(loginTime) from LoginLog where user = ? and loginTime > '2021-12-31'")
.setParameter(1, user)
.unique();
int dayLeft = initLoginTime == null || initLoginTime[0] == null ? 0
: CalendarUtils.getDayLeft((Date) initLoginTime[0]);
if (dayLeft >= -30 || "true".equals(System.getProperty("ForceTour"))) { // 30d
ServletUtils.setSessionAttribute(request, SK_START_TOUR, "yes");
}
}
private void createLoginLog(HttpServletRequest request, ID user) {

View file

@ -25,7 +25,7 @@
<script th:src="@{/commons/frontjs/use-frontjs}" type="text/babel"></script>
</th:block>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script th:if="${env == 'prodution'}" async src="https://www.googletagmanager.com/gtag/js?id=UA-70670864-2"></script>
<script th:if="${env == 'prodution'}" src="https://www.googletagmanager.com/gtag/js?id=UA-70670864-2" async></script>
<script>
window.dataLayer = window.dataLayer || []
function gtag() {
@ -34,4 +34,10 @@
gtag('js', new Date())
gtag('config', 'UA-70670864-2')
</script>
<th:block th:if="${user != null && session.needStartTour != null}">
<link rel="stylesheet" type="text/css" th:href="@{/assets/lib/introjs.min.css}" />
<script th:src="@{/assets/lib/intro.min.js}"></script>
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/rebuild-tour.css}" />
<script th:src="@{/assets/js/rebuild-tour.js}"></script>
</th:block>
</th:block>

View file

@ -0,0 +1,84 @@
/*
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
body.rebuild-tour-body {
overflow: hidden !important;
}
.introjs-tooltip.rebuild-tour-tooltip {
background-color: #4285f4;
color: #fff;
border-radius: 0;
border: 0 none;
width: 280px;
min-width: 280px;
max-width: 280px;
}
.introjs-tooltip.rebuild-tour-tooltip .introjs-tooltip-header .introjs-tooltip-title {
font-size: 1.2rem;
margin: 0;
}
.introjs-tooltip.rebuild-tour-tooltip .introjs-tooltip-header .introjs-skipbutton {
color: #fff;
opacity: 0.6;
padding: 5px 0;
font-weight: normal;
font-size: 1.6rem;
}
.introjs-tooltip.rebuild-tour-tooltip .introjs-tooltip-header .introjs-skipbutton:hover {
opacity: 0.8;
}
.introjs-tooltip.rebuild-tour-tooltip .introjs-tooltiptext {
padding-bottom: 10px;
line-height: 1.6;
}
.introjs-tooltip.rebuild-tour-tooltip .introjs-arrow.left {
border-right-color: #4285f4;
}
.introjs-tooltip.rebuild-tour-tooltip .introjs-arrow.bottom {
border-top-color: #4285f4;
}
.introjs-tooltip.rebuild-tour-tooltip .introjs-arrow.top-middle,
.introjs-tooltip.rebuild-tour-tooltip .introjs-arrow.top-right,
.introjs-tooltip.rebuild-tour-tooltip .introjs-arrow.top {
border-bottom-color: #4285f4;
}
.introjs-helperLayer.rebuild-tour-highlight {
border-radius: unset !important;
box-shadow: unset !important;
}
.introjs-button {
border: 0 none !important;
font-size: 1rem;
}
.introjs-bullets ul li a {
background-color: rgba(255, 255, 255, 0.6);
}
.introjs-bullets ul li a.active,
.introjs-bullets ul li a:focus,
.introjs-bullets ul li a:hover {
background-color: rgba(255, 255, 255, 1);
}
.introjs-tooltipbuttons {
border-color: rgba(255, 255, 255, 0.2);
}
.introjs-tooltipbuttons a > i {
font: normal normal normal 17px/1 'Material-Design-Iconic-Font';
}

View file

@ -5,7 +5,7 @@ rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
/* Page of editor */
/* Editor */
.timeline.spare {
max-width: 2000px;
@ -141,7 +141,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
margin-top: 5px;
}
/* formula */
/* Formula */
.formula-calc {
position: relative;

View file

@ -20,6 +20,8 @@ $(document).ready(function () {
let dash_list = null
$.get('/dashboard/dash-gets', (res) => {
typeof window.startTour === 'function' && window.startTour(1000)
dash_list = res.data
if (!dash_list || dash_list.length === 0) {
$('.chart-grid').removeClass('invisible')
@ -67,12 +69,12 @@ $(document).ready(function () {
$('.chart-grid').addClass('uneditable')
}
$('.J_dash-new').click(() => dlgShow('DlgDashAdd'))
$('.J_dash-edit').click(() => dlgShow('DlgDashSettings', { title: d[4], shareTo: d[1] }))
$('.J_chart-new').click(() => dlgShow('DlgAddChart'))
$('.J_dash-select').click(() => dlgShow('DashSelect', { dashList: dash_list }))
$('.J_dash-new').on('click', () => dlgShow('DlgDashAdd'))
$('.J_dash-edit').on('click', () => dlgShow('DlgDashSettings', { title: d[4], shareTo: d[1] }))
$('.J_chart-new').on('click', () => dlgShow('DlgAddChart'))
$('.J_dash-select').on('click', () => dlgShow('DashSelect', { dashList: dash_list }))
$('.J_dash-refresh .dropdown-item').click(function () {
$('.J_dash-refresh .dropdown-item').on('click', function () {
const $this = $(this)
$('.J_dash-refresh .btn span').text($this.text())
refresh_timeout = ~~$this.data('time')
@ -89,7 +91,7 @@ $(document).ready(function () {
}
})
$('.J_dash-fullscreen').click(() => {
$('.J_dash-fullscreen').on('click', () => {
const $body = $(document.body)
if ($body.hasClass('fullscreen')) exitFullscreen()
else fullScreen()
@ -98,7 +100,7 @@ $(document).ready(function () {
})
let dlgChartSelect
$('.J_chart-select').click(() => {
$('.J_chart-select').on('click', () => {
const appended = []
$('.grid-stack-item-content').each(function () {
appended.push($(this).attr('id').substr(6))
@ -202,7 +204,7 @@ const render_dashboard = function (init) {
if (rendered_charts.length === 0) {
const gsi = `<div class="grid-stack-item"><div id="chart-add" class="grid-stack-item-content"><a class="chart-add"><i class="zmdi zmdi-plus"></i><p>${$L('添加图表')}</p></a></div></div>`
const $gsi = gridstack.addWidget(gsi, 0, 0, 2, 2)
$gsi.find('a').click(() => {
$gsi.find('a').on('click', () => {
if ($('.J_chart-new').length === 0) $('.J_chart-select').trigger('click')
else dlgShow('DlgAddChart')
})
@ -233,7 +235,7 @@ const add_widget = function (item) {
const chart_add = $('#chart-add')
if (chart_add.length > 0) gridstack.removeWidget(chart_add.parent())
const gsi = '<div class="grid-stack-item"><div id="' + chid + '" class="grid-stack-item-content"></div></div>'
const gsi = `<div class="grid-stack-item"><div id="${chid}" class="grid-stack-item-content"></div></div>`
// Use gridstar
if (item.size_x || item.size_y) {
gridstack.addWidget(gsi, (item.col || 1) - 1, (item.row || 1) - 1, item.size_x || 2, item.size_y || 2, 2, 12, 2, 12)
@ -267,7 +269,7 @@ const save_dashboard = function () {
gridstack_serialize = s
$setTimeout(
() => {
$.post('/dashboard/dash-config?id=' + dashid, JSON.stringify(gridstack_serialize), () => {
$.post(`/dashboard/dash-config?id=${dashid}`, JSON.stringify(gridstack_serialize), () => {
if (rb.env === 'dev') console.log('Saved dashboard : ' + JSON.stringify(gridstack_serialize))
})
},
@ -320,7 +322,7 @@ class DlgAddChart extends RbFormHandler {
next() {
const e = this.__select2.val()
if (!e) return
location.href = rb.baseUrl + '/dashboard/chart-design?source=' + e + '&dashid=' + this.props.dashid
location.href = `${rb.baseUrl}/dashboard/chart-design?source=${e}&dashid=${this.props.dashid}`
}
}
@ -394,7 +396,7 @@ class DlgDashSettings extends RbFormHandler {
confirmText: $L('删除'),
confirm: function () {
this.disabled(true)
$.post('/app/entity/common-delete?id=' + dashid, function (res) {
$.post(`/app/entity/common-delete?id=${dashid}`, function (res) {
// if (res.error_code === 0) location.replace('home#del=' + dashid) // Chrome no refresh?
if (res.error_code === 0) location.reload()
else RbHighbar.error(res.error_msg)

View file

@ -0,0 +1,162 @@
/*
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
/* global introJs */
window.startTour = function (delay) {
if ($(window).width() < 1000) return
setTimeout(startTour123, delay || 100)
}
const startTour123 = function () {
let stepName
let steps
if (location.href.includes('/dashboard/home')) {
stepName = 'TourEnd-Dashboard'
steps = StepRebuild()
StepDashboard().forEach((item) => steps.push(item))
}
if (!steps) return
const isEnd = $storage.get(stepName)
if (isEnd) return // 已经展示完
const stepsObj = []
steps.forEach((item) => {
const $el = item.element ? $(item.element) : [null]
if ($el.length > 0) stepsObj.push({ ...item, element: $el[0] })
})
if (stepsObj.length === 0) return
// 隐藏滚动条
$(document.body).addClass('rebuild-tour-body')
const $introJs = introJs()
.setOptions({
steps: stepsObj,
overlayOpacity: 0,
disableInteraction: true,
exitOnOverlayClick: false,
exitOnEsc: false,
scrollToElement: false,
tooltipClass: 'rebuild-tour-tooltip',
highlightClass: 'rebuild-tour-highlight',
prevLabel: '<i class="zmdi zmdi-arrow-left"></i>',
nextLabel: '<i class="zmdi zmdi-arrow-right"></i>',
doneLabel: $L('完成'),
})
.onchange((target) => {
const $target = $(target)
let stepIndex = -1
for (let i = 1; i < steps.length; i++) {
if ($target.hasClass(steps[i].element.substr(1))) {
stepIndex = i
break
}
}
if (stepIndex < 0) return
// hack: 位置更新
$('.rebuild-tour-highlight').css('box-shadow', 'none')
const pos = { margin: 0 }
const s = steps[stepIndex]
if (s && s.rbLeft) pos.marginLeft = s.rbLeft
else if (s && s.rbRight) pos.marginRight = s.rbRight
if (s && s.rbTop) pos.marginTop = s.rbTop
else if (s && s.rbBottom) pos.marginBottom = s.rbBottom
setTimeout(() => $('.rebuild-tour-tooltip').css(pos), 360)
// $introJs.refresh()
})
.oncomplete(() => {
$storage.set(stepName, 'yes')
})
.onexit(() => {
$(document.body).removeClass('rebuild-tour-body')
})
// $introJs.goToStep(1)
$introJs.start()
}
// ~~ 向导步骤
const StepRebuild = () => {
return [
{
title: $L('欢迎使用'),
intro: $L('本向导将带你了解系统的基本功能使用让我们开始吧'),
},
{
element: '.rb-left-sidebar',
title: $L('导航菜单'),
intro: $L('使用导航菜单可以在各个功能模块之间切换'),
rbLeft: -10,
rbTop: 16,
},
{
element: '.nav-settings',
title: $L('导航菜单设置'),
intro: $L('点击此处进行个性化导航菜单设置'),
rbLeft: -10,
rbBottom: -10,
},
{
element: '.global-search',
title: $L('全局搜索'),
intro: $L('全局搜索可以帮助你快速查询需要的数据'),
rbLeft: 5,
},
{
element: '.global-create',
title: $L('快速新建'),
intro: $L('点击此处快速新建业务记录'),
rbLeft: 8,
},
{
element: '.admin-settings',
title: $L('管理中心'),
intro: $L('REBUILD 拥有强大的配置管理中心你可以根据需求自由搭建系统'),
rbLeft: 5,
},
{
element: '.page-help',
title: $L('帮助中心'),
intro: $L('使用遇到问题可以查阅帮助文档你也可以通过阅读文档 GET 更多技能'),
rbLeft: 5,
},
{
element: '.J_top-notifications',
title: $L('通知'),
intro: $L('与你相关的通知消息都在这里'),
rbRight: 9,
},
{
element: '.J_top-user',
title: $L('个人设置'),
intro: $L('点击此处设置你的个人信息或选择界面主题等'),
rbRight: 11,
},
]
}
const StepDashboard = () => {
return [
{
element: '.dash-head',
title: $L('切换仪表盘'),
intro: $L('点击此处切换仪表盘显示或进行设置/新增仪表盘'),
rbTop: -6,
},
{
element: '.J_chart-adds',
title: $L('添加图表'),
intro: $L('点击此处为当前仪表盘添加图表'),
rbTop: -1,
rbRight: 21,
},
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long