211 lines
8.8 KiB
HTML
211 lines
8.8 KiB
HTML
{extend name="common/plugin_layout" /}
|
|
{block name="title"}{$plugin.title} - {:config_get('title')}{/block}
|
|
{block name="main"}
|
|
<style>
|
|
.stat {
|
|
width: 100%;
|
|
column-gap: 1rem;
|
|
padding: 1rem 1.5rem;
|
|
}
|
|
.stat-title {
|
|
white-space: nowrap;
|
|
opacity: .6;
|
|
}
|
|
.stat-value {
|
|
white-space: nowrap;
|
|
grid-column-start: 1;
|
|
font-size: 1.8rem;
|
|
font-weight: 700;
|
|
line-height: 2.5rem
|
|
}
|
|
</style>
|
|
<div class="container-xl" id="app">
|
|
<div class="col-sm-12 col-md-10 col-xl-8 center-block">
|
|
<div class="card card-preview">
|
|
<div class="card-inner mt-3">
|
|
<div class="nya-title nk-ibx-action-item progress-rating">
|
|
<span class="nk-menu-text font-weight-bold">让流量消失</span>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">下载链接</label>
|
|
<div class="form-control-wrap">
|
|
<input type="text" v-model="set.input" placeholder="下载链接" class="form-control" autocomplete="off">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">线程</label>
|
|
<div class="form-control-wrap number-spinner-wrap">
|
|
<button class="btn btn-icon btn-outline-primary number-spinner-btn number-minus" @click="if(set.thread>1)set.thread--"><em class="icon ni ni-minus"></em></button>
|
|
<input type="number" class="form-control number-spinner" min="1" max="32" v-model="set.thread">
|
|
<button class="btn btn-icon btn-outline-primary number-spinner-btn number-plus"><em class="icon ni ni-plus" @click="if(set.thread<32)set.thread++"></em></button>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="preview-block">
|
|
<div class="custom-control custom-switch">
|
|
<input type="checkbox" class="custom-control-input" v-model="set.infinite" id="customSwitch2">
|
|
<label class="custom-control-label" for="customSwitch2">无限循环</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-dim btn-outline-secondary btn-block card-link" @click="run">
|
|
{{set.status?'停止':'开始'}}
|
|
</button>
|
|
<div class="row mt-4">
|
|
<div class="col-sm-12 col-md-4 border stat">
|
|
<div class="text-dark"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-15 w-15 float-right pt-3"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"></path></svg></div>
|
|
<div class="stat-title">已消失的流量</div>
|
|
<div class="stat-value">{{changeFilesize(waste)}}</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-4 border stat">
|
|
<div class="text-info"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-15 w-15 float-right pt-3"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg></div>
|
|
<div class="stat-title">每秒实时流量</div>
|
|
<div class="stat-value text-info">{{speed}}</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-4 border stat">
|
|
<div class="text-dark"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-15 w-15 float-right pt-3"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg></div>
|
|
<div class="stat-title">运行时长</div>
|
|
<div class="stat-value">{{secToTime(spend)}}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card card-preview">
|
|
<div class="card-inner">
|
|
<h6><em class="icon ni ni-info"></em> 工具说明</h6>
|
|
<div class="accordion-inner">
|
|
<p>此工具可以以浪费的形式消耗你的流量</p>
|
|
<p>注意:无法请求跨域链接</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/block}
|
|
{block name="script"}
|
|
<script src="{$cdn_cdnjs}vue/2.6.14/vue.min.js"></script>
|
|
<script src="{$cdn_cdnjs}axios/0.26.0/axios.min.js"></script>
|
|
<script>
|
|
var errors = null
|
|
new Vue({
|
|
el: '#app',
|
|
data: {
|
|
set: {
|
|
input: 'https://cachefly.cachefly.net/100mb.test',
|
|
output: '',
|
|
infinite: true,
|
|
status: false,
|
|
thread: 1,
|
|
},
|
|
tasks: [],
|
|
speed: '0.00B/s',
|
|
spend: 0,
|
|
waste: 0,
|
|
timer: null,
|
|
cancelSource: axios.CancelToken.source()
|
|
},
|
|
watch: {
|
|
async 'set.status'(newVal) {
|
|
if (newVal) {
|
|
this.cancelSource = axios.CancelToken.source()
|
|
this.timer = setInterval(() => {
|
|
this.speed = this.changeDownloadSpeed(this.tasks.reduce(function (prev, curr) {
|
|
return prev + curr;
|
|
}, 0));
|
|
this.spend++
|
|
}, 1000)
|
|
do {
|
|
await new Promise(resolve => {
|
|
let task = []
|
|
for (let i = 0; i < this.set.thread; i++) {
|
|
task.push(this.download(Math.random().toString(36).substr(2, 10)))
|
|
}
|
|
Promise.all(task).finally(resolve)
|
|
})
|
|
} while (this.set.status && this.set.infinite)
|
|
} else {
|
|
clearInterval(this.timer)
|
|
this.cancelSource.cancel()
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
run() {
|
|
this.set.status = !this.set.status
|
|
},
|
|
download(id) {
|
|
let loaded = 0
|
|
let speed = 0
|
|
let timestamp = new Date().getTime()
|
|
let that = this
|
|
const index = this.tasks.push(speed) - 1
|
|
return axios.request({
|
|
url: this.set.input,
|
|
cancelToken: this.cancelSource.token,
|
|
onDownloadProgress: function (progressEvent) {
|
|
// 处理原生进度事件
|
|
const now = new Date().getTime();
|
|
speed = (progressEvent.loaded - loaded) / (now - timestamp) * 1000
|
|
that.tasks[index] = speed
|
|
that.waste += progressEvent.loaded - loaded
|
|
loaded = progressEvent.loaded
|
|
timestamp = now
|
|
},
|
|
}).catch(e => {
|
|
if (!axios.isCancel(e)) {
|
|
layer.msg(e.message, {icon:2})
|
|
this.set.status = false
|
|
}
|
|
}).finally(() => {
|
|
delete that.tasks[index]
|
|
})
|
|
},
|
|
secToTime(s) {
|
|
let t = '';
|
|
if (s > -1) {
|
|
let hour = Math.floor(s / 3600)
|
|
let min = Math.floor(s / 60) % 60
|
|
let sec = s % 60
|
|
if (hour > 0) {
|
|
if (hour < 10) {
|
|
t += '0'
|
|
}
|
|
t = hour + "h"
|
|
}
|
|
if (hour > 0 || min > 0) {
|
|
if (min < 10) {
|
|
t += '0'
|
|
}
|
|
t += min + "m"
|
|
}
|
|
if (sec < 10) {
|
|
t += '0'
|
|
}
|
|
t += sec + 's'
|
|
}
|
|
return t
|
|
},
|
|
changeFilesize: (filesize) => {
|
|
filesize = parseInt(filesize);
|
|
let size = "";
|
|
if (filesize === 0) {
|
|
size = "0.00 B"
|
|
} else if (filesize < 1024) { //小于1KB,则转化成B
|
|
size = filesize.toFixed(2) + " B"
|
|
} else if (filesize < 1024 * 1024) { //小于1MB,则转化成KB
|
|
size = (filesize / 1024).toFixed(2) + " KB"
|
|
} else if (filesize < 1024 * 1024 * 1024) { //小于1GB,则转化成MB
|
|
size = (filesize / (1024 * 1024)).toFixed(2) + " MB"
|
|
} else { //其他转化成GB
|
|
size = (filesize / (1024 * 1024 * 1024)).toFixed(2) + " GB"
|
|
}
|
|
return size;
|
|
},
|
|
changeDownloadSpeed(filesize) {
|
|
filesize = this.changeFilesize(filesize);
|
|
return filesize.replace(/\s([K|M|G|B]*)B{0,1}/, '$1/s')
|
|
}
|
|
},
|
|
})
|
|
</script>
|
|
{/block} |