主题切换

参考主题控制器。以下甄选了几个好看的主题。

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
/** @type {import('tailwindcss').Config} */
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",
], // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"]
darkTheme: "dark", // name of one of the included themes for dark mode
base: true, // applies background color and foreground color for root element by default
styled: true, // include daisyUI colors and design decisions for all components
utils: true, // adds responsive and modifier utility classes
prefix: "", // prefix for daisyUI classnames (components, modifiers and responsive class names. Not colors)
logs: true, // Shows info about daisyUI version and used config in the console when building your CSS
themeRoot: ":root", // The element that receives theme color CSS variables
},
}


我的主题切换器

其中 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
//themeStore.js
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;
})