fix(新手任务): 增加新手任务成功页面,修复新手引导流程异常问题,调整新手任务权限,优化样式

This commit is contained in:
lan-yonghui 2023-04-06 11:14:43 +08:00 committed by 刘瑞斌
parent eaf42a313e
commit c799050fd5
14 changed files with 382 additions and 84 deletions

View File

@ -1,5 +1,4 @@
import {get, post} from "../plugins/request" import {get, post} from "../plugins/request"
import {getCurrentUserId} from "../utils/token";
import {TASK_DATA} from "../utils/constants"; import {TASK_DATA} from "../utils/constants";
@ -15,11 +14,6 @@ export function saveTask(data) {
return post(`/novice/save/task`,{'dataOption': JSON.stringify(data)}); return post(`/novice/save/task`,{'dataOption': JSON.stringify(data)});
} }
export function updateUserByResourceId(resourceId) {
let userId = getCurrentUserId();
return get(`/user/update/current-by-resource/${resourceId}`);
}
export function initTaskData(url){ export function initTaskData(url){
getSideTask().then(res=>{ getSideTask().then(res=>{
let taskData = TASK_DATA let taskData = TASK_DATA

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,6 +1,6 @@
@import '~shepherd.js/dist/css/shepherd.css'; @import '~shepherd.js/dist/css/shepherd.css';
.shepherd-has-title.shepherd-element { .shepherd-has-title.shepherd-element {
width: 220px !important; width: 220px;
} }
.custom-width { .custom-width {
width: 320px !important; width: 320px !important;

View File

@ -62,16 +62,23 @@ export default {
localStorage.setItem('step', res.data[0].guideStep) localStorage.setItem('step', res.data[0].guideStep)
} else { } else {
localStorage.setItem('guide','0') localStorage.setItem('guide','0')
localStorage.removeItem('step')
} }
let microApps = JSON.parse(sessionStorage.getItem("micro_apps")); let microApps = JSON.parse(sessionStorage.getItem("micro_apps"));
if(localStorage.getItem("guide") === '0' && microApps && microApps['project']) { if(localStorage.getItem("guide") === '0' && microApps && microApps['project']) {
localStorage.setItem("step", '1') let step = localStorage.getItem("step") ? localStorage.getItem("step") : "1"
this.initStep() localStorage.setItem("step", step)
if(step !== '3'){
if(this.$route.path.includes('/project/home')){
this.initStepAll()
}else{
this.initStep()
}
}
} }
}) })
}, },
initStep() { initStepAll() {
const _this = this const _this = this
_this.$nextTick(() => { _this.$nextTick(() => {
const tour = _this.$shepherd({ const tour = _this.$shepherd({
@ -216,11 +223,84 @@ export default {
tour.start() tour.start()
}) })
}, },
initStep() {
const _this = this
_this.$nextTick(() => {
const tour = _this.$shepherd({
useModalOverlay: true,
exitOnEsc: false,
keyboardNavigation: false,
defaultStepOptions: {
scrollTo: {
behavior: 'smooth',
block: 'center'
},
canClickTarget: false,
//
modalOverlayOpeningPadding: 0,
//
modalOverlayOpeningRadius: 4
}
})
tour.addSteps([
{
attachTo: {
element: document.querySelector('.shepherd-workspace'),
on: 'bottom-start'
},
buttons: [
{
action: function() {
_this.$refs.introduction.resVisible = localStorage.getItem("step") > 1
return _this.gotoCancel(this, true)
},
classes: 'close-btn',
text: _this.$t("shepherd.exit")
},
{
action: function() {
return _this.gotoNext(this, null, 2)
},
classes: 'shep-btn',
text: _this.$t("shepherd.next")
}
],
title: _this.$t("shepherd.step1.title"),
text: _this.$t("shepherd.step1.text")
},
{
attachTo: {
element: document.querySelector('.shepherd-menu'),
on: 'right'
},
buttons: [
{
action: function() {
_this.$refs.introduction.resVisible = localStorage.getItem("step") > 1
return _this.gotoCancel(this, true)
},
classes: 'close-btn',
text: _this.$t("shepherd.exit")
},
{
action: function() {
return _this.gotoNext(this, '/project/home', 3)
},
classes: 'shep-btn',
text: _this.$t("shepherd.next")
}
],
title: _this.$t("shepherd.step2.title"),
text: _this.$t("shepherd.step2.text")
},
])
tour.start()
})
},
skipOpen(path){ skipOpen(path){
if(path){ if(path){
this.$refs.sideMenu.skipOpen(path); this.$refs.sideMenu.skipOpen(path);
} }
} }
} }
}; };

View File

@ -119,7 +119,6 @@ export default {
if(redirectUrl.includes("track")){ if(redirectUrl.includes("track")){
this.$emit("skipOpen", "/track/case/all") this.$emit("skipOpen", "/track/case/all")
this.$router.push("/track/case/all") this.$router.push("/track/case/all")
// this.$router.go(0);
}else{ }else{
this.$router.push({ this.$router.push({
path: '/track/case/all', path: '/track/case/all',

View File

@ -17,6 +17,7 @@
import MsSiteTask from "../../components/sidemenu/components/SiteTask"; import MsSiteTask from "../../components/sidemenu/components/SiteTask";
import {getSideTask} from "../../api/novice"; import {getSideTask} from "../../api/novice";
import {TASK_DATA} from "../../utils/constants"; import {TASK_DATA} from "../../utils/constants";
import {hasLicense, hasPermissions} from "../../utils/permission";
export default { export default {
@ -46,7 +47,8 @@ export default {
let num = 0 let num = 0
let total = 0 let total = 0
this.taskData.forEach(item =>{ this.taskData.forEach(item =>{
if(!(microApp && microApp[item.name])){ if(!(microApp && microApp[item.name]) || (item.name === 'ui' && !hasLicense()) ||
!hasPermissions(...item.permission)){
item.status = -1 item.status = -1
total++ total++
} else { } else {
@ -95,7 +97,7 @@ export default {
.parentBox .contentsBox div { .parentBox .contentsBox div {
transition: all 1s; transition: all 1s;
position: fixed; position: fixed;
right: 0; right: 16px;
width: 27px; width: 27px;
border-radius: 50px; border-radius: 50px;
background-color: #783787; background-color: #783787;
@ -113,7 +115,7 @@ export default {
margin-left: 10px; margin-left: 10px;
} }
.parentBox .contentsBox div:nth-child(1) { .parentBox .contentsBox div:nth-child(1) {
bottom: 100px; bottom: 125px;
} }
.parentBox .contentsBox div:hover { .parentBox .contentsBox div:hover {
right: 0; right: 0;

View File

@ -1,13 +1,52 @@
<template> <template>
<div> <div>
<div class="csat-popup" v-if="cardVisible"> <div class="csat-popup over" v-if="cardVisible && taskInfo.length === completeNum">
<el-card class="box-card">
<div slot="header" class="clearfix over-header">
<span style="float: right; padding: 5px 0;" class="moon" @click="open()">
<font-awesome-icon :icon="['fa', 'times']" class="icon"/>
</span>
<img src="/assets/guide/talent-requirements.png" class="image" alt="MS">
</div>
<div class="text" :style="language === 'en-US' ? 'text-align: center;height: 200px' :
'text-align: center;height: 165px'">
<span class="title" >
<img src="../../../assets/guide/flower.png" alt="MS">
{{ $t("side_task.over.title") }}
</span>
<p class="text">
<img v-for="num in completeNum" src="../../../assets/guide/moon-dark.png" class="over-moon" alt="MS" :key="num
+ 'd'">
{{ $t("side_task.over.subtitle") }}
</p>
<p class="text">{{ $t("side_task.over.desc") }}</p>
<p class="desc">
<a href="https://blog.fit2cloud.com/categories/metersphere" target="_blank">
{{ $t("side_task.over.blog_url") }}
</a>
</p>
<p class="desc">
<a href="https://space.bilibili.com/510493147/channel/collectiondetail?sid=40439" target="_blank">
{{ $t("side_task.over.live_url") }}
</a>
</p>
</div>
<div class="footer">
<el-button style="float: right; padding: 15px 0;color:#8C8C8C" type="text" @click="skip()">
{{$t('side_task.skip')}}
</el-button>
</div>
</el-card>
</div>
<div class="csat-popup" v-else-if="cardVisible && taskInfo.length > 0">
<template v-for="(item,index) in taskInfo" > <template v-for="(item,index) in taskInfo" >
<el-card :key="item.id" v-if="(index + 1) === taskIndex" class="box-card" > <el-card :key="item.id" v-if="(index + 1) === taskIndex && checkPermissions(item.permission)"
class="box-card" >
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
<span class="text-header">{{$t(item.title)}}</span>
<el-button style="float: right; padding: 5px 0;" class="moon" type="text" @click="open()"> <el-button style="float: right; padding: 5px 0;" class="moon" type="text" @click="open()">
<font-awesome-icon :icon="['fa', 'chevron-down']" class="icon"/> <font-awesome-icon :icon="['fa', 'chevron-down']" class="icon"/>
</el-button> </el-button>
<span class="text-header" v-html="$t(item.title)" />
<el-button style="float: right; padding: 3px 0;margin-right: 25px" type="text" > <el-button style="float: right; padding: 3px 0;margin-right: 25px" type="text" >
<img v-for="num in completeNum" src="../../../assets/guide/moon-dark.png" <img v-for="num in completeNum" src="../../../assets/guide/moon-dark.png"
class="moon" alt="MS" :key="num + 'd'"> class="moon" alt="MS" :key="num + 'd'">
@ -16,11 +55,13 @@
<img v-for="num in incompleteNum" src="../../../assets/guide/moon.png" <img v-for="num in incompleteNum" src="../../../assets/guide/moon.png"
class="moon" alt="MS" :key="num"> class="moon" alt="MS" :key="num">
</el-button> </el-button>
<el-progress :percentage="item.percentage" color="#783787" class="progress-card"></el-progress>
<el-progress :percentage="item.percentage" color="#783787"
:class="language === 'en-US' ? 'progress-card-en' : 'progress-card-zh'"></el-progress>
</div> </div>
<div style="height: 220px"> <div style="height: 220px">
<template v-for="(val,i) in item.taskData"> <template v-for="(val,i) in item.taskData">
<div class="text item" v-permission="val.permission" :key="i"> <div class="text item" v-if="checkPermissions(val.permission)" :key="i">
<p v-if="val.status === 1"> <p v-if="val.status === 1">
<font-awesome-icon :icon="['far', 'check-circle']" style="color:#783887" /> <font-awesome-icon :icon="['far', 'check-circle']" style="color:#783887" />
<label> {{$t(val.name)}}</label> <label> {{$t(val.name)}}</label>
@ -40,13 +81,14 @@
<el-button v-if="taskIndex > 1" style="float: right;margin-left: 10px; padding: 15px 0" type="text" @click="prev()"> <el-button v-if="taskIndex > 1" style="float: right;margin-left: 10px; padding: 15px 0" type="text" @click="prev()">
{{$t('side_task.prev')}} {{$t('side_task.prev')}}
</el-button> </el-button>
<el-button style="float: right; padding: 15px 0;color:#8C8C8C" type="text" @click="skip()"> <el-button style="float: left; padding: 15px 0;color:#8C8C8C" type="text" @click="skip()">
{{$t('side_task.skip')}} {{$t('side_task.skip')}}
</el-button> </el-button>
</div> </div>
</el-card> </el-card>
</template> </template>
</div> </div>
<div class="csat-popup-gif" v-if="gifVisible"> <div class="csat-popup-gif" v-if="gifVisible">
<el-card class="box-card"> <el-card class="box-card">
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
@ -54,17 +96,18 @@
<font-awesome-icon :icon="['fa', 'times']" class="icon"/> <font-awesome-icon :icon="['fa', 'times']" class="icon"/>
</span> </span>
</div> </div>
<div class="text" style="text-align: center;height: 210px"> <div class="text" :style="language === 'en-US' ? 'text-align: center;height: 230px' :
'text-align: center;height: 216px'">
<el-image <el-image
style="width: 340px;border-radius: 4px;" style="width: 340px;border-radius: 8px;"
:src="gifData.url" :src="gifData.url"
:preview-src-list="[gifData.url]" lazy> :preview-src-list="[gifData.url]" lazy>
<div slot="placeholder" class="image-slot"> <div slot="placeholder" class="image-slot">
加载中<span class="dot">...</span> loading<span class="dot">...</span>
</div> </div>
</el-image> </el-image>
</div> </div>
<div class="gif-footer"> <div :class="language === 'en-US' ? 'gif-footer-en' : 'gif-footer'">
<el-button type="primary" round size="small" class="is-plain" @click="gotoPath(gifData.path)"> <el-button type="primary" round size="small" class="is-plain" @click="gotoPath(gifData.path)">
{{$t(gifData.name)}} {{$t(gifData.name)}}
</el-button> </el-button>
@ -75,9 +118,7 @@
</template> </template>
<script> <script>
import {hasLicense} from "../../../utils/permission"; import {hasPermissions} from "../../../utils/permission";
import {TASK_DATA, TASK_MODULE} from "../../../utils/constants";
import {getSideTask} from "../../../api/novice";
export default { export default {
name: "SiteTask", name: "SiteTask",
@ -95,7 +136,8 @@ export default {
completeNum: 0, completeNum: 0,
ongoingNum: 0, ongoingNum: 0,
incompleteNum: 0, incompleteNum: 0,
totalNum:0, totalNum: 0,
language: localStorage.getItem('language'),
status: this.$route.query.status status: this.$route.query.status
} }
}, },
@ -104,8 +146,6 @@ export default {
}, },
created() { created() {
console.log("this.status")
console.log(this.status)
if(this.status){ if(this.status){
this.skipOpen("/track/case/all") this.skipOpen("/track/case/all")
} }
@ -116,7 +156,7 @@ export default {
let completeNum = 0 let completeNum = 0
let ongoingNum = 0 let ongoingNum = 0
this.taskInfo = [] this.taskInfo = []
// status -1 0 1 2 // status -1 访 0 1 2
this.taskData.forEach(item=>{ this.taskData.forEach(item=>{
if(item.status === 1){ if(item.status === 1){
completeNum++; completeNum++;
@ -177,17 +217,30 @@ export default {
}) })
}) })
} }
},
checkPermissions(permission) {
return hasPermissions(...permission);
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.title {
font-size: 24px;
font-weight: 500;
}
.text { .text {
font-size: 16px; font-size: 16px;
font-weight: 300; font-weight: 300;
} }
.desc {
color:#783887;
text-align: left;
margin: 3px 6px;
font-size: 12px;
font-weight: 300;
}
.item { .item {
margin-bottom: 10px; margin-bottom: 10px;
@ -223,7 +276,12 @@ export default {
color:#783887; color:#783887;
} }
.progress-card { .progress-card-en {
margin-top: 10px;
margin-right: 20px;
}
.progress-card-zh {
margin-top: 10px; margin-top: 10px;
margin-right: 40px; margin-right: 40px;
} }
@ -239,6 +297,12 @@ export default {
text-align: center; text-align: center;
} }
.gif-footer-en {
width: 100%;
margin: 10px 0 44px;
text-align: center;
}
.clearfix:before, .clearfix:before,
.clearfix:after { .clearfix:after {
display: table; display: table;
@ -255,7 +319,7 @@ export default {
.csat-popup { .csat-popup {
position: fixed; position: fixed;
right: 16px; right: 16px;
bottom: 160px; bottom: 170px;
width: 400px; width: 400px;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
@ -271,7 +335,7 @@ export default {
.csat-popup-gif { .csat-popup-gif {
position: fixed; position: fixed;
right: 426px; right: 426px;
bottom: 160px; bottom: 170px;
width: 400px; width: 400px;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
@ -293,10 +357,27 @@ export default {
height: 20px; height: 20px;
} }
.circle { .over-moon {
vertical-align: bottom;
width: 12px; width: 12px;
height: 12px; height: 12px;
margin-right: 5px; }
.image {
width: 160px;
height: 120px;
}
.over-header {
text-align: center;
}
::v-deep .over .el-card {
border-radius: 8px;
background-image: linear-gradient(to bottom, #f4f4f4 44%, #FFF 0);
}
::v-deep .el-card__body {
padding: 0 10px;
} }
::v-deep .csat-popup .el-card__header { ::v-deep .csat-popup .el-card__header {
@ -304,6 +385,11 @@ export default {
padding: 20px 24px 10px 24px; padding: 20px 24px 10px 24px;
} }
::v-deep .over .el-card__header {
border-bottom: none;
padding: 20px 24px 0px 24px;
}
::v-deep .csat-popup-gif .el-card__header { ::v-deep .csat-popup-gif .el-card__header {
border-bottom: none; border-bottom: none;
padding: 20px 20px 10px 24px; padding: 20px 20px 10px 24px;

View File

@ -3575,8 +3575,8 @@ const message = {
button: 'Next: UI Test' button: 'Next: UI Test'
}, },
ui: { ui: {
title: 'Portable UI element library and instruction set', title: 'Portable UI element library and command set',
desc: '<span>Arrange scenario cases based on reusable element libraries and instructions;</span><br> combine your commonly used test steps into new instructions, <br> <span> which can be flexibly called in automation scenarios. </span>', desc: '<span>Arrange scenario cases based on reusable element library and commands;</span><br> combine your commonly used test steps into new command, <br> <span> which can be flexibly called in automation scenarios. </span>',
button: 'Next: Performance Test' button: 'Next: Performance Test'
}, },
performance: { performance: {
@ -3588,7 +3588,7 @@ const message = {
}, },
side_task: { side_task: {
test_tracking: { test_tracking: {
title: "Challenging Test Track", title: "<span>Challenging</span><br><span>Test Track</span>",
task_1: "Join a project", task_1: "Join a project",
task_2: "Create a functional test case", task_2: "Create a functional test case",
task_3: "Create a review plan", task_3: "Create a review plan",
@ -3598,36 +3598,43 @@ const message = {
task_7: "Add ralated issue to test case", task_7: "Add ralated issue to test case",
}, },
api_test: { api_test: {
title: "Challenging API Test", title: "<span>Challenging</span><br><span>API Test</span>",
task_1: "Create an API definition", task_1: "Create an API definition",
task_2: "Import local API definition or API cases", task_2: "Import local API definition or API cases",
task_3: "Execute an API testing", task_3: "Execute an API testing",
task_4: "Create a new test case based on API testing", task_4: "Create a new test case based on API testing",
task_5: "Share API documents", task_5: "Share API documents",
task_6: "Create an automation scenario case", task_6: "Create an automation scenario case",
task_7: "Execute automated API testing with scheduled task ", task_7: "Execute automated API testing with scheduled task",
}, },
performance_test: { performance_test: {
title: "Challenging Performance Test", title: "<span>Challenging</span><br><span>Performance Test</span>",
task_1: "Convert API scenario case into performance testing", task_1: "Convert API scenario case into performance testing",
task_2: "Share performance testing report", task_2: "Share performance testing report",
}, },
project_setting: { project_setting: {
title: "Challenging Project Settings", title: "<span>Challenging</span><br><span>Project Settings</span>",
task_1: "create a new project", task_1: "Create a new project",
task_2: "Add a project member", task_2: "Add a project member",
task_3: "Create a project environment", task_3: "Create a project environment",
}, },
ui_test: { ui_test: {
title: "Challenging UI Test", title: "<span>Challenging</span><br><span>UI Test</span>",
task_1: "Create an element", task_1: "Create an element",
task_2: "Create an automated UI scenario case", task_2: "Create an automated UI scenario case",
task_3: "Execute an automated UI scenario case", task_3: "Execute an automated UI scenario case",
}, },
next: "Next", next: "Next",
prev: "Previous", prev: "Previous",
skip: "Skip", skip: "Skip",
novice_task: "Novice Task" novice_task: "Novice Task",
over: {
title: "Congratulations!",
subtitle: "You have completed all the novice journey, full of energy",
desc: "If you want to continue to learn about advanced tutorials, please follow our technical blog and live channel",
blog_url: "Technical Blog",
live_url: "Live Channel"
}
} }
}; };

View File

@ -3498,7 +3498,14 @@ const message = {
next: "下一章", next: "下一章",
prev: "上一章", prev: "上一章",
skip: "跳过", skip: "跳过",
novice_task: "新手旅程" novice_task: "新手旅程",
over: {
title: "恭喜通关!",
subtitle: "您已完成全部新手旅程 能量满载~",
desc: "想继续了解进阶教程,请关注我们的技术博客和直播",
blog_url: "博客地址",
live_url: "直播间地址"
}
} }
}; };

View File

@ -3498,7 +3498,14 @@ const message = {
next: "下一章", next: "下一章",
prev: "上一章", prev: "上一章",
skip: "跳過", skip: "跳過",
novice_task: "新手旅程" novice_task: "新手旅程",
over: {
title: "恭喜通關!",
subtitle: "您已完成全部新手旅程 能量滿載~",
desc: "想繼續了解進階教程,請關注我們的技術博客和直播",
blog_url: "博客地址",
live_url: "直播間地址"
}
} }
}; };

View File

@ -267,23 +267,24 @@ export const SECOND_LEVEL_ROUTE_PERMISSION_MAP = {
export const TASK_PATH = [ export const TASK_PATH = [
"/test/case/add", "/test/case/add",
"/test/case/review/save", "/test/case/review/save",
"/test/case/review/comment/save", "/test/case/comment/save",
"/test/plan/add", "/test/plan/add",
"/test/plan/relevance", "/test/plan/relevance",
"/issues/add", "issues/add",
"test/case/issues/relate",
"/api/definition/create", "/api/definition/create",
"/api/definition/run/debug", "/api/definition/run/debug",
"/api/testcase/create", "/api/testcase/create",
"/share/info/generateApiDocumentShareInfo", "/share/generate/api/document",
"/api/definition/import", "/api/definition/import",
"/api/automation/create", "/api/automation/create",
"/api/automation/schedule/update", "/api/automation/schedule/create",
"/performance/save", "/performance/save",
"/share/info/generateShareInfoWithExpired", "/share/generate/expired",
"/project/add", "/project/add",
"/project/member/add", "/project/member/add",
"/user/project/member/add", "/setting/user/project/member/add",
"/api/environment/add", "/environment/add",
"/ui/element/add", "/ui/element/add",
"/ui/automation/create", "/ui/automation/create",
"/ui/automation/run/debug", "/ui/automation/run/debug",
@ -295,14 +296,15 @@ export const TASK_DATA = [
name: "track", name: "track",
title: "side_task.test_tracking.title", title: "side_task.test_tracking.title",
percentage: 14, percentage: 14,
permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ','PROJECT_TRACK_CASE:READ+CREATE','PROJECT_TRACK_REVIEW:READ+CREATE','PROJECT_TRACK_REVIEW:READ+COMMENT','PROJECT_TRACK_PLAN:READ+CREATE','PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL','PROJECT_TRACK_ISSUE:READ+CREATE','PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'],
taskData: [ taskData: [
{ id: 1, name: "side_task.test_tracking.task_1", status: 1, permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ'], api: [''], path: '/setting/project/:type', url: "" }, { id: 1, name: "side_task.test_tracking.task_1", status: 1, permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ'], api: [''], path: '/setting/project/:type', url: "" },
{ id: 2, name: "side_task.test_tracking.task_2", status: 0, permission: ['PROJECT_TRACK_CASE:READ+CREATE'], api: ["/test/case/add"], path: '/track/case/all', url: "/assets/guide/track/task-2.gif" }, { id: 2, name: "side_task.test_tracking.task_2", status: 0, permission: ['PROJECT_TRACK_CASE:READ+CREATE'], api: ["/test/case/add"], path: '/track/case/all', url: "/assets/guide/track/task-2.gif" },
{ id: 3, name: "side_task.test_tracking.task_3", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+CREATE'], api: ["/test/case/review/save"], path: '/track/review/all', url: "/assets/guide/track/task-3.gif" }, { id: 3, name: "side_task.test_tracking.task_3", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+CREATE'], api: ["/test/case/review/save"], path: '/track/review/all', url: "/assets/guide/track/task-3.gif" },
{ id: 4, name: "side_task.test_tracking.task_4", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+COMMENT'], api: ["/test/case/review/comment/save"], path: '/track/review/all', url: "/assets/guide/track/task-4.gif" }, { id: 4, name: "side_task.test_tracking.task_4", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+COMMENT'], api: ["/test/case/comment/save"], path: '/track/review/all', url: "/assets/guide/track/task-4.gif" },
{ id: 5, name: "side_task.test_tracking.task_5", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+CREATE'], api: ["/test/plan/add"], path: '/track/plan/all', url: "/assets/guide/track/task-5.gif" }, { id: 5, name: "side_task.test_tracking.task_5", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+CREATE'], api: ["/test/plan/add"], path: '/track/plan/all', url: "/assets/guide/track/task-5.gif" },
{ id: 6, name: "side_task.test_tracking.task_6", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL'], api: ["/test/plan/relevance"], path: '/track/plan/all', url: "/assets/guide/track/task-6.gif" }, { id: 6, name: "side_task.test_tracking.task_6", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL'], api: ["/test/plan/relevance"], path: '/track/plan/all', url: "/assets/guide/track/task-6.gif" },
{ id: 7, name: "side_task.test_tracking.task_7", status: 0, permission: ['PROJECT_TRACK_ISSUE:READ+CREATE'], api: ["/issues/add"], path: '/track/issue', url: "/assets/guide/track/task-7.gif" }, { id: 7, name: "side_task.test_tracking.task_7", status: 0, permission: ['PROJECT_TRACK_ISSUE:READ+CREATE','PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'], api: ["issues/add","test/case/issues/relate"], path: '/track/issue', url: "/assets/guide/track/task-7.gif" },
], ],
rate: 1, rate: 1,
status: 0 status: 0
@ -312,14 +314,15 @@ export const TASK_DATA = [
name: "api", name: "api",
title: 'side_task.api_test.title', title: 'side_task.api_test.title',
percentage: 0, percentage: 0,
permission: ['PROJECT_API_DEFINITION:READ+CREATE_API','PROJECT_API_DEFINITION:READ+IMPORT_API','PROJECT_API_DEFINITION:READ+DEBUG','PROJECT_API_DEFINITION:READ+CREATE_CASE','PROJECT_API_DEFINITION:READ','PROJECT_API_SCENARIO:READ+CREATE','PROJECT_API_SCENARIO:READ+SCHEDULE'],
taskData: [ taskData: [
{id: 1, name: "side_task.api_test.task_1", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_API'], api: ["/api/definition/create"], url: "/assets/guide/api/task-1.gif" }, {id: 1, name: "side_task.api_test.task_1", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_API'], api: ["/api/definition/create"], url: "/assets/guide/api/task-1.gif" },
{id: 2, name: "side_task.api_test.task_2", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+IMPORT_API'], api: ["/api/definition/import"], url: "/assets/guide/api/task-2.gif" }, {id: 2, name: "side_task.api_test.task_2", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+IMPORT_API'], api: ["/api/definition/import"], url: "/assets/guide/api/task-2.gif" },
{id: 3, name: "side_task.api_test.task_3", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+DEBUG'], api: ["/api/definition/run/debug"], url: "/assets/guide/api/task-3.gif" }, {id: 3, name: "side_task.api_test.task_3", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+DEBUG'], api: ["/api/definition/run/debug"], url: "/assets/guide/api/task-3.gif" },
{id: 4, name: "side_task.api_test.task_4", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_CASE'], api: ["/api/testcase/create"], url: "/assets/guide/api/task-4.gif" }, {id: 4, name: "side_task.api_test.task_4", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_CASE'], api: ["/api/testcase/create"], url: "/assets/guide/api/task-4.gif" },
{id: 5, name: "side_task.api_test.task_5", status: 0, path: '/api/automation', permission: ['PROJECT_API_DEFINITION:READ'], api: ["/share/info/generateApiDocumentShareInfo"], url: "/assets/guide/api/task-5.gif" }, {id: 5, name: "side_task.api_test.task_5", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ'], api: ["/share/generate/api/document"], url: "/assets/guide/api/task-5.gif" },
{id: 6, name: "side_task.api_test.task_6", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+CREATE'], api: ["/api/automation/create"], url: "/assets/guide/api/task-6.gif" }, {id: 6, name: "side_task.api_test.task_6", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+CREATE'], api: ["/api/automation/create"], url: "/assets/guide/api/task-6.gif" },
{id: 7, name: "side_task.api_test.task_7", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+SCHEDULE'], api: ["/api/automation/schedule/update"], url: "/assets/guide/api/task-7.gif" }, {id: 7, name: "side_task.api_test.task_7", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+SCHEDULE'], api: ["/api/automation/schedule/create"], url: "/assets/guide/api/task-7.gif" },
], ],
rate: 0, rate: 0,
status: 0 status: 0
@ -329,9 +332,10 @@ export const TASK_DATA = [
name: "performance", name: "performance",
title: 'side_task.performance_test.title', title: 'side_task.performance_test.title',
percentage: 0, percentage: 0,
permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE',"PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH",'PROJECT_PERFORMANCE_REPORT:READ'],
taskData: [ taskData: [
{id: 1, name: 'side_task.performance_test.task_1', status: 0, path: '/performance/test/all', permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE',"PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH"], api: ["/performance/save"], url: "/assets/guide/performance/task-1.gif" }, {id: 1, name: 'side_task.performance_test.task_1', status: 0, path: '/performance/test/all', permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE',"PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH"], api: ["/performance/save"], url: "/assets/guide/performance/task-1.gif" },
{id: 2, name: 'side_task.performance_test.task_2', status: 0, path: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ'], api: ["/share/info/generateShareInfoWithExpired"], url: "/assets/guide/performance/task-2.gif" }, {id: 2, name: 'side_task.performance_test.task_2', status: 0, path: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ'], api: ["/share/generate/expired"], url: "/assets/guide/performance/task-2.gif" },
], ],
rate: 0, rate: 0,
status: 0 status: 0
@ -341,10 +345,11 @@ export const TASK_DATA = [
name: "project", name: "project",
title: 'side_task.project_setting.title', title: 'side_task.project_setting.title',
percentage: 0, percentage: 0,
permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE','PROJECT_USER:READ+CREATE','PROJECT_ENVIRONMENT:READ+CREATE'],
taskData: [ taskData: [
{id: 1, name: 'side_task.project_setting.task_1', status: 0, permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE'], api: ["/project/add"], path: '/setting/project/:type', url: "/assets/guide/project/task-1.gif" }, {id: 1, name: 'side_task.project_setting.task_1', status: 0, permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE'], api: ["/project/add"], path: '/setting/project/:type', url: "/assets/guide/project/task-1.gif" },
{id: 2, name: 'side_task.project_setting.task_2', status: 0, permission: ['PROJECT_USER:READ+CREATE'], api: ["/project/member/add","/user/project/member/add"], path: '/project/member', url: "/assets/guide/project/task-2.gif" }, {id: 2, name: 'side_task.project_setting.task_2', status: 0, permission: ['PROJECT_USER:READ+CREATE'], api: ["/project/member/add","/setting/user/project/member/add"], path: '/project/member', url: "/assets/guide/project/task-2.gif" },
{id: 3, name: 'side_task.project_setting.task_3', status: 0, permission: ['PROJECT_ENVIRONMENT:READ+CREATE'], api: ["/api/environment/add"], path: '/project/env', url: "/assets/guide/project/task-3.gif" }, {id: 3, name: 'side_task.project_setting.task_3', status: 0, permission: ['PROJECT_ENVIRONMENT:READ+CREATE'], api: ["/environment/add"], path: '/project/env', url: "/assets/guide/project/task-3.gif" },
], ],
rate: 0, rate: 0,
status: 0 status: 0
@ -354,10 +359,11 @@ export const TASK_DATA = [
name: "ui", name: "ui",
title: 'side_task.ui_test.title', title: 'side_task.ui_test.title',
percentage: 0, percentage: 0,
permission: ['PROJECT_UI_ELEMENT:READ+CREATE','PROJECT_UI_SCENARIO:READ+CREATE','PROJECT_UI_SCENARIO:READ+RUN','PROJECT_UI_SCENARIO:READ+DEBUG'],
taskData: [ taskData: [
{id: 1, name: 'side_task.ui_test.task_1', status: 0, permission: ['PROJECT_UI_ELEMENT:READ+CREATE'], api: ["/ui/element/add"], path: '/ui/element', url: "/assets/guide/ui/task-1.gif" }, {id: 1, name: 'side_task.ui_test.task_1', status: 0, permission: ['PROJECT_UI_ELEMENT:READ+CREATE'], api: ["/ui/element/add"], path: '/ui/element', url: "/assets/guide/ui/task-1.gif" },
{id: 2, name: 'side_task.ui_test.task_2', status: 0, permission: ['PROJECT_UI_ELEMENT:READ+CREATE'], api: ["/ui/automation/create"], path: '/ui/automation', url: "/assets/guide/ui/task-2.gif" }, {id: 2, name: 'side_task.ui_test.task_2', status: 0, permission: ['PROJECT_UI_SCENARIO:READ+CREATE'], api: ["/ui/automation/create"], path: '/ui/automation', url: "/assets/guide/ui/task-2.gif" },
{id: 2, name: 'side_task.ui_test.task_3', status: 0, permission: ['PROJECT_UI_SCENARIO:READ+RUN'], api: ["/ui/automation/run/debug"], path: '/ui/report', url: "/assets/guide/ui/task-3.gif" }, {id: 2, name: 'side_task.ui_test.task_3', status: 0, permission: ['PROJECT_UI_SCENARIO:READ+RUN','PROJECT_UI_SCENARIO:READ+DEBUG'], api: ["/ui/automation/run/debug"], path: '/ui/automation', url: "/assets/guide/ui/task-3.gif" },
], ],
rate: 0, rate: 0,
status: 0 status: 0

View File

@ -9,7 +9,6 @@ import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -53,7 +52,6 @@ public class NoviceService {
long systemTime = System.currentTimeMillis(); long systemTime = System.currentTimeMillis();
if(noviceInfo != null && noviceInfo.size() > 0){ if(noviceInfo != null && noviceInfo.size() > 0){
NoviceStatistics noviceStatistics = noviceInfo.get(0); NoviceStatistics noviceStatistics = noviceInfo.get(0);
noviceStatistics.setGuideStep(stepRequest.getGuideStep());
noviceStatistics.setGuideNum(noviceStatistics.getGuideNum() + 1); noviceStatistics.setGuideNum(noviceStatistics.getGuideNum() + 1);
noviceStatistics.setUpdateTime(systemTime); noviceStatistics.setUpdateTime(systemTime);
NoviceStatisticsExample example = new NoviceStatisticsExample(); NoviceStatisticsExample example = new NoviceStatisticsExample();
@ -65,7 +63,7 @@ public class NoviceService {
noviceStatistics.setId(UUID.randomUUID().toString()); noviceStatistics.setId(UUID.randomUUID().toString());
noviceStatistics.setUserId(SessionUtils.getUserId()); noviceStatistics.setUserId(SessionUtils.getUserId());
noviceStatistics.setGuideStep(stepRequest.getGuideStep()); noviceStatistics.setGuideStep(stepRequest.getGuideStep());
noviceStatistics.setGuideStep(1); noviceStatistics.setGuideNum(1);
noviceStatistics.setCreateTime(systemTime); noviceStatistics.setCreateTime(systemTime);
noviceStatistics.setUpdateTime(systemTime); noviceStatistics.setUpdateTime(systemTime);

View File

@ -136,6 +136,7 @@
</el-row> </el-row>
</el-card> </el-card>
<ms-introduction ref="introduction"/>
<edit-project ref="editProject" :is-show-app="isShowApp"/> <edit-project ref="editProject" :is-show-app="isShowApp"/>
</ms-main-container> </ms-main-container>
</ms-container> </ms-container>
@ -149,10 +150,11 @@ import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
import {hasPermission} from "metersphere-frontend/src/utils/permission"; import {hasPermission} from "metersphere-frontend/src/utils/permission";
import {getProject, getProjectMemberSize} from "../../api/project"; import {getProject, getProjectMemberSize} from "../../api/project";
import EditProject from "./EditProject"; import EditProject from "./EditProject";
import MsIntroduction from "metersphere-frontend/src/components/guide/components/Introduction";
export default { export default {
name: "ProjectHome", name: "ProjectHome",
components: {MsMainContainer, MsContainer, EditProject}, components: {MsMainContainer, MsContainer, EditProject, MsIntroduction},
data() { data() {
return { return {
project: { project: {
@ -166,6 +168,12 @@ export default {
isShowApp: false isShowApp: false
} }
}, },
mounted() {
this.$refs.introduction.resVisible = false
if(localStorage.getItem("step") === '3') {
this.initStep()
}
},
methods: { methods: {
click(str, permissions) { click(str, permissions) {
for (let permission of permissions) { for (let permission of permissions) {
@ -178,7 +186,102 @@ export default {
}, },
edit() { edit() {
this.$refs.editProject.edit(this.project); this.$refs.editProject.edit(this.project);
} },
initStep() {
const _this = this
_this.$nextTick(() => {
const tour = _this.$shepherd({
useModalOverlay: true,
exitOnEsc: false,
keyboardNavigation: false,
defaultStepOptions: {
scrollTo: {
behavior: 'smooth',
block: 'center'
},
canClickTarget: false,
//
modalOverlayOpeningPadding: 0,
//
modalOverlayOpeningRadius: 4
}
})
tour.addSteps([
{
attachTo: {
element: document.querySelector('.shepherd-project'),
on: 'bottom-end'
},
classes: "custom-width",
buttons: [
{
action: function() {
_this.$refs.introduction.resVisible = true
return _this.gotoCancel(this, true)
},
classes: 'close-btn',
text: _this.$t("shepherd.exit")
},
{
action: function() {
return _this.gotoNext(this, null, 4)
},
classes: 'shep-btn',
text: _this.$t("shepherd.next")
}
],
title: _this.$t("shepherd.step3.title"),
text: _this.$t("shepherd.step3.text")
},
{
attachTo: {
element: document.querySelector('.shepherd-project-menu'),
on: 'bottom-start'
},
buttons: [
{
action: function() {
_this.$refs.introduction.resVisible = true
return _this.gotoCancel(this, true)
},
classes: 'close-btn',
text: _this.$t("shepherd.exit")
},
{
action: function() {
return _this.gotoNext(this, null, 5)
},
classes: 'shep-btn',
text: _this.$t("shepherd.next")
}
],
title: _this.$t("shepherd.step4.title"),
text: _this.$t("shepherd.step4.text")
},
{
arrow:true,
modalOverlayOpeningPadding: 8,
attachTo: {
element: document.querySelector('.shepherd-project-name'),
on: 'right'
},
buttons: [
{
action: function() {
_this.$refs.introduction.resVisible = true
return _this.gotoCancel(this, false)
},
classes: 'close-btn',
text: _this.$t("shepherd.know")
}
],
title: _this.$t("shepherd.step5.title"),
text: _this.$t("shepherd.step5.text")
}
])
tour.start()
})
},
}, },
computed: { computed: {
projectId() { projectId() {
@ -195,6 +298,7 @@ export default {
.then(res => { .then(res => {
this.memberSize = res.data; this.memberSize = res.data;
}) })
} }
} }
</script> </script>

View File

@ -1,10 +1,18 @@
CREATE TABLE `novice_statistics` ( SET SESSION innodb_lock_wait_timeout = 7200;
`id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户id', CREATE TABLE `novice_statistics`
`guide_step` tinyint NOT NULL DEFAULT '0' COMMENT '新手引导完成的步骤', (
`guide_num` int NOT NULL DEFAULT '1' COMMENT '新手引导的次数', `id` varchar(50) NOT NULL COMMENT 'ID',
`data_option` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'data option (JSON format)', `user_id` varchar(64) NOT NULL COMMENT '用户id',
`create_time` bigint DEFAULT NULL, `guide_step` tinyint NOT NULL DEFAULT '0' COMMENT '新手引导完成的步骤',
`update_time` bigint DEFAULT NULL, `guide_num` int(10) NOT NULL DEFAULT '1' COMMENT '新手引导的次数',
PRIMARY KEY (`id`) USING BTREE `data_option` longtext DEFAULT NULL COMMENT 'data option (JSON format)',
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; `create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci;
SET SESSION innodb_lock_wait_timeout = DEFAULT;