初始化

This commit is contained in:
李琦
2026-01-15 14:57:36 +08:00
parent 20e6e9fb3f
commit a43dc4c7fb
7 changed files with 107 additions and 15878 deletions

1
client/.gitgnore Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,33 @@
<template>
<code ref="codeBlock" :class="languageClass">{{ code }}</code>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, nextTick } from 'vue'
import hljs from 'highlight.js'
const props = defineProps<{
code: string
language: string
}>()
const codeBlock = ref<HTMLElement | null>(null)
const languageClass = `language-${props.language || 'plaintext'}`
const highlight = () => {
if (codeBlock.value) {
// Reset internal state if needed, though highlighting overwrites innerHTML
delete codeBlock.value.dataset.highlighted
hljs.highlightElement(codeBlock.value)
}
}
onMounted(() => {
highlight()
})
watch(() => props.code, () => {
nextTick(highlight)
})
</script>

View File

@@ -1,11 +1,12 @@
<template>
<div
v-if="isOpen"
class="fixed inset-0 z-[100] bg-black/90 backdrop-blur-sm flex items-center justify-center p-4 md:p-10 transition-opacity duration-300"
class="fixed inset-0 z-[100] flex items-center justify-center p-4 md:p-10 transition-opacity duration-300 pointer-events-auto"
:class="{ 'opacity-0': isAnimating, 'opacity-100': !isAnimating }"
>
<div class="absolute inset-0 bg-black/90 backdrop-blur-sm" @click="close"></div>
<div
class="bg-[#0a0a0c] border border-white/10 w-full max-w-6xl h-full md:h-[80vh] rounded-2xl flex flex-col overflow-hidden shadow-2xl transform transition-transform duration-300"
class="relative z-[101] bg-[#0a0a0c] border border-white/10 w-full max-w-6xl h-[80vh] rounded-2xl flex flex-col overflow-hidden shadow-2xl transform transition-transform duration-300"
:class="{ 'scale-95': isAnimating, 'scale-100': !isAnimating }"
>
<!-- Header -->
@@ -23,10 +24,10 @@
<div class="flex-1 flex flex-col md:flex-row h-full overflow-hidden">
<!-- Code Section -->
<div class="w-full md:w-1/2 bg-[#0d0d0d] overflow-auto p-6 border-r border-white/10 relative group">
<button class="absolute top-4 right-4 text-xs bg-white/10 px-2 py-1 rounded text-white/50 hover:text-white" @click="copyCode">
<button class="absolute top-4 right-4 text-xs bg-white/10 px-2 py-1 rounded text-white/50 hover:text-white z-10" @click="copyCode">
复制
</button>
<pre class="font-mono text-sm leading-relaxed text-gray-300">{{ code }}</pre>
<pre class="font-mono text-sm leading-relaxed"><code ref="codeBlock" :class="languageClass">{{ code }}</code></pre>
</div>
<!-- Preview Section -->
@@ -72,7 +73,8 @@
</template>
<script setup lang="ts">
import { ref, watch, onMounted, computed } from 'vue'
import { ref, watch, onMounted, computed, nextTick } from 'vue'
import hljs from 'highlight.js'
const props = defineProps<{
isOpen: boolean
@@ -87,6 +89,24 @@ const isAnimating = ref(true)
const mouseX = ref(0)
const mouseY = ref(0)
const containerRect = ref<DOMRect | null>(null)
const codeBlock = ref<HTMLElement | null>(null)
const languageClass = computed(() => {
const map: Record<string, string> = {
'mouse': 'javascript',
'glass': 'css',
'noise': 'xml',
'animate': 'css'
}
return `language-${map[props.type] || 'plaintext'}`
})
const highlightCode = () => {
if (codeBlock.value) {
delete codeBlock.value.dataset.highlighted
hljs.highlightElement(codeBlock.value)
}
}
// Watch isOpen to handle entry/exit animations
watch(() => props.isOpen, (newVal) => {
@@ -101,6 +121,11 @@ watch(() => props.isOpen, (newVal) => {
setTimeout(() => {
if (window.lucide) window.lucide.createIcons()
}, 50)
// Highlight code
nextTick(() => {
highlightCode()
})
} else {
document.body.style.overflow = ''
isAnimating.value = true

View File

@@ -1,6 +1,7 @@
import { createApp } from "vue"
import App from "./App.vue"
import "./style.css"
import "highlight.js/styles/atom-one-dark.css"
import router from "./router"
const app = createApp(App)

View File

@@ -39,24 +39,39 @@
<div class="text-xs font-mono text-art-accent opacity-0 group-hover:opacity-100 transition-opacity">点击运行 -></div>
</div>
<!-- Code Preview (Styled) -->
<div class="p-6 overflow-x-auto font-mono text-sm leading-relaxed pointer-events-none opacity-80 group-hover:opacity-100 transition-opacity">
<pre class="text-gray-300">{{ snippet.code }}</pre>
<div class="p-6 overflow-hidden font-mono text-sm leading-relaxed pointer-events-none opacity-80 group-hover:opacity-100 transition-opacity">
<pre class="whitespace-pre-wrap break-all line-clamp-4"><CodeBlock :code="snippet.code" :language="getSnippetLang(snippet.type)" /></pre>
</div>
</div>
</div>
</div>
<!-- Snippet Modal -->
<SnippetModal
:is-open="modalOpen"
:title="currentSnippet.title"
:code="currentSnippet.code"
:type="currentSnippet.type"
@close="closeSnippet"
/>
</section>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeMount } from 'vue'
import { ref, onMounted, onBeforeMount, nextTick } from 'vue'
import { fetchSnippets, Snippet } from '../services/api'
import { useScrollAnimation } from '../composables/useScrollAnimation'
import SnippetModal from '../components/SnippetModal.vue'
import CodeBlock from '../components/CodeBlock.vue'
import hljs from 'highlight.js'
const snippets = ref<Snippet[]>([])
const loading = ref(false)
const error = ref('')
const modalOpen = ref(false)
const currentSnippet = ref<Snippet>({ id: '', title: '', code: '', type: '' })
const { initObserver } = useScrollAnimation()
const fetchSnippetsData = async () => {
@@ -94,6 +109,8 @@ export const useMousePosition = () => {
code: `.glass-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
}`,
type: 'glass'
@@ -102,7 +119,7 @@ export const useMousePosition = () => {
id: 'noise',
title: 'SVG 噪点纹理滤镜',
code: `<filter id="noise">
<feTurbulence type="fractalNoise" baseFrequency="0.8" /
<feTurbulence type="fractalNoise" baseFrequency="0.8" />
</filter>`,
type: 'noise'
},
@@ -127,24 +144,23 @@ export const useMousePosition = () => {
}
}
const getSnippetType = (type: string) => {
const typeMap: Record<string, string> = {
javascript: 'JavaScript',
css: 'CSS',
html: 'HTML',
mouse: 'React Hook',
glass: 'CSS',
noise: 'SVG',
animate: 'CSS Animation'
const getSnippetLang = (type: string) => {
const map: Record<string, string> = {
'mouse': 'javascript',
'glass': 'css',
'noise': 'xml',
'animate': 'css'
}
return typeMap[type] || type
return map[type] || 'plaintext'
}
const openSnippet = (snippet: any) => {
// 使用全局函数打开代码片段模态框
if (window.showSnippet) {
window.showSnippet(snippet.title, snippet.code)
}
const openSnippet = (snippet: Snippet) => {
currentSnippet.value = snippet
modalOpen.value = true
}
const closeSnippet = () => {
modalOpen.value = false
}
onMounted(() => {

View File

@@ -43,6 +43,9 @@
.mac-code-block::before { content: ''; position: absolute; top: 1rem; left: 1.5rem; width: 0.75rem; height: 0.75rem; border-radius: 50%; background: #ff5f56; box-shadow: 1.25rem 0 0 #ffbd2e, 2.5rem 0 0 #27c93f; }
.code-keyword { color: #c678dd; } .code-func { color: #61afef; } .code-string { color: #98c379; } .code-comment { color: #5c6370; font-style: italic; }
/* Highlight.js Override */
.hljs { background: transparent !important; padding: 0 !important; }
/* Layout Specifics */
.work-detail-page { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #050505; z-index: 100; overflow-y: auto; }
.gallery-image { width: 100%; margin-bottom: 4rem; filter: grayscale(20%); transition: filter 0.5s; }