Beautify user dashboard

This commit is contained in:
Pig Fang 2019-03-26 09:44:04 +08:00
parent c168970723
commit f7c03a3fac
7 changed files with 121 additions and 52 deletions

View File

@ -19,6 +19,7 @@
"dependencies": {
"@babel/runtime": "^7.3.4",
"@fortawesome/fontawesome-free": "^5.7.2",
"@tweenjs/tween.js": "^17.3.0",
"admin-lte": "^2.4.10",
"echarts": "^4.1.0",
"element-theme-chalk": "^2.6.3",

View File

@ -1,2 +1,4 @@
@import '~element-theme-chalk/src/button.scss';
@import '~element-theme-chalk/src/message.scss';
@import '~element-theme-chalk/src/message-box.scss';
@import '~element-theme-chalk/src/progress.scss';

View File

@ -1,15 +1,16 @@
import Vue from 'vue'
import Button from 'element-ui/lib/button'
import Message from 'element-ui/lib/message'
import MessageBox from 'element-ui/lib/message-box'
Vue.use(Button)
Vue.prototype.$message = Message
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
window.MessageBox = MessageBox
export {
Message,
MessageBox,

View File

@ -7,66 +7,85 @@
</div><!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-8">
<div class="progress-group">
<span v-t="'user.used.players'" class="progress-text" />
<span class="progress-number"><b>{{ playersUsed }}</b> / {{ playersTotal }}</span>
<div class="progress sm">
<div class="progress-bar progress-bar-aqua" :style="{ width: playersPercentage + '%' }" />
</div>
</div><!-- /.progress-group -->
<div class="progress-group">
<span v-t="'user.used.storage'" class="progress-text" />
<span id="user-storage" class="progress-number">
<template v-if="storageUsed > 1024">
<b>{{ round(storageUsed / 1024) }}</b> / {{ round(storageTotal / 1024) }} MB
</template>
<template v-else>
<b>{{ storageUsed }}</b> / {{ storageTotal }} KB
</template>
</span>
<div class="progress sm">
<div
id="user-storage-bar"
class="progress-bar progress-bar-yellow"
:style="{ width: storagePercentage + '%' }"
/>
</div>
</div><!-- /.progress-group -->
</div><!-- /.col -->
<div class="col-md-1" />
<div class="col-md-3">
<div class="text-center usage-label">
<b v-t="'user.used.players'" />
</div>
<el-progress
type="circle"
:percentage="playersPercentage"
status="text"
color="#00c0ef"
class="usage-circle"
>
<b>{{ playersUsed }}</b> / {{ playersTotal }}
</el-progress>
</div>
<div class="col-md-3">
<div class="text-center usage-label">
<b v-t="'user.used.storage'" />
</div>
<el-progress
type="circle"
:percentage="storagePercentage"
status="text"
color="#f39c12"
class="usage-circle"
>
<template v-if="storageUsed > 1024">
<b>{{ round(storageUsed / 1024) }}</b> / {{ round(storageTotal / 1024) }} MB
</template>
<template v-else>
<b>{{ storageUsed }}</b> / {{ storageTotal }} KB
</template>
</el-progress>
</div>
<div class="col-md-4">
<p class="text-center">
<strong v-t="'user.cur-score'" />
</p>
<p id="score" data-toggle="modal" data-target="#modal-score-instruction">
{{ score }}
{{ animatedScore }}
</p>
<p v-t="'user.score-notice'" class="text-center" style="font-size: smaller; margin-top: 20px;" />
</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- ./box-body -->
<div class="box-footer">
<button v-if="canSign" class="btn btn-primary pull-left" @click="sign">
<el-button
v-if="canSign"
class="btn btn-primary pull-left"
type="primary"
round
@click="sign"
>
<i class="far fa-calendar-check" aria-hidden="true" /> &nbsp;{{ $t('user.sign') }}
</button>
<button
</el-button>
<el-button
v-else
class="btn btn-primary pull-left"
type="primary"
round
:title="$t('user.last-sign', { time: lastSignAt.toLocaleString() })"
disabled
>
<i class="far fa-calendar-check" aria-hidden="true" /> &nbsp;
{{ remainingTimeText }}
</button>
</el-button>
</div><!-- /.box-footer -->
</div>
</div>
</template>
<script>
import Vue from 'vue'
import Progress from 'element-ui/lib/progress'
import Tween from '@tweenjs/tween.js'
import EmailVerification from '../../components/EmailVerification.vue'
Vue.use(Progress)
const ONE_DAY = 24 * 3600 * 1000
export default {
@ -76,13 +95,15 @@ export default {
},
data: () => ({
score: 0,
tweenedScore: 0,
lastSignAt: new Date(),
signAfterZero: false,
signGap: 0,
playersUsed: 0,
playersTotal: 0,
playersTotal: 1,
storageUsed: 0,
storageTotal: 0,
storageTotal: 1,
tween: null,
}),
computed: {
playersPercentage() {
@ -115,14 +136,31 @@ export default {
canSign() {
return this.signRemainingTime <= 0
},
animatedScore() {
return this.tweenedScore.toFixed(0)
},
},
watch: {
score(newValue) {
this.tween.to({ tweenedScore: newValue }, 1000).start()
},
},
created() {
this.tween = new Tween.Tween(this.$data)
},
beforeMount() {
this.fetchScoreInfo()
},
mounted() {
function animate() {
requestAnimationFrame(animate)
Tween.update()
}
animate()
},
methods: {
async fetchScoreInfo() {
const data = await this.$http.get('/user/score-info')
this.score = data.user.score
this.lastSignAt = new Date(data.user.lastSignAt)
this.signAfterZero = data.signAfterZero
this.signGap = data.signGapTime * 3600 * 1000
@ -130,6 +168,7 @@ export default {
this.playersTotal = data.stats.players.total
this.storageUsed = data.stats.storage.used
this.storageTotal = data.stats.storage.total
this.score = data.user.score
},
round: Math.round,
async sign() {
@ -157,6 +196,9 @@ export default {
margin-top 20px
cursor help
.progress
margin-top 4px
.usage-circle
padding-top 10px
.usage-label
padding-right 20%
</style>

View File

@ -1,6 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */
import 'jest-extended'
import Vue from 'vue'
import { Button } from 'element-ui'
window.blessing = {
base_url: '',
@ -35,6 +36,7 @@ Vue.prototype.$http = {
post: jest.fn(),
}
Vue.use(Button)
Vue.prototype.$message = {
info: jest.fn(),
success: jest.fn(),

View File

@ -1,8 +1,27 @@
/* eslint-disable no-mixed-operators */
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { Button } from 'element-ui'
import Dashboard from '@/views/user/Dashboard.vue'
jest.mock('@tweenjs/tween.js', () => ({
Tween: class <T> {
data: T
constructor(data: T) {
this.data = data
}
to(data: Partial<T>, _: number) {
Object.assign(this.data, data)
return this
}
start() {}
},
update() {},
}))
window.blessing.extra = { unverified: false }
function scoreInfo(data = {}) {
@ -33,7 +52,6 @@ test('players usage', async () => {
const wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('3 / 15')
expect(wrapper.find('.progress-bar-aqua').attributes('style')).toBe('width: 20%;')
})
test('storage usage', async () => {
@ -52,12 +70,10 @@ test('storage usage', async () => {
let wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('5 / 20 KB')
expect(wrapper.find('.progress-bar-yellow').attributes('style')).toBe('width: 25%;')
wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('2 / 4 MB')
expect(wrapper.find('.progress-bar-yellow').attributes('style')).toBe('width: 50%;')
})
test('display score', async () => {
@ -79,19 +95,19 @@ test('button `sign` state', async () => {
let wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.find('button').attributes('disabled')).toBeNil()
expect(wrapper.find(Button).attributes('disabled')).toBeNil()
wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.find('button').attributes('disabled')).toBe('disabled')
expect(wrapper.find(Button).attributes('disabled')).toBe('disabled')
wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.find('button').attributes('disabled')).toBeNil()
expect(wrapper.find(Button).attributes('disabled')).toBeNil()
wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.find('button').attributes('disabled')).toBe('disabled')
expect(wrapper.find(Button).attributes('disabled')).toBe('disabled')
})
test('remaining time', async () => {
@ -108,13 +124,13 @@ test('remaining time', async () => {
let wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.find('button').text()).toMatch(/(29)|(30)/)
expect(wrapper.find('button').text()).toContain('min')
expect(wrapper.find(Button).text()).toMatch(/(29)|(30)/)
expect(wrapper.find(Button).text()).toContain('min')
wrapper = mount(Dashboard)
await wrapper.vm.$nextTick()
expect(wrapper.find('button').text()).toContain('23')
expect(wrapper.find('button').text()).toContain('hour')
expect(wrapper.find(Button).text()).toContain('23')
expect(wrapper.find(Button).text()).toContain('hour')
Vue.prototype.$t = origin
})
@ -131,7 +147,7 @@ test('sign', async () => {
storage: { used: 3, total: 4 },
})
const wrapper = mount(Dashboard)
const button = wrapper.find('button')
const button = wrapper.find(Button)
await wrapper.vm.$nextTick()
button.trigger('click')

View File

@ -902,6 +902,11 @@
"@types/istanbul-lib-coverage" "^1.1.0"
"@types/yargs" "^12.0.9"
"@tweenjs/tween.js@^17.3.0":
version "17.3.0"
resolved "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.3.0.tgz#1cbea14b4b5baa292274c96766fd1b2a56b7ca9f"
integrity sha512-SPkhNj9/wGfbdX2C3B3KhttLQ4iesd+Ny8Dv1RnqF1MFUIqsZz/OJVLzJEHSEl7zheNx70dvqrwfbCFDQ0sWBw==
"@types/anymatch@*":
version "1.3.1"
resolved "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"