效果截图
进度条组件的代码
<template>
<div>
<div class="iconbox">
<i class="el-icon-caret-left" @click="getProgress" v-if="!isShow"></i>
<i class="el-icon-caret-right" @click="getClose" v-else></i>
</div>
<div class="progressbox" v-show="isShow">
<div class="title">进 度 展 示</div>
<div class="contbox">
<div v-for="(item,index) in jobData" :key="index" style="width: 100%;">
<!-- 进度完成的 -->
<div class="item_box_true" v-if="item.completed">
<div class="filename" :title="item.jobName">{{ item.jobName }}:</div>
<div class="jindu"></div>
<div class="progressnum">100%</div>
</div>
<!-- 进度进行的 -->
<div class="item_box" v-show="!item.completed">
<div class="filename" :title="item.jobName">{{ item.jobName }}:</div>
<div class="jindu">
<div class="planbox" :style="getStyle(item)" :id="`${item.id}`"></div>
</div>
<div class="progressnum">{{ item.percent }}%</div>
</div>
</div>
</div>
</div>
<div id="showDiv" style="position: absolute; background-color: white; border: 1px solid black;">
</div>
</div>
</template>
<script>
import {
getjobAdd,
getjobList,
} from '@/api/purchaserecord/opentender.js'
import {mapGetters} from "vuex";
export default {
components: {
},
// 定义属性
data() {
return {
isShow:false,
queryParams:{
batchName:undefined,
batchCode:undefined
},
rateForm:{},
jobData:[],
oldJobData:[],
clientId:undefined,
jobForm:{}
}
},
comments:{
...mapGetters['rateData']
},
watch: {
"$store.getters.rateData": {
//深度监听,可监听到对象、数组的变化
handler(newValue,oldValue) {
this.rateForm = newValue
if(this.jobData && this.jobData.length>0){
let arr = []
this.jobData.forEach(item =>{
// item.completed为true的表示进度为100%的,这里看后端返回数据怎么定义,看个人项目情况
if(!item.completed){
if(item.id === this.rateForm.jobId){
item.percent = this.rateForm.percent;
if(item.percent == 100){
item.completed = true;
if(this.rateForm.messageType == 'business' && this.rateForm.jobType == '1'){
// this.getList()
}
}else{
this.updateProgress(item.id);
}
}
}
arr.push(item)
})
this.jobData = arr
// 获取存入浏览器的任务列表,保证打开进度框的进度条是最新的(如果后端返回的jobData数据里有与推送消息同步的percent,可以忽略此步骤)
sessionStorage.setItem('oldJobData',JSON.stringify(this.jobData))
}
},
deep: true, //true 深度监听
}
},
created() {
if(sessionStorage.getItem('batchForm')){
let form = JSON.parse(sessionStorage.getItem('batchForm'))
this.queryParams.batchName = form.batchName
this.queryParams.batchCode = form.batchCode
}
// 获取存入浏览器的唯一标识
if(sessionStorage.getItem('clientId')){
this.clientId = sessionStorage.getItem('clientId')
}
// 获取存入浏览器的任务列表,保证打开进度框的进度条是最新的
if(sessionStorage.getItem('oldJobData')){
this.oldJobData = JSON.parse(sessionStorage.getItem('oldJobData'));
}
},
mounted() {
},
// 销毁时断开连接
beforeDestroy(){
if (this.dataForm) {
this.dataForm.close();
}
},
methods: {
// 触发连接,接收消息
getcreateLink(){
this.$store.dispatch("CreateLink",this.clientId).then(res =>{
// this.source = res
})
},
//打开进度条弹框
getProgress(){
this.getjobList()
},
//关闭进度条弹框
getClose(val){
this.isShow = false
},
// job列表,进度条任务列表
getjobList(){
let form = {
clientId:this.clientId,
batchName:this.queryParams.batchName,
batchCode:this.queryParams.batchCode
}
getjobList(form).then(res =>{
this.jobData = res.data.data;
this.jobData.forEach((item)=>{
if(this.oldJobData.filter((a)=>a.id === item.id).length > 0){
item.percent = this.oldJobData.filter((a)=>a.id === item.id)[0].percent
}else {
item.percent = 0;
}
})
this.isShow = true
})
},
// 进度条进度绘制
updateProgress(val){
if(this.rateForm && this.rateForm.percent){
let width = 0
const progressBar = document.getElementById(`${val}`);
width = this.rateForm.percent
progressBar.style.width = width + '%';
}
},
// 计算初始进度条进度
getStyle(row){
return `width:${row.percent?row.percent*2:0}px;`
}
},
}
</script>
<style lang='scss' scoped>
.iconbox{
position: fixed;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #ffffff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
right: 1%;
bottom: 5%;
z-index: 9999 !important;
display: flex;
justify-content: center;
align-items: center;
i{
font-size: 24px;
color: #009688;
}
}
.progressbox{
border-radius: 10px 10px 0 0;
position: fixed;
width: 420px;
height: 260px;
background-color: #ffffff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
right: 5%;
bottom: 5%;
z-index: 9999 !important;
.title{
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
color: #ffffff;
border-radius: 10px 10px 0 0;
background-color: #009688;
}
.contbox{
width: 100%;
height: 200px;
overflow: hidden;
overflow-y: auto;
text-align: center;
.item_box{
width:400px;
height: 30px;
margin: 0 10px;
display: flex;
justify-content: center;
align-items: center;
.filename{
font-size: 12px !important;
width: 140px;
margin-right: 10px;
white-space: nowrap; /* 保持文本在一行内显示 */
overflow: hidden; /* 超出容器部分隐藏 */
text-overflow: ellipsis; /* 使用省略号表示文本被截断 */
}
.jindu{
width:200px;
height: 10px;
border-radius: 5px;
background-color: #d9d9d9;
.planbox{
height: 10px;
border-radius: 5px;
background-color: #009688;
}
}
.progressnum{
width: 30px;
font-size: 12px !important;
margin-left: 10px;
}
}
.item_box_true{
width:400px;
height: 30px;
margin: 0 10px;
display: flex;
justify-content: center;
align-items: center;
.filename{
font-size: 12px !important;
width: 140px;
margin-right: 10px;
white-space: nowrap; /* 保持文本在一行内显示 */
overflow: hidden; /* 超出容器部分隐藏 */
text-overflow: ellipsis; /* 使用省略号表示文本被截断 */
}
.jindu{
width:200px;
height: 10px;
border-radius: 5px;
background-color: #009688;
}
.progressnum{
width: 30px;
font-size: 12px !important;
margin-left: 10px;
}
}
}
}
</style>
存入vueX中的触发方法(这里是我项目里方法,可根据自己项目进行更改)
let ws = null;
let wsState = false
async function createWebSocket(commit,id) {
const wsUri = '/ws/'+id;
if ('WebSocket' in window) {
ws = new WebSocket(wsUri);
} else {
console.log('您的浏览器不支持websocket协议');
}
}
function initEventHandle( commit,id) {
console.log('ws=>',ws);
ws.onmessage = (event) => {
// console.log('接收消息=>:' + event.data + ' 时间:' + new Date());
if(event.data && event.data.length>0){
const rateForm = JSON.parse(event.data)
commit('SET_RATEDATA',rateForm)
}
};
ws.onerror = function () {
console.log('ws连接错误!');
};
}
const user = {
state: {
rateData:null,
sourceid:[],
},
actions:{
CreateLink({commit},id) {
return new Promise((resolve) => {
let strid = sessionStorage.getItem('sourceid')?sessionStorage.getItem('sourceid'):null
const issource = sessionStorage.getItem('issource')
createWebSocket(commit,id);
if(strid !== id){
sessionStorage.setItem('sourceid',id)
if(issource != 1){
ws.onopen = (event) => {
console.log('建立onopen链接=>',event.data);
sessionStorage.setItem('issource',1)
setInterval(()=>{
ws.send('11')
},2000)
}
}
}
initEventHandle(commit,id);
})
}
},
mutations:{
SET_RATEDATA: (state,rateData) => {
state.rateData = rateData;
},
SET_SOURCEID: (state,sourceid) => {
state.sourceid = sourceid;
},
}
}
app.vue中的代码(这里是我项目里方法,可根据自己项目进行更改。此处作用是保证刷新或者切换项目其他页面,也能保证接收到新的消息推送)
data() {
return {
sk:'',
dataForm:null,
rateForm:{}
};
},
watch: {},
created() {
// 创建浏览器唯一标识id,建立sse通信实时连接
FingerprintJS.load().then(fp => {
fp.get().then(result => {
const clientId = result.visitorId;
sessionStorage.setItem('clientId',clientId)
this.createLink(clientId)
});
});
},
beforeDestroy(){
if (this.dataForm) {
this.dataForm.close();
}
},
methods: {
// sse消息推送建立链接
createLink(id) {
this.$store.dispatch("CreateLink",id).then(res =>{
this.dataForm = res
})
}
},
computed: {
...mapGetters(['source'])
},
其他页面使用本组件时
<template>
<div>
<basic-container>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button
type="primary"
size="small"
icon="el-icon-refresh"
@click="handleRenew"
>更新</el-button
>
</el-col>
</el-row>
<Progress ref="progs"></Progress>
</basic-container>
</div>
</template>
methods: {
handleRenew(){
this.$confirm("是否更新数据?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
let jobform = {
jobName:'供应商标书信息',
jobType:3,
clientId:this.clientId,
batchCode:this.queryParams.batchCode,
batchName:this.queryParams.batchName
}
// 触发连接接收消息
this.$refs['progs'].getcreateLink()
// 打开进度框
this.$refs['progs'].getProgress()
// 创建任务id,生成任务
getjobAdd(jobform).then(res =>{
getMerge(jobform).then(res =>{
})
})
});
},
}
vue.config.js里的修改
//开发模式反向代理配置,生产模式请使用Nginx部署并配置反向代理
devServer: {
port: 1888,
proxy: {
'/api': {
//本地服务接口地址
target: 'http://192.168.2.17:28825',
//远程演示服务地址,可用于直接启动项目
//target: 'https://saber.bladex.vip/api',
ws: true,
pathRewrite: {
'^/api': '/'
}
},
'/ws': {
//本地服务接口地址
target: 'ws://192.168.2.17:28825',
//远程演示服务地址,可用于直接启动项目
//target: 'https://saber.bladex.vip/api',
ws: true
},
},
disableHostCheck:true
}
接下来的为借鉴大佬的WebSocket代码(放在vuex里的)
const state = {
wsState: false,
};
let ws = null;
async function createWebSocket(commit) {
const res = await getHeartbeatFlag();
const isHeartbeat = res.data === 'true';
if (isHeartbeat) {
let ip = document.location.protocol + '//' + window.location.host;
ip = ip.replace('http', 'ws').replace('https', 'wss');
const wsUri = ip + process.env.VUE_APP_BASE_API + defaultSettings.heartWsApi + state.token;
// console.log(wsUri)
try {
if ('WebSocket' in window) {
ws = new WebSocket(wsUri);
} else {
console.log('您的浏览器不支持websocket协议');
}
initEventHandle(wsUri, commit);
} catch (e) {
console.log(e);
}
}
}
function initEventHandle(wsUri, commit) {
ws.onclose = function () {
console.log('Ws is Close, Re WebSocket!' + new Date() + ' wsState: ' + state.wsState);
if (state.wsState) {
createWebSocket(commit);
}
};
ws.onopen = function () {
// console.log('wsSuccess!' + new Date())
};
ws.onmessage = function (event) {
if (event.data === '1') {
console.log('heartbeat:' + event.data + ' 时间:' + new Date());
} else if (event.data === '-1') {
console.log('服务器发送关闭');
state.wsState = false;
if (ws !== null) {
ws.close();
}
Modal.confirm({
title: '注销成功',
content: '您已注销,请您重新登录!',
closable: false,
okText: '重新登录',
cancelText: '取消',
onOk() {
console.log('request error -> resetToken');
store.dispatch('user/resetToken').then(() => {
location.reload();
});
},
});
}
};
ws.onerror = function () {
console.log('ws连接错误!');
};
}
const actions = {
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token)
.then((response) => {
const { data } = response;
if (!data) {
reject('验证失败,请重新登录。');
}
const {
roles,
name,
userList,
userId,
newUser,
avatar,
introduction,
rolesMenus,
userFunctions,
currentRole,
passOutDay,
isValidate,
menus,
} = data;
// roles must be a non-empty array
if (!roles || roles.length <= 0) {
reject('getInfo: 角色必须是非零数组!');
}
commit('SET_ROLES', roles);
commit('SET_NAME', name);
commit('SET_UserId', userId);
commit('SET_NewUser', newUser);
commit('SET_UserList', userList);
commit('SET_AVATAR', avatar);
commit('SET_INTRODUCTION', introduction);
commit('SET_ROLES_MENUS', rolesMenus);
commit('SET_USER_FUNCTIONS', userFunctions);
commit('SET_CURRENT_ROLE', currentRole);
commit('SET_PASSOUTDAY', passOutDay);
commit('SET_VALIDATE', isValidate);
commit('SET_NAVLIST', {
roles,
menus,
});
// heartbeatFlag(commit)
// 开启ws连接
state.wsState = true;
createWebSocket(commit);
resolve(data);
})
.catch((error) => {
reject(error);
});
});
},
}