style
This commit is contained in:
+31
-1
@@ -1,15 +1,45 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watchEffect, provide } from 'vue'
|
import { ref, watchEffect, provide, onMounted } from 'vue'
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import ThemeToggle from './components/ThemeToggle.vue'
|
import ThemeToggle from './components/ThemeToggle.vue'
|
||||||
|
|
||||||
const isDarkMode = ref(false)
|
const isDarkMode = ref(false)
|
||||||
|
|
||||||
|
// 检查系统颜色模式
|
||||||
|
const checkSystemColorScheme = () => {
|
||||||
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从本地存储获取用户之前的选择
|
||||||
|
const getUserPreference = () => {
|
||||||
|
const storedPreference = localStorage.getItem('colorMode')
|
||||||
|
if (storedPreference) {
|
||||||
|
return storedPreference === 'dark'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置颜色模式
|
||||||
|
const setColorMode = (isDark: boolean) => {
|
||||||
|
isDarkMode.value = isDark
|
||||||
|
localStorage.setItem('colorMode', isDark ? 'dark' : 'light')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const userPreference = getUserPreference()
|
||||||
|
if (userPreference !== null) {
|
||||||
|
setColorMode(userPreference)
|
||||||
|
} else {
|
||||||
|
setColorMode(checkSystemColorScheme())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
document.documentElement.classList.toggle('dark', isDarkMode.value)
|
document.documentElement.classList.toggle('dark', isDarkMode.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
provide('isDarkMode', isDarkMode)
|
provide('isDarkMode', isDarkMode)
|
||||||
|
provide('setColorMode', setColorMode)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<div class="border-progress-container" ref="container">
|
||||||
|
<canvas ref="canvas" class="border-progress-canvas"></canvas>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
progress: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const container = ref<HTMLDivElement | null>(null);
|
||||||
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||||
|
let ctx: CanvasRenderingContext2D | null = null;
|
||||||
|
|
||||||
|
const drawProgress = () => {
|
||||||
|
if (!ctx || !canvas.value || !container.value) return;
|
||||||
|
|
||||||
|
const width = container.value.clientWidth;
|
||||||
|
const height = container.value.clientHeight;
|
||||||
|
canvas.value.width = width;
|
||||||
|
canvas.value.height = height;
|
||||||
|
|
||||||
|
const borderWidth = 4; // 边框宽度
|
||||||
|
const cornerRadius = 8; // 拐角半径
|
||||||
|
ctx.lineWidth = borderWidth;
|
||||||
|
|
||||||
|
// 创建渐变
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, width, height);
|
||||||
|
gradient.addColorStop(0, '#4f46e5'); // 靛蓝色
|
||||||
|
gradient.addColorStop(0.5, '#7c3aed'); // 紫色
|
||||||
|
gradient.addColorStop(1, '#db2777'); // 粉色
|
||||||
|
|
||||||
|
// 绘制背景
|
||||||
|
ctx.strokeStyle = 'rgba(229, 231, 235, 0.2)'; // 淡灰色半透明
|
||||||
|
drawRoundedRect(ctx, borderWidth / 2, borderWidth / 2, width - borderWidth, height - borderWidth, cornerRadius);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 计算进度
|
||||||
|
const totalLength = (width + height) * 2 - 8 * cornerRadius + 2 * Math.PI * cornerRadius;
|
||||||
|
const progressLength = (totalLength * props.progress) / 100;
|
||||||
|
|
||||||
|
// 绘制进度
|
||||||
|
ctx.strokeStyle = gradient;
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
ctx.lineJoin = 'round';
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
let remainingLength = progressLength;
|
||||||
|
const halfBorder = borderWidth / 2;
|
||||||
|
const innerWidth = width - borderWidth;
|
||||||
|
const innerHeight = height - borderWidth;
|
||||||
|
|
||||||
|
// 上边
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const topLength = Math.min(innerWidth - 2 * cornerRadius, remainingLength);
|
||||||
|
ctx.moveTo(cornerRadius + halfBorder, halfBorder);
|
||||||
|
ctx.lineTo(topLength + cornerRadius + halfBorder, halfBorder);
|
||||||
|
remainingLength -= topLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右上角
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const angle = Math.min(Math.PI / 2, remainingLength / cornerRadius);
|
||||||
|
ctx.arc(innerWidth - cornerRadius + halfBorder, cornerRadius + halfBorder, cornerRadius, -Math.PI / 2, angle - Math.PI / 2, false);
|
||||||
|
remainingLength -= angle * cornerRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右边
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const rightLength = Math.min(innerHeight - 2 * cornerRadius, remainingLength);
|
||||||
|
ctx.lineTo(innerWidth + halfBorder, rightLength + cornerRadius + halfBorder);
|
||||||
|
remainingLength -= rightLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右下角
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const angle = Math.min(Math.PI / 2, remainingLength / cornerRadius);
|
||||||
|
ctx.arc(innerWidth - cornerRadius + halfBorder, innerHeight - cornerRadius + halfBorder, cornerRadius, 0, angle, false);
|
||||||
|
remainingLength -= angle * cornerRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下边
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const bottomLength = Math.min(innerWidth - 2 * cornerRadius, remainingLength);
|
||||||
|
ctx.lineTo(innerWidth - bottomLength - cornerRadius + halfBorder, innerHeight + halfBorder);
|
||||||
|
remainingLength -= bottomLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左下角
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const angle = Math.min(Math.PI / 2, remainingLength / cornerRadius);
|
||||||
|
ctx.arc(cornerRadius + halfBorder, innerHeight - cornerRadius + halfBorder, cornerRadius, Math.PI / 2, Math.PI / 2 + angle, false);
|
||||||
|
remainingLength -= angle * cornerRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左边
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const leftLength = Math.min(innerHeight - 2 * cornerRadius, remainingLength);
|
||||||
|
ctx.lineTo(halfBorder, innerHeight - leftLength - cornerRadius + halfBorder);
|
||||||
|
remainingLength -= leftLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左上角
|
||||||
|
if (remainingLength > 0) {
|
||||||
|
const angle = Math.min(Math.PI / 2, remainingLength / cornerRadius);
|
||||||
|
ctx.arc(cornerRadius + halfBorder, cornerRadius + halfBorder, cornerRadius, Math.PI, Math.PI + angle, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
function drawRoundedRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + radius, y);
|
||||||
|
ctx.lineTo(x + width - radius, y);
|
||||||
|
ctx.arcTo(x + width, y, x + width, y + radius, radius);
|
||||||
|
ctx.lineTo(x + width, y + height - radius);
|
||||||
|
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
|
||||||
|
ctx.lineTo(x + radius, y + height);
|
||||||
|
ctx.arcTo(x, y + height, x, y + height - radius, radius);
|
||||||
|
ctx.lineTo(x, y + radius);
|
||||||
|
ctx.arcTo(x, y, x + radius, y, radius);
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (canvas.value) {
|
||||||
|
ctx = canvas.value.getContext('2d');
|
||||||
|
drawProgress();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.progress, drawProgress);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.border-progress-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-progress-canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { inject } from 'vue'
|
||||||
import { SunIcon, MoonIcon } from 'lucide-vue-next'
|
import { SunIcon, MoonIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
const props = defineProps<{
|
const isDarkMode = inject('isDarkMode') as { value: boolean }
|
||||||
modelValue: boolean
|
const setColorMode = inject('setColorMode') as (isDark: boolean) => void
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const toggleColorMode = () => {
|
||||||
(e: 'update:modelValue', value: boolean): void
|
setColorMode(!isDarkMode.value)
|
||||||
}>()
|
}
|
||||||
|
|
||||||
const isDarkMode = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
@click="isDarkMode = !isDarkMode"
|
@click="toggleColorMode"
|
||||||
class="fixed top-4 right-4 p-2 rounded-full transition-all duration-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transform hover:rotate-180"
|
class="fixed top-4 right-4 p-2 rounded-full transition-all duration-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transform hover:rotate-180"
|
||||||
:class="isDarkMode ? 'bg-gray-800 text-yellow-300' : 'bg-white text-gray-800'"
|
:class="isDarkMode ? 'bg-gray-800 text-yellow-300' : 'bg-white text-gray-800'"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<!-- 文件上传区域 -->
|
<!-- 文件上传区域 -->
|
||||||
<div
|
<div
|
||||||
v-if="sendType === 'file'"
|
v-if="sendType === 'file'"
|
||||||
class="rounded-xl p-6 flex flex-col items-center justify-center border-2 border-dashed transition-all duration-300 group cursor-pointer"
|
class="rounded-xl p-6 flex flex-col items-center justify-center border-2 border-dashed transition-all duration-300 group cursor-pointer relative"
|
||||||
:class="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-800 bg-opacity-50 border-gray-600 hover:border-indigo-500'
|
? 'bg-gray-800 bg-opacity-50 border-gray-600 hover:border-indigo-500'
|
||||||
@@ -65,6 +65,9 @@
|
|||||||
@change="handleFileUpload"
|
@change="handleFileUpload"
|
||||||
ref="fileInput"
|
ref="fileInput"
|
||||||
/>
|
/>
|
||||||
|
<div class="absolute inset-0 w-full h-full" v-if="uploadProgress > 0">
|
||||||
|
<BorderProgressBar :progress="uploadProgress" />
|
||||||
|
</div>
|
||||||
<UploadCloudIcon
|
<UploadCloudIcon
|
||||||
:class="[
|
:class="[
|
||||||
'w-16 h-16 transition-colors duration-300',
|
'w-16 h-16 transition-colors duration-300',
|
||||||
@@ -172,32 +175,59 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, inject } from 'vue'
|
import { ref, inject } from 'vue'
|
||||||
import { UploadCloudIcon, SendIcon } from 'lucide-vue-next'
|
import { UploadCloudIcon, SendIcon } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import BorderProgressBar from '../components/BorderProgressBar.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
// 修改这里:使用 inject 来获取 isDarkMode
|
|
||||||
const isDarkMode = inject('isDarkMode')
|
const isDarkMode = inject('isDarkMode')
|
||||||
|
|
||||||
const sendType = ref('file')
|
const sendType = ref('file')
|
||||||
const selectedFile:any = ref(null)
|
const selectedFile = ref<File | null>(null)
|
||||||
const textContent = ref('')
|
const textContent = ref('')
|
||||||
const fileInput:any = ref(null)
|
const fileInput = ref<HTMLInputElement | null>(null)
|
||||||
const expirationMethod = ref('time')
|
const expirationMethod = ref('time')
|
||||||
const expirationTime = ref('')
|
const expirationTime = ref('')
|
||||||
const expirationViews = ref('')
|
const expirationViews = ref('')
|
||||||
|
const uploadProgress = ref(0)
|
||||||
|
|
||||||
const triggerFileUpload = () => {
|
const triggerFileUpload = () => {
|
||||||
fileInput.value.click()
|
fileInput.value?.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileUpload = (event:any) => {
|
const handleFileUpload = (event: Event) => {
|
||||||
selectedFile.value = event.target.files[0]
|
const target = event.target as HTMLInputElement
|
||||||
|
if (target.files && target.files.length > 0) {
|
||||||
|
selectedFile.value = target.files[0]
|
||||||
|
simulateFileUpload()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileDrop = (event:any) => {
|
const handleFileDrop = (event: DragEvent) => {
|
||||||
|
if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
|
||||||
selectedFile.value = event.dataTransfer.files[0]
|
selectedFile.value = event.dataTransfer.files[0]
|
||||||
|
simulateFileUpload()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const simulateFileUpload = () => {
|
||||||
|
uploadProgress.value = 0
|
||||||
|
const duration = 2000; // 总动画时长(毫秒)
|
||||||
|
const steps = 100; // 动画步数
|
||||||
|
const stepDuration = duration / steps;
|
||||||
|
const stepIncrement = 100 / steps;
|
||||||
|
|
||||||
|
const animate = (step: number) => {
|
||||||
|
if (step <= steps) {
|
||||||
|
uploadProgress.value = step * stepIncrement;
|
||||||
|
setTimeout(() => animate(step + 1), stepDuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
animate(0);
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (sendType.value === 'file' && !selectedFile.value) {
|
if (sendType.value === 'file' && !selectedFile.value) {
|
||||||
alert('请选择要上传的文件')
|
alert('请选择要上传的文件')
|
||||||
|
|||||||
Reference in New Issue
Block a user