feat: init

This commit is contained in:
ygx
2026-03-05 23:45:39 +08:00
commit 8fab91c5c7
214 changed files with 33682 additions and 0 deletions

27
src/hooks/chart-option.ts Normal file
View File

@@ -0,0 +1,27 @@
import { useAppStore } from '@/store'
import { EChartsOption } from 'echarts'
import { computed } from 'vue'
// for code hints
// import { SeriesOption } from 'echarts';
// Because there are so many configuration items, this provides a relatively convenient code hint.
// When using vue, pay attention to the reactive issues. It is necessary to ensure that corresponding functions can be triggered, TypeScript does not report errors, and code writing is convenient.
interface optionsFn {
(isDark: boolean): EChartsOption
}
export default function useChartOption(sourceOption: optionsFn) {
const appStore = useAppStore()
const isDark = computed(() => {
return appStore.theme === 'dark'
})
// echarts support https://echarts.apache.org/zh/theme-builder.html
// It's not used here
// TODO echarts themes
const chartOption = computed<EChartsOption>(() => {
return sourceOption(isDark.value)
})
return {
chartOption,
}
}

16
src/hooks/loading.ts Normal file
View File

@@ -0,0 +1,16 @@
import { ref } from 'vue'
export default function useLoading(initValue = false) {
const loading = ref(initValue)
const setLoading = (value: boolean) => {
loading.value = value
}
const toggle = () => {
loading.value = !loading.value
}
return {
loading,
setLoading,
toggle,
}
}

22
src/hooks/locale.ts Normal file
View File

@@ -0,0 +1,22 @@
import { Message } from '@arco-design/web-vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export default function useLocale() {
const i18 = useI18n()
const currentLocale = computed(() => {
return i18.locale.value
})
const changeLocale = (value: string) => {
if (i18.locale.value === value) {
return
}
i18.locale.value = value
localStorage.setItem('arco-locale', value)
Message.success(i18.t('navbar.action.locale'))
}
return {
currentLocale,
changeLocale,
}
}

30
src/hooks/permission.ts Normal file
View File

@@ -0,0 +1,30 @@
import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
import { useUserStore } from '@/store'
export default function usePermission() {
const userStore = useUserStore()
return {
accessRouter(route: RouteLocationNormalized | RouteRecordRaw) {
return (
!route.meta?.requiresAuth || !route.meta?.roles || route.meta?.roles?.includes('*') || route.meta?.roles?.includes(userStore.role)
)
},
findFirstPermissionRoute(_routers: any, role = 'admin') {
const cloneRouters = [..._routers]
while (cloneRouters.length) {
const firstElement = cloneRouters.shift()
if (
firstElement?.meta?.roles?.find((el: string[]) => {
return el.includes('*') || el.includes(role)
})
)
return { name: firstElement.name }
if (firstElement?.children) {
cloneRouters.push(...firstElement.children)
}
}
return null
},
// You can add any rules you want
}
}

26
src/hooks/request.ts Normal file
View File

@@ -0,0 +1,26 @@
import { ref, UnwrapRef } from 'vue'
import { AxiosResponse } from 'axios'
import { HttpResponse } from '@/api/interceptor'
import useLoading from './loading'
// use to fetch list
// Don't use async function. It doesn't work in async function.
// Use the bind function to add parameters
// example: useRequest(api.bind(null, {}))
export default function useRequest<T>(
api: () => Promise<AxiosResponse<HttpResponse>>,
defaultValue = [] as unknown as T,
isLoading = true
) {
const { loading, setLoading } = useLoading(isLoading)
const response = ref<T>(defaultValue)
api()
.then((res) => {
response.value = res.data as unknown as UnwrapRef<T>
})
.finally(() => {
setLoading(false)
})
return { loading, response }
}

32
src/hooks/responsive.ts Normal file
View File

@@ -0,0 +1,32 @@
import { useAppStore } from '@/store'
import { addEventListen, removeEventListen } from '@/utils/event'
import { useDebounceFn } from '@vueuse/core'
import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue'
const WIDTH = 992 // https://arco.design/vue/component/grid#responsivevalue
function queryDevice() {
const rect = document.body.getBoundingClientRect()
return rect.width - 1 < WIDTH
}
export default function useResponsive(immediate?: boolean) {
const appStore = useAppStore()
function resizeHandler() {
if (!document.hidden) {
const isMobile = queryDevice()
appStore.toggleDevice(isMobile ? 'mobile' : 'desktop')
appStore.toggleMenu(isMobile)
}
}
const debounceFn = useDebounceFn(resizeHandler, 100)
onMounted(() => {
if (immediate) debounceFn()
})
onBeforeMount(() => {
addEventListen(window, 'resize', debounceFn)
})
onBeforeUnmount(() => {
removeEventListen(window, 'resize', debounceFn)
})
}

12
src/hooks/themes.ts Normal file
View File

@@ -0,0 +1,12 @@
import { computed } from 'vue'
import { useAppStore } from '@/store'
export default function useThemes() {
const appStore = useAppStore()
const isDark = computed(() => {
return appStore.theme === 'dark'
})
return {
isDark,
}
}

24
src/hooks/user.ts Normal file
View File

@@ -0,0 +1,24 @@
import { useRouter } from 'vue-router'
import { Message } from '@arco-design/web-vue'
import { useUserStore } from '@/store'
export default function useUser() {
const router = useRouter()
const userStore = useUserStore()
const logout = async (logoutTo?: string) => {
await userStore.logout()
const currentRoute = router.currentRoute.value
Message.success('登出成功')
router.push({
name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login',
query: {
...router.currentRoute.value.query,
redirect: currentRoute.name as string,
},
})
}
return {
logout,
}
}

16
src/hooks/visible.ts Normal file
View File

@@ -0,0 +1,16 @@
import { ref } from 'vue'
export default function useVisible(initValue = false) {
const visible = ref(initValue)
const setVisible = (value: boolean) => {
visible.value = value
}
const toggle = () => {
visible.value = !visible.value
}
return {
visible,
setVisible,
toggle,
}
}