feat: 暂存
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useCounterStore } from '@/stores/counter';
|
||||
import { useCounterStore } from '@/stores/counter.ts';
|
||||
|
||||
import { useDark, useToggle } from '@vueuse/core';
|
||||
const isDark = useDark();
|
||||
@@ -20,6 +20,8 @@ const store = useCounterStore();
|
||||
</el-icon>
|
||||
|
||||
<SvgIcon icon="lock"></SvgIcon>
|
||||
|
||||
<ChangeTheme />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
19
src/components/i18n/SelectLanguage.vue
Normal file
19
src/components/i18n/SelectLanguage.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click">
|
||||
<SvgIcon pointer icon="language"></SvgIcon>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="changeLang('zh')">中文</el-dropdown-item>
|
||||
<el-dropdown-item @click="changeLang('en')">English</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const { locale } = useI18n();
|
||||
const changeLang = (lang: string) => {
|
||||
locale.value = lang;
|
||||
localStorage.setItem('lang', lang);
|
||||
};
|
||||
</script>
|
||||
``
|
||||
33
src/components/icon/SvgIcon.vue
Normal file
33
src/components/icon/SvgIcon.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'icon',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
},
|
||||
pointer: {
|
||||
type: Boolean,
|
||||
},
|
||||
});
|
||||
|
||||
const symbolId = computed(() => {
|
||||
return `#${props.prefix}-${props.icon}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-icon :size="size" :color="color" :style="props.pointer ? { cursor: 'pointer' } : {}">
|
||||
<svg aria-hidden="true">
|
||||
<use :xlink:href="symbolId" :fill="color" />
|
||||
</svg>
|
||||
</el-icon>
|
||||
</template>
|
||||
8
src/components/icon/data.d.ts
vendored
Normal file
8
src/components/icon/data.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare namespace TYPE {
|
||||
type SvgIconType = {
|
||||
icon: string;
|
||||
size?: number;
|
||||
color?: string;
|
||||
prefix?: string;
|
||||
};
|
||||
}
|
||||
6
src/components/icon/useSvgIcon.ts
Normal file
6
src/components/icon/useSvgIcon.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { VNode } from 'vue';
|
||||
import SvgIcon from './SvgIcon.vue';
|
||||
|
||||
export const useSvgIcon = (props: TYPE.SvgIconType): VNode => {
|
||||
return h(SvgIcon, props);
|
||||
};
|
||||
66
src/components/theme/ChangeTheme.vue
Normal file
66
src/components/theme/ChangeTheme.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { useDark } from '@vueuse/core';
|
||||
import { useSvgIcon } from '../icon/useSvgIcon';
|
||||
const isDark = useDark();
|
||||
|
||||
const darkIcon = useSvgIcon({ icon: 'dark', color: '#fde047' });
|
||||
const lightIcon = useSvgIcon({ icon: 'light', color: '#fde047' });
|
||||
|
||||
// 添加动画效果
|
||||
function toggleTheme(event: MouseEvent) {
|
||||
const isAppearanceTransition =
|
||||
// @ts-expect-error 实验性质方法
|
||||
document.startViewTransition &&
|
||||
!window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
if (!isAppearanceTransition || !event) {
|
||||
return;
|
||||
}
|
||||
const x = event.clientX;
|
||||
const y = event.clientY;
|
||||
const endRadius = Math.hypot(
|
||||
Math.max(x, innerWidth - x),
|
||||
Math.max(y, innerHeight - y)
|
||||
);
|
||||
// @ts-expect-error: Transition API
|
||||
const transition = document.startViewTransition(async () => {
|
||||
isDark.value = !isDark.value;
|
||||
await nextTick();
|
||||
});
|
||||
transition.ready.then(() => {
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||
];
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: isDark.value ? [...clipPath].reverse() : clipPath,
|
||||
},
|
||||
{
|
||||
duration: 4500,
|
||||
easing: 'ease-in',
|
||||
pseudoElement: isDark.value
|
||||
? '::view-transition-old(root)'
|
||||
: '::view-transition-new(root)',
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<!-- :border-color="blackColor"
|
||||
:active-color="blackColor" -->
|
||||
|
||||
<template>
|
||||
<el-switch
|
||||
@click.stop="
|
||||
(e:MouseEvent) => {
|
||||
toggleTheme(e);
|
||||
}
|
||||
"
|
||||
:before-change="() => false"
|
||||
v-model="isDark"
|
||||
inline-prompt
|
||||
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #a1a1a1"
|
||||
:active-icon="darkIcon"
|
||||
:inactive-icon="lightIcon"
|
||||
></el-switch>
|
||||
</template>
|
||||
@@ -5,6 +5,7 @@ import { createMemoryHistory, createRouter } from 'vue-router';
|
||||
import App from './App.vue';
|
||||
import HelloWorld from './components/HelloWorld.vue';
|
||||
|
||||
// 引入黑色主题
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css';
|
||||
import Demo from './components/Demo.vue';
|
||||
import './style.css';
|
||||
|
||||
11
src/stores/global.ts
Normal file
11
src/stores/global.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useGlobalStore = defineStore('global', () => {
|
||||
const count = ref(0);
|
||||
const doubleCount = computed(() => count.value * 2);
|
||||
function increment() {
|
||||
count.value++;
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment };
|
||||
});
|
||||
145
src/style.css
145
src/style.css
@@ -1,79 +1,110 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
/* 清除 HTML 默认样式的 CSS Reset */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
/* 使字体大小在浏览器上更容易控制 */
|
||||
font-size: 100%;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
body {
|
||||
/* 确保 body 占满整个屏幕高度 */
|
||||
min-height: 100vh;
|
||||
text-rendering: optimizeSpeed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas,
|
||||
svg {
|
||||
/* 确保多媒体元素不超过其容器 */
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
/* 去除表单元素的默认样式,使用继承字体 */
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
/* 清除链接的默认下划线和颜色 */
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
ul,
|
||||
ol {
|
||||
/* 清除列表的默认缩进和样式 */
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
table {
|
||||
/* 确保表格的布局正确 */
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 确保所有元素都有一致的外边距、填充和边界盒模型 */
|
||||
* {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
/* 为所有浏览器设定一致的滚动行为 */
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
/* 去除按钮的默认边框 */
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
blockquote,
|
||||
q {
|
||||
/* 取消 blockquote 和 q 的默认引用样式 */
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
blockquote::before,
|
||||
blockquote::after,
|
||||
q::before,
|
||||
q::after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
// 添加主题切换动画
|
||||
::view-transition-new(root),
|
||||
::view-transition-old(root) {
|
||||
animation: none;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
::view-transition-old(root) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
::view-transition-new(root) {
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
html.dark::view-transition-old(root) {
|
||||
z-index: 2147483646;
|
||||
}
|
||||
html.dark::view-transition-new(root) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
12
src/vite-env.d.ts
vendored
12
src/vite-env.d.ts
vendored
@@ -1 +1,13 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
/**
|
||||
* 1. /// <reference types="vite/client" />:
|
||||
• 这行代码是一个三斜线指令,它告诉 TypeScript 编译器去引用 vite/client 的类型声明。
|
||||
• Vite 自带的 vite/client 模块中包含了 Vite 环境下常用的全局变量和工具的类型声明。比如:
|
||||
• import.meta: Vite 扩展了 ES Modules 中的 import.meta 对象,在 Vite 项目中有额外的属性如 import.meta.env,该属性中包含了一些 Vite 的环境变量。
|
||||
• import.meta.env: 通过它,你可以访问定义在 .env 文件或 VITE_ 前缀的自定义环境变量。这些变量在 vite/client 模块中有类型定义,能提供良好的开发体验和自动完成支持。
|
||||
2. vite-env.d.ts 文件的作用:
|
||||
• 这是一个全局的类型声明文件,通常用于声明在整个项目中可以使用的全局类型。
|
||||
• 该文件通常位于项目的根目录下,它使 TypeScript 知道 Vite 环境下的一些特殊语法(如 import.meta)以及任何自定义的全局类型。
|
||||
• 这个文件的存在确保在使用 Vite 特定功能(例如动态导入、环境变量)时,不会出现 TypeScript 编译错误,并能获得相应的类型提示。
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user