主题切换
参考主题控制器 。以下甄选了几个好看的主题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 export default { content : ["./src/**/*.{html,js,vue}" ], theme : { extend : {}, }, plugins : [require ("daisyui" )], daisyui : { themes : [ "light" , "dark" , "halloween" , "garden" , "forest" , "lofi" , "pastel" , "fantasy" , "wireframe" , "dracula" , "cmyk" , "autumn" , ], darkTheme : "dark" , base : true , styled : true , utils : true , prefix : "" , logs : true , themeRoot : ":root" , }, }
我的主题切换器
其中 tabindex=“0” 代表元素能否通过进行聚焦(用点的或者Tab键),此处第一个tabindex="0"不能省略,因为下面的ul的dropdown-content的底层css样式写的就是:上面那个Theme div聚焦了,下面这个列表才显示。至于第二个ul的聚焦,没啥用,可以删掉,因为input能直接聚焦。
通过 :checked=“index == themeIndex” 来初始化主题选择器的radio 的选项默认选中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <template> <div class="dropdown"> <div tabindex="0" role="button" class="btn m-1"> Theme <svg width="12px" height="12px" class="h-2 w-2 fill-current opacity-60 inline-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"> <path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z"></path> </svg> </div> <ul tabindex="0" class="dropdown-content z-[1] p-2 shadow-2xl bg-base-300 rounded-box w-52"> <li v-for="(theme, index) in themeList" @click="changeTheme(index, theme)"> <input type="radio" name="theme-dropdown" class="theme-controller btn btn-sm btn-block btn-ghost justify-start" :aria-label="theme" :value="theme" :checked="index == themeIndex" /> </li> </ul> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { useThemeStore } from '@/stores/themeStore'; const { changeThemeTo, getCurrentThemeIndex } = useThemeStore(); const themeIndex = ref(0); const themeList = ref([ "light", "dark", "halloween", "garden", "forest", "lofi", "pastel", "fantasy", "wireframe", "black", "luxury", "dracula", "cmyk", "autumn", ]) function changeTheme(index, themeName) { themeIndex.value = index; changeThemeTo(index, themeName); } onMounted(() => { themeIndex.value = getCurrentThemeIndex(); }) </script> <style scoped></style>
以下是pinia持久化的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import { ref } from 'vue' import { defineStore } from 'pinia' export const useThemeStore = defineStore ('theme' , () => { const theme = ref ('light' ); const themeIndex = ref (0 ); function initialTheme ( ) { let html = document .getElementsByTagName ('html' )[0 ]; html.setAttribute ('data-theme' , theme.value ); } function changeThemeTo (index, themeName ) { themeIndex.value = index; theme.value = themeName; } function getCurrentThemeIndex ( ) { return themeIndex.value ; } return { theme, themeIndex, initialTheme, changeThemeTo, getCurrentThemeIndex } }, { persist : true })
即便在控制器中初始化了默认radio 的默认选中,但是在App.vue的onMounted函数中调用初始化主题仍然是必要的。因为主题控制器大多在导航栏中,而有的登陆界面中没有导航栏,意味着主题控制器未生效,此时需要根据本地数据主动初始化主题。
封装好的PopingAlert
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <div ref ="alert" role ="alert" class ="alert w-fit absolute left-1/2 -translate-x-1/2 transition-all duration-1000" :class ="{ 'opacity-0 invisible -top-3': !visible, 'opacity-100 top-3': visible, [alertTypeClass]: true }" > <svg v-if ="type == 'default'" xmlns ="http://www.w3.org/2000/svg" fill ="none" viewBox ="0 0 24 24" class ="stroke-info shrink-0 w-6 h-6" > <path stroke-linecap ="round" stroke-linejoin ="round" stroke-width ="2" d ="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" > </path > </svg > <svg v-if ="type == 'info'" xmlns ="http://www.w3.org/2000/svg" fill ="none" viewBox ="0 0 24 24" class ="stroke-current shrink-0 w-6 h-6" > <path stroke-linecap ="round" stroke-linejoin ="round" stroke-width ="2" d ="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" > </path > </svg > <svg v-if ="type == 'success'" xmlns ="http://www.w3.org/2000/svg" class ="stroke-current shrink-0 h-6 w-6" fill ="none" viewBox ="0 0 24 24" > <path stroke-linecap ="round" stroke-linejoin ="round" stroke-width ="2" d ="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg > <svg v-if ="type == 'warning'" xmlns ="http://www.w3.org/2000/svg" class ="stroke-current shrink-0 h-6 w-6" fill ="none" viewBox ="0 0 24 24" > <path stroke-linecap ="round" stroke-linejoin ="round" stroke-width ="2" d ="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> </svg > <svg v-if ="type == 'error'" xmlns ="http://www.w3.org/2000/svg" class ="stroke-current shrink-0 h-6 w-6" fill ="none" viewBox ="0 0 24 24" > <path stroke-linecap ="round" stroke-linejoin ="round" stroke-width ="2" d ="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg > <span > {{ messgae }}</span > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import { ref, computed } from 'vue' ;const alert = ref (null );const type = ref ("default" )const messgae = ref ("Hello world." );const visible = ref (false );function showAlert (msg, typeName = "default" ) { messgae.value = msg; type.value = typeName; visible.value = true ; setTimeout (() => visible.value = false , 2000 ); } defineExpose ({ showAlert });const alertTypeClass = computed (() => { let typeClass = "default" ; switch (type.value ) { case 'info' : typeClass = 'alert-info' ; break ; case 'success' : typeClass = 'alert-success' ; break ; case 'warning' : typeClass = 'alert-warning' ; break ; case 'error' : typeClass = 'alert-error' ; break ; default : break ; } return typeClass; })