mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-06-28 01:06:58 +00:00
Reconstruct notification center for client side
This commit is contained in:
parent
4c8ba6b0a8
commit
173cc57490
21
src/static/client/package-lock.json
generated
21
src/static/client/package-lock.json
generated
@ -10,7 +10,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.6",
|
"bootstrap": "^5.3.6",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
@ -1708,6 +1710,12 @@
|
|||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz",
|
||||||
@ -2645,6 +2653,19 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "11.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz",
|
||||||
|
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/esm/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.3.5",
|
||||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.5.tgz",
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.6",
|
"bootstrap": "^5.3.6",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,30 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import './assets/main.css'
|
import './assets/main.css'
|
||||||
|
import NotificationList from "@/components/notification/notificationList.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div data-bs-theme="dark" class="text-body bg-body">
|
<div data-bs-theme="dark" class="text-body bg-body w-100 h-100">
|
||||||
<RouterView></RouterView>
|
<Suspense>
|
||||||
|
<RouterView v-slot="{ Component }">
|
||||||
|
<Transition name="app" mode="out-in" type="transition" appear>
|
||||||
|
<Component :is="Component"></Component>
|
||||||
|
</Transition>
|
||||||
|
</RouterView>
|
||||||
|
</Suspense>
|
||||||
|
<NotificationList></NotificationList>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.app-enter-active,
|
||||||
|
.app-leave-active {
|
||||||
|
transition: all 0.5s cubic-bezier(0.82, 0.58, 0.17, 1);
|
||||||
|
}
|
||||||
|
.app-enter-from,
|
||||||
|
.app-leave-to{
|
||||||
|
opacity: 0;
|
||||||
|
filter: blur(5px);
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -52,4 +52,8 @@
|
|||||||
.btn-brand:hover{
|
.btn-brand:hover{
|
||||||
--brandColor1: rgb(0, 142, 216);
|
--brandColor1: rgb(0, 142, 216);
|
||||||
--brandColor2: rgba(249, 70, 71)
|
--brandColor2: rgba(249, 70, 71)
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
<script setup>
|
||||||
|
import {onMounted} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
notificationData: {
|
||||||
|
id: "",
|
||||||
|
show: true,
|
||||||
|
content: "",
|
||||||
|
time: "",
|
||||||
|
status: ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let timeout = undefined;
|
||||||
|
const show = () => {
|
||||||
|
props.notificationData.show = true;
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
dismiss()
|
||||||
|
}, 50000)
|
||||||
|
}
|
||||||
|
const clearTime = () => clearTimeout(timeout)
|
||||||
|
const dismiss = () => props.notificationData.show = false;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
show()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
@mouseenter="clearTime()"
|
||||||
|
@mouseleave="notificationData.show ? show():undefined"
|
||||||
|
:class="{
|
||||||
|
'text-bg-success': notificationData.status === 'success',
|
||||||
|
'text-bg-warning': notificationData.status === 'warning',
|
||||||
|
'text-bg-danger': notificationData.status === 'danger'
|
||||||
|
}"
|
||||||
|
class="card shadow rounded-3 position-relative message ms-auto notification">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center mb-2">
|
||||||
|
<small>
|
||||||
|
{{ notificationData.time.format("hh:mm A") }}
|
||||||
|
</small>
|
||||||
|
<small class="ms-auto">
|
||||||
|
<a role="button" @click="dismiss()">
|
||||||
|
Dismiss<i class="bi bi-x-lg ms-2"></i>
|
||||||
|
</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="fw-medium">{{ notificationData.content }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.notification{
|
||||||
|
width: 100%;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 576px) {
|
||||||
|
.notification{
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,57 @@
|
|||||||
|
<script setup>
|
||||||
|
import {clientStore} from "@/stores/clientStore.js";
|
||||||
|
import Notification from "@/components/notification/notification.vue";
|
||||||
|
import {computed, onMounted} from "vue";
|
||||||
|
const store = clientStore()
|
||||||
|
const notifications = computed(() => {
|
||||||
|
return store.notifications.filter(x => x.show).slice().reverse()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
store.newNotification("Hi!!lskadjlkasjdlkasjkldjaslkdjklasjdlkjaslkdjlkasjdlkjsalkdjlkasjdlk", "warning")
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="messageCentre text-body position-absolute d-flex">
|
||||||
|
<TransitionGroup name="message" tag="div"
|
||||||
|
class="position-relative flex-sm-grow-0 flex-grow-1 d-flex align-items-end ms-sm-auto flex-column gap-2">
|
||||||
|
<Notification v-for="n in notifications"
|
||||||
|
:notificationData="n" :key="n.id"></Notification>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.message-move, /* apply transition to moving elements */
|
||||||
|
.message-enter-active,
|
||||||
|
.message-leave-active {
|
||||||
|
transition: all 0.5s cubic-bezier(0.82, 0.58, 0.17, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-enter-from,
|
||||||
|
.message-leave-to {
|
||||||
|
filter: blur(2px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-enter-from{
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-leave-to{
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageCentre{
|
||||||
|
z-index: 9999;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.messageCentre{
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
22
src/static/client/src/stores/clientStore.js
Normal file
22
src/static/client/src/stores/clientStore.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {defineStore} from "pinia";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {v4} from "uuid"
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
|
||||||
|
export const clientStore = defineStore('clientStore', () => {
|
||||||
|
const notifications = ref([])
|
||||||
|
function newNotification(content, status) {
|
||||||
|
notifications.value.push({
|
||||||
|
id: v4().toString(),
|
||||||
|
status: status,
|
||||||
|
content: content,
|
||||||
|
time: dayjs(),
|
||||||
|
show: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notifications, newNotification
|
||||||
|
}
|
||||||
|
})
|
@ -2,38 +2,47 @@
|
|||||||
import {reactive} from "vue";
|
import {reactive} from "vue";
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
username: "",
|
email: "",
|
||||||
password: ""
|
password: ""
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const submit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
for (let key in formData){
|
||||||
|
if (formData[key].length === 0){
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="d-flex vh-100 vw-100 p-4 overflow-y-scroll">
|
<div class="d-flex vh-100 vw-100 p-4 overflow-y-scroll">
|
||||||
<div class="m-auto" style="width: 600px">
|
<div class="m-auto" style="width: 600px">
|
||||||
<h4 class="mb-0">Welcome to</h4>
|
<h1>Sign In</h1>
|
||||||
<h1 class="fw-bold display-4">WGDashboard Client</h1>
|
<p>to your WGDashboard Client account</p>
|
||||||
<div class="mt-4 d-flex flex-column gap-3">
|
<form class="mt-4 d-flex flex-column gap-3" @submit="e => submit(e)">
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
required
|
required
|
||||||
v-model="formData.username"
|
v-model="formData.email"
|
||||||
name="username"
|
name="email"
|
||||||
autocomplete="username"
|
autocomplete="email"
|
||||||
autofocus
|
autofocus
|
||||||
class="form-control rounded-3" id="username" placeholder="Username">
|
class="form-control rounded-3" id="email" placeholder="email">
|
||||||
<label for="floatingInput" class="d-flex">
|
<label for="floatingInput" class="d-flex">
|
||||||
<i class="bi bi-person-circle me-2"></i>
|
<i class="bi bi-person-circle me-2"></i>
|
||||||
Username
|
Email
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input type="password"
|
<input type="password"
|
||||||
required
|
required
|
||||||
v-model="formData.password"
|
v-model="formData.password"
|
||||||
name="username"
|
name="password"
|
||||||
autocomplete="username"
|
autocomplete="password"
|
||||||
autofocus
|
class="form-control rounded-3" id="password" placeholder="Password">
|
||||||
class="form-control rounded-3" id="username" placeholder="Username">
|
|
||||||
<label for="floatingInput" class="d-flex">
|
<label for="floatingInput" class="d-flex">
|
||||||
<i class="bi bi-key me-2"></i>
|
<i class="bi bi-key me-2"></i>
|
||||||
Password
|
Password
|
||||||
@ -42,8 +51,13 @@ const formData = reactive({
|
|||||||
<a href="#" class="text-body text-decoration-none">
|
<a href="#" class="text-body text-decoration-none">
|
||||||
Forgot Password?
|
Forgot Password?
|
||||||
</a>
|
</a>
|
||||||
<button class="btn btn-primary rounded-3 btn-lg btn-brand w-100 fw-bold">Sign In</button>
|
<div class="d-flex">
|
||||||
</div>
|
<button class="ms-auto btn btn-primary rounded-3 btn-brand px-3 py-2">
|
||||||
|
Continue
|
||||||
|
<i class="ms-2 bi bi-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
|
@ -2,16 +2,77 @@
|
|||||||
import {reactive} from "vue";
|
import {reactive} from "vue";
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
username: "",
|
|
||||||
email: "",
|
email: "",
|
||||||
password: ""
|
password: "",
|
||||||
|
confirmPassword: ""
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="d-flex vh-100 vw-100 p-4 overflow-y-scroll">
|
<div class="d-flex vh-100 vw-100 p-4 overflow-y-scroll">
|
||||||
<div class="m-auto" style="width: 600px">
|
<div class="m-auto" style="width: 600px">
|
||||||
<h1 class="fw-bold display-4">Sign Up</h1>
|
<h1>Sign Up</h1>
|
||||||
|
<p>to use WGDashboard Client</p>
|
||||||
|
<div class="mt-4 d-flex flex-column gap-3">
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="text"
|
||||||
|
required
|
||||||
|
v-model="formData.email"
|
||||||
|
name="email"
|
||||||
|
autocomplete="email"
|
||||||
|
autofocus
|
||||||
|
class="form-control rounded-3" id="email" placeholder="email">
|
||||||
|
<label for="floatingInput" class="d-flex">
|
||||||
|
<i class="bi bi-person-circle me-2"></i>
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="password"
|
||||||
|
required
|
||||||
|
v-model="formData.password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="password"
|
||||||
|
autofocus
|
||||||
|
class="form-control rounded-3" id="password" placeholder="password">
|
||||||
|
<label for="floatingInput" class="d-flex">
|
||||||
|
<i class="bi bi-key me-2"></i>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="password"
|
||||||
|
required
|
||||||
|
v-model="formData.password"
|
||||||
|
name="confirm_password"
|
||||||
|
autocomplete="confirm_password"
|
||||||
|
autofocus
|
||||||
|
class="form-control rounded-3" id="confirm_password" placeholder="confirm_password">
|
||||||
|
<label for="floatingInput" class="d-flex">
|
||||||
|
<i class="bi bi-key me-2"></i>
|
||||||
|
Confirm Password
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
|
<button class="ms-auto btn btn-primary rounded-3 btn-brand px-3 py-2">
|
||||||
|
Continue
|
||||||
|
<i class="ms-2 bi bi-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="text-muted">
|
||||||
|
Already have an account?
|
||||||
|
</span>
|
||||||
|
<RouterLink to="/signin" class="text-body text-decoration-none ms-auto fw-bold">
|
||||||
|
Sign In
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user