feat: add loading
This commit is contained in:
@@ -2,4 +2,5 @@
|
||||
<router-view />
|
||||
<Toast position="bottom-right" />
|
||||
<ConfirmDialog style="width: min(500px, 92vw)" />
|
||||
<LoadingOverlay />
|
||||
</template>
|
||||
Vendored
-5
@@ -30,11 +30,6 @@ declare module 'vue' {
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Select: typeof import('primevue/select')['default']
|
||||
Tab: typeof import('primevue/tab')['default']
|
||||
TabList: typeof import('primevue/tablist')['default']
|
||||
TabPanel: typeof import('primevue/tabpanel')['default']
|
||||
TabPanels: typeof import('primevue/tabpanels')['default']
|
||||
Tabs: typeof import('primevue/tabs')['default']
|
||||
Tag: typeof import('primevue/tag')['default']
|
||||
Textarea: typeof import('primevue/textarea')['default']
|
||||
Toast: typeof import('primevue/toast')['default']
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="fixed inset-0 z-9999 flex items-center justify-center bg-white/70 dark:bg-black/60 backdrop-blur-sm"
|
||||
>
|
||||
<ProgressSpinner
|
||||
strokeWidth="4"
|
||||
fill="transparent"
|
||||
animationDuration=".8s"
|
||||
:style="{ width: '48px', height: '48px' }"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useLoading } from '../composables/useLoading'
|
||||
|
||||
const { loading } = useLoading()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const pendingCount = ref(0)
|
||||
const loading = ref(false)
|
||||
|
||||
export function useLoading() {
|
||||
function start() {
|
||||
pendingCount.value++
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (pendingCount.value > 0) {
|
||||
pendingCount.value--
|
||||
}
|
||||
if (pendingCount.value === 0) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return { loading, start, stop }
|
||||
}
|
||||
+9
-1
@@ -1,4 +1,7 @@
|
||||
import axios, { type AxiosInstance } from 'axios'
|
||||
import { useLoading } from '../composables/useLoading'
|
||||
|
||||
const { start: loadingStart, stop: loadingStop } = useLoading()
|
||||
|
||||
export const api: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:2000',
|
||||
@@ -8,6 +11,7 @@ export const api: AxiosInstance = axios.create({
|
||||
})
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
loadingStart()
|
||||
const token = localStorage.getItem('mws_token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
@@ -16,8 +20,12 @@ api.interceptors.request.use((config) => {
|
||||
})
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(response) => {
|
||||
loadingStop()
|
||||
return response
|
||||
},
|
||||
(error) => {
|
||||
loadingStop()
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('mws_token')
|
||||
localStorage.removeItem('mws_user')
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
draggable="true"
|
||||
@dragstart="onDragStart(task)"
|
||||
@dragend="onDragEnd"
|
||||
@click.stop="toggleDesc(task.id)"
|
||||
@click.stop="openEdit(task)"
|
||||
>
|
||||
<div class="font-medium">{{ task.title }}</div>
|
||||
<div v-if="expandedDescs.has(task.id) && task.description" class="muted-note mt-1 line-clamp-2 text-[0.85rem]">{{ task.description }}</div>
|
||||
<div v-if="task.description" class="muted-note mt-1 line-clamp-2 text-[0.85rem]">{{ task.description }}</div>
|
||||
<div class="mt-2 text-[0.8rem]" v-if="task.assigneeName">
|
||||
<span class="flex items-center gap-1 muted-note">
|
||||
<i class="pi pi-user text-[0.7rem]"></i> {{ task.assigneeName }}
|
||||
@@ -80,7 +80,7 @@ const route = useRoute()
|
||||
const toast = useToast()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const projectId = String(route.params.id)
|
||||
const projectId = computed(() => String(route.params.id))
|
||||
const tasks = ref<Task[]>([])
|
||||
const members = ref<{ userId: string; displayName: string }[]>([])
|
||||
|
||||
@@ -89,13 +89,10 @@ const dragOverStatus = ref<TaskStatus | null>(null)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const editingTask = ref<Task | null>(null)
|
||||
const expandedDescs = ref(new Set<string>())
|
||||
|
||||
function toggleDesc(id: string) {
|
||||
const s = new Set(expandedDescs.value)
|
||||
if (s.has(id)) s.delete(id)
|
||||
else s.add(id)
|
||||
expandedDescs.value = s
|
||||
function openEdit(task: Task) {
|
||||
editingTask.value = task
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const columns = [
|
||||
@@ -111,7 +108,7 @@ function tasksIn(status: TaskStatus) {
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const res = await getTasks(projectId, undefined, 1, 100)
|
||||
const res = await getTasks(projectId.value, undefined, 1, 100)
|
||||
tasks.value = res.items
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: errorMessage(e), life: 4000 })
|
||||
@@ -120,7 +117,7 @@ async function load() {
|
||||
|
||||
async function loadMembers() {
|
||||
try {
|
||||
const res = await getMembers(projectId, 1, 100)
|
||||
const res = await getMembers(projectId.value, 1, 100)
|
||||
members.value = res.items.map((m) => ({
|
||||
userId: m.userId,
|
||||
displayName: m.displayName,
|
||||
@@ -196,10 +193,14 @@ function formatDate(v: string) {
|
||||
return new Date(v).toLocaleDateString()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async () => {
|
||||
await loadMembers()
|
||||
await load()
|
||||
})
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user