332 lines
9.4 KiB
Vue
332 lines
9.4 KiB
Vue
<template>
|
||
<div class="space-y-6">
|
||
<!-- 标题和描述 -->
|
||
<div class="card p-6">
|
||
<h2 class="text-2xl font-bold text-primary mb-4">CSS渐变生成器</h2>
|
||
<p class="text-secondary mb-4">创建线性渐变和径向渐变,生成CSS代码并实时预览效果</p>
|
||
</div>
|
||
|
||
<!-- 渐变类型选择 -->
|
||
<div class="card p-6">
|
||
<h3 class="text-lg font-semibold text-primary mb-4">渐变类型</h3>
|
||
<div class="flex gap-3">
|
||
<button
|
||
@click="gradientType = 'linear'"
|
||
:class="gradientType === 'linear' ? 'btn-primary' : 'btn-secondary'"
|
||
class="px-4 py-2 rounded-lg"
|
||
>
|
||
线性渐变
|
||
</button>
|
||
<button
|
||
@click="gradientType = 'radial'"
|
||
:class="gradientType === 'radial' ? 'btn-primary' : 'btn-secondary'"
|
||
class="px-4 py-2 rounded-lg"
|
||
>
|
||
径向渐变
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 线性渐变设置 -->
|
||
<div v-if="gradientType === 'linear'" class="card p-6">
|
||
<h3 class="text-lg font-semibold text-primary mb-4">角度设置</h3>
|
||
<div class="flex items-center gap-4">
|
||
<input
|
||
v-model.number="linearAngle"
|
||
type="range"
|
||
min="0"
|
||
max="360"
|
||
class="flex-1"
|
||
/>
|
||
<input
|
||
v-model.number="linearAngle"
|
||
type="number"
|
||
min="0"
|
||
max="360"
|
||
class="input w-20"
|
||
/>
|
||
<span>°</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 径向渐变设置 -->
|
||
<div v-if="gradientType === 'radial'" class="card p-6">
|
||
<h3 class="text-lg font-semibold text-primary mb-4">径向渐变设置</h3>
|
||
|
||
<!-- 形状 -->
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-secondary mb-2">形状</label>
|
||
<div class="flex gap-3">
|
||
<button
|
||
v-for="shape in radialShapes"
|
||
:key="shape.value"
|
||
@click="radialShape = shape.value"
|
||
:class="{
|
||
'btn-primary': radialShape === shape.value,
|
||
'btn-secondary': radialShape !== shape.value
|
||
}"
|
||
class="px-4 py-2 rounded transition-colors"
|
||
>
|
||
{{ shape.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 大小 -->
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-secondary mb-2">大小</label>
|
||
<div class="grid grid-cols-2 gap-2">
|
||
<button
|
||
v-for="size in radialSizes"
|
||
:key="size.value"
|
||
@click="radialSize = size.value"
|
||
:class="{
|
||
'btn-primary': radialSize === size.value,
|
||
'btn-secondary': radialSize !== size.value
|
||
}"
|
||
class="px-3 py-2 text-sm rounded transition-colors"
|
||
>
|
||
{{ size.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 位置 -->
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-secondary mb-2">位置</label>
|
||
<div class="grid grid-cols-3 gap-2">
|
||
<button
|
||
v-for="position in radialPositions"
|
||
:key="position.value"
|
||
@click="radialPosition = position.value"
|
||
:class="{
|
||
'btn-primary': radialPosition === position.value,
|
||
'btn-secondary': radialPosition !== position.value
|
||
}"
|
||
class="px-3 py-2 text-sm rounded transition-colors"
|
||
>
|
||
{{ position.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 颜色设置 -->
|
||
<div class="card p-6">
|
||
<h3 class="text-lg font-semibold text-primary mb-4">颜色设置</h3>
|
||
<div class="space-y-3 mb-4">
|
||
<div v-for="(color, index) in colors" :key="index" class="flex items-center gap-3">
|
||
<input
|
||
v-model="color.color"
|
||
type="color"
|
||
class="w-12 h-10 rounded"
|
||
/>
|
||
<input
|
||
v-model="color.color"
|
||
type="text"
|
||
class="input flex-1"
|
||
/>
|
||
<input
|
||
v-model.number="color.position"
|
||
type="number"
|
||
min="0"
|
||
max="100"
|
||
class="input w-20"
|
||
/>
|
||
<span>%</span>
|
||
<button
|
||
@click="removeColor(index)"
|
||
:disabled="colors.length <= 2"
|
||
class="btn-secondary"
|
||
>
|
||
删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<button @click="addColor" class="btn-primary">添加颜色</button>
|
||
</div>
|
||
|
||
<!-- 预览 -->
|
||
<div class="card p-6">
|
||
<h3 class="text-lg font-semibold text-primary mb-4">预览</h3>
|
||
<div
|
||
class="w-full h-40 rounded-lg border"
|
||
:style="{ background: generatedCSS }"
|
||
></div>
|
||
</div>
|
||
|
||
<!-- 生成的CSS -->
|
||
<div class="card p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-semibold text-primary">生成的CSS</h3>
|
||
<button @click="copyCSS" class="btn-secondary">复制</button>
|
||
</div>
|
||
<textarea
|
||
:value="generatedCSS"
|
||
readonly
|
||
class="input w-full h-20 font-mono"
|
||
></textarea>
|
||
</div>
|
||
|
||
<!-- 使用说明 -->
|
||
<div class="card p-6">
|
||
<h3 class="text-lg font-semibold text-primary mb-4">使用说明</h3>
|
||
<div class="prose text-secondary max-w-none">
|
||
<ul class="space-y-2">
|
||
<li><strong>线性渐变:</strong>沿直线方向的颜色过渡,可调整角度和方向</li>
|
||
<li><strong>径向渐变:</strong>从中心点向外辐射的颜色过渡,可调整形状、大小和位置</li>
|
||
<li><strong>颜色设置:</strong>点击颜色块选择颜色,调整位置百分比控制渐变分布</li>
|
||
<li><strong>预设渐变:</strong>提供多种常用渐变效果,点击即可应用</li>
|
||
<li><strong>实时预览:</strong>所有修改都会实时显示在预览区域</li>
|
||
<li><strong>代码生成:</strong>自动生成标准CSS代码,支持复制完整规则或仅background属性</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue'
|
||
|
||
// 颜色接口
|
||
interface ColorStop {
|
||
color: string
|
||
position: number
|
||
}
|
||
|
||
// 渐变类型
|
||
const gradientTypes = [
|
||
{ label: '线性渐变', value: 'linear' },
|
||
{ label: '径向渐变', value: 'radial' }
|
||
]
|
||
|
||
// 线性渐变方向
|
||
const linearDirections = [
|
||
{ label: '向右', value: 90 },
|
||
{ label: '向左', value: 270 },
|
||
{ label: '向下', value: 180 },
|
||
{ label: '向上', value: 0 },
|
||
{ label: '右下', value: 135 },
|
||
{ label: '左上', value: 315 },
|
||
{ label: '右上', value: 45 },
|
||
{ label: '左下', value: 225 }
|
||
]
|
||
|
||
// 径向渐变形状
|
||
const radialShapes = [
|
||
{ label: '椭圆', value: 'ellipse' },
|
||
{ label: '圆形', value: 'circle' }
|
||
]
|
||
|
||
// 径向渐变大小
|
||
const radialSizes = [
|
||
{ label: '最近边', value: 'closest-side' },
|
||
{ label: '最近角', value: 'closest-corner' },
|
||
{ label: '最远边', value: 'farthest-side' },
|
||
{ label: '最远角', value: 'farthest-corner' }
|
||
]
|
||
|
||
// 径向渐变位置
|
||
const radialPositions = [
|
||
{ label: '左上', value: 'top left' },
|
||
{ label: '上方', value: 'top' },
|
||
{ label: '右上', value: 'top right' },
|
||
{ label: '左侧', value: 'left' },
|
||
{ label: '中心', value: 'center' },
|
||
{ label: '右侧', value: 'right' },
|
||
{ label: '左下', value: 'bottom left' },
|
||
{ label: '下方', value: 'bottom' },
|
||
{ label: '右下', value: 'bottom right' }
|
||
]
|
||
|
||
// 响应式状态
|
||
const gradientType = ref('linear')
|
||
const linearAngle = ref(90)
|
||
const radialShape = ref('ellipse')
|
||
const radialSize = ref('farthest-corner')
|
||
const radialPosition = ref('center')
|
||
|
||
const colors = ref<ColorStop[]>([
|
||
{ color: '#667eea', position: 0 },
|
||
{ color: '#764ba2', position: 100 }
|
||
])
|
||
|
||
// 计算生成的CSS
|
||
const generatedCSS = computed(() => {
|
||
const colorStops = colors.value
|
||
.sort((a, b) => a.position - b.position)
|
||
.map(color => `${color.color} ${color.position}%`)
|
||
.join(', ')
|
||
|
||
if (gradientType.value === 'linear') {
|
||
return `linear-gradient(${linearAngle.value}deg, ${colorStops})`
|
||
} else {
|
||
return `radial-gradient(${radialShape.value} ${radialSize.value} at ${radialPosition.value}, ${colorStops})`
|
||
}
|
||
})
|
||
|
||
// 添加颜色
|
||
const addColor = () => {
|
||
const newPosition = colors.value.length > 0
|
||
? Math.max(...colors.value.map(c => c.position)) + 10
|
||
: 50
|
||
|
||
colors.value.push({
|
||
color: '#000000',
|
||
position: Math.min(newPosition, 100)
|
||
})
|
||
}
|
||
|
||
// 删除颜色
|
||
const removeColor = (index: number) => {
|
||
if (colors.value.length > 2) {
|
||
colors.value.splice(index, 1)
|
||
}
|
||
}
|
||
|
||
// 复制CSS
|
||
const copyCSS = async () => {
|
||
try {
|
||
await navigator.clipboard.writeText(generatedCSS.value)
|
||
alert('CSS代码已复制到剪贴板')
|
||
} catch (error) {
|
||
console.error('复制失败:', error)
|
||
alert('复制失败,请手动复制')
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.slider {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
height: 6px;
|
||
border-radius: 3px;
|
||
background: linear-gradient(to right, #ddd, #ddd);
|
||
outline: none;
|
||
}
|
||
|
||
.slider::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: var(--primary-color);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.slider::-moz-range-thumb {
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: var(--primary-color);
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
.prose ul {
|
||
list-style-type: disc;
|
||
padding-left: 1.5rem;
|
||
}
|
||
</style> |