239 lines
7.0 KiB
Vue
239 lines
7.0 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="p-6 max-w-3xl mx-auto bg-white rounded-xl shadow-lg space-y-6">
|
|||
|
|
<h2 class="text-2xl font-bold text-gray-900">帮提服务图标生成器</h2>
|
|||
|
|
|
|||
|
|
<!-- Icon Preview -->
|
|||
|
|
<div class="flex justify-center">
|
|||
|
|
<div class="w-36 h-36 bg-gray-50 rounded-lg flex items-center justify-center">
|
|||
|
|
<svg
|
|||
|
|
ref="iconRef"
|
|||
|
|
:width="BASE_SIZE"
|
|||
|
|
:height="BASE_SIZE"
|
|||
|
|
:viewBox="`0 0 ${BASE_SIZE} ${BASE_SIZE}`"
|
|||
|
|
>
|
|||
|
|
<!-- Base layer with standard background color -->
|
|||
|
|
<rect
|
|||
|
|
:x="x"
|
|||
|
|
:y="y"
|
|||
|
|
:width="ICON_SIZE"
|
|||
|
|
:height="ICON_SIZE"
|
|||
|
|
:fill="selectedCombo.baseColor"
|
|||
|
|
:rx="CORNER_RADIUS"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- Gradient layer -->
|
|||
|
|
<rect
|
|||
|
|
:x="x"
|
|||
|
|
:y="y"
|
|||
|
|
:width="ICON_SIZE"
|
|||
|
|
:height="ICON_SIZE"
|
|||
|
|
:fill="`url(#gradient-${selectedCombo.name})`"
|
|||
|
|
:rx="CORNER_RADIUS"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- White expression layer with shadow -->
|
|||
|
|
<g :filter="`url(#shadow-${selectedCombo.name})`">
|
|||
|
|
<rect
|
|||
|
|
:x="x"
|
|||
|
|
:y="y"
|
|||
|
|
:width="ICON_SIZE"
|
|||
|
|
:height="ICON_SIZE"
|
|||
|
|
fill="white"
|
|||
|
|
:rx="CORNER_RADIUS"
|
|||
|
|
/>
|
|||
|
|
</g>
|
|||
|
|
|
|||
|
|
<!-- Service Icon - Two people interaction -->
|
|||
|
|
<g :transform="`translate(${x}, ${y}) scale(${SCALE})`">
|
|||
|
|
<!-- Standing Person -->
|
|||
|
|
<path
|
|||
|
|
d="M8 4C9.1 4 10 4.9 10 6C10 7.1 9.1 8 8 8C6.9 8 6 7.1 6 6C6 4.9 6.9 4 8 4ZM8 9C10.2 9 12 10.8 12 13V16H4V13C4 10.8 5.8 9 8 9Z"
|
|||
|
|
:fill="selectedCombo.shadowColor"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- Person with Luggage -->
|
|||
|
|
<path
|
|||
|
|
d="M18 4C19.1 4 20 4.9 20 6C20 7.1 19.1 8 18 8C16.9 8 16 7.1 16 6C16 4.9 16.9 4 18 4ZM18 9C20.2 9 22 10.8 22 13V16H18V13C18 10.8 16.2 9 14 9C13.7 9 13.4 9 13.1 9.1C13.7 10 14 11 14 12V16H16V22H14V18H10V22H8V16H10V12C10 11 9.7 10 9.1 9.1C8.8 9 8.5 9 8.2 9"
|
|||
|
|
:fill="selectedCombo.shadowColor"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- Luggage -->
|
|||
|
|
<path
|
|||
|
|
d="M18 13V20H14V13H18Z"
|
|||
|
|
:fill="selectedCombo.shadowColor"
|
|||
|
|
/>
|
|||
|
|
</g>
|
|||
|
|
|
|||
|
|
<!-- Gradient and shadow definitions -->
|
|||
|
|
<defs>
|
|||
|
|
<linearGradient
|
|||
|
|
:id="`gradient-${selectedCombo.name}`"
|
|||
|
|
x1="0"
|
|||
|
|
y1="0"
|
|||
|
|
x2="1"
|
|||
|
|
y2="1"
|
|||
|
|
>
|
|||
|
|
<stop offset="0%" :stop-color="selectedCombo.gradients[0]" />
|
|||
|
|
<stop offset="100%" :stop-color="selectedCombo.gradients[1]" />
|
|||
|
|
</linearGradient>
|
|||
|
|
<filter
|
|||
|
|
:id="`shadow-${selectedCombo.name}`"
|
|||
|
|
x="-20%"
|
|||
|
|
y="-20%"
|
|||
|
|
width="140%"
|
|||
|
|
height="140%"
|
|||
|
|
>
|
|||
|
|
<feDropShadow
|
|||
|
|
dx="0"
|
|||
|
|
:dy="SCALE * 0.5"
|
|||
|
|
:stdDeviation="SCALE * 0.5"
|
|||
|
|
:flood-color="selectedCombo.shadowColor"
|
|||
|
|
flood-opacity="0.5"
|
|||
|
|
/>
|
|||
|
|
</filter>
|
|||
|
|
</defs>
|
|||
|
|
</svg>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Color Combination Selection -->
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">颜色组合</label>
|
|||
|
|
<div class="grid grid-cols-4 gap-4">
|
|||
|
|
<button
|
|||
|
|
v-for="(combo, index) in COLOR_COMBINATIONS"
|
|||
|
|
:key="index"
|
|||
|
|
@click="setSelectedCombo(combo)"
|
|||
|
|
class="h-16 relative rounded-lg overflow-hidden"
|
|||
|
|
:class="{ 'ring-2 ring-blue-500': selectedCombo === combo }"
|
|||
|
|
>
|
|||
|
|
<div class="absolute inset-0" :style="{ backgroundColor: combo.baseColor }" />
|
|||
|
|
<div
|
|||
|
|
class="absolute inset-0"
|
|||
|
|
:style="{
|
|||
|
|
background: `linear-gradient(135deg, ${combo.gradients[0]}, ${combo.gradients[1]})`
|
|||
|
|
}"
|
|||
|
|
/>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Download Button -->
|
|||
|
|
<div class="flex justify-center">
|
|||
|
|
<button
|
|||
|
|
@click="downloadIcon"
|
|||
|
|
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|||
|
|
>
|
|||
|
|
<download-icon class="mr-2 h-5 w-5" />
|
|||
|
|
导出图标 (PNG 108x108)
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="text-sm text-gray-500">
|
|||
|
|
规格说明:
|
|||
|
|
- 尺寸:108x108px (3倍图)
|
|||
|
|
- 格式:PNG
|
|||
|
|
- 背景:透明
|
|||
|
|
- 大小:≤120kb
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { ref, reactive, computed } from 'vue';
|
|||
|
|
import { Download as DownloadIcon } from 'lucide-vue-next';
|
|||
|
|
|
|||
|
|
|
|||
|
|
const COLOR_COMBINATIONS = [
|
|||
|
|
{
|
|||
|
|
name: '蓝色组合',
|
|||
|
|
baseColor: '#F5F8FD',
|
|||
|
|
gradients: ['#46ACFF', '#1E92F0'],
|
|||
|
|
shadowColor: '#1E92F0'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '金色组合',
|
|||
|
|
baseColor: '#F5F8FD',
|
|||
|
|
gradients: ['#FFD157', '#F2B921'],
|
|||
|
|
shadowColor: '#F2B921'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '绿色组合',
|
|||
|
|
baseColor: '#F5F8FD',
|
|||
|
|
gradients: ['#15CC96', '#15CC96'],
|
|||
|
|
shadowColor: '#15CC96'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '青色组合',
|
|||
|
|
baseColor: '#F5F8FD',
|
|||
|
|
gradients: ['#30DFF3', '#43ABFF'],
|
|||
|
|
shadowColor: '#43ABFF'
|
|||
|
|
}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
components: {
|
|||
|
|
DownloadIcon
|
|||
|
|
},
|
|||
|
|
setup() {
|
|||
|
|
const selectedCombo = ref(COLOR_COMBINATIONS[0]);
|
|||
|
|
const iconRef = ref(null);
|
|||
|
|
|
|||
|
|
// 严格按照36x36基础尺寸的3倍图
|
|||
|
|
const SCALE = 3;
|
|||
|
|
const BASE_SIZE = 36 * SCALE;
|
|||
|
|
const CORNER_RADIUS = 2 * SCALE;
|
|||
|
|
const ICON_SIZE = 28 * SCALE;
|
|||
|
|
|
|||
|
|
const x = computed(() => (BASE_SIZE - ICON_SIZE) / 2);
|
|||
|
|
const y = computed(() => (BASE_SIZE - ICON_SIZE) / 2);
|
|||
|
|
|
|||
|
|
const setSelectedCombo = (combo) => {
|
|||
|
|
selectedCombo.value = combo;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const downloadIcon = () => {
|
|||
|
|
if (!iconRef.value) return;
|
|||
|
|
const svgData = new XMLSerializer().serializeToString(iconRef.value);
|
|||
|
|
const canvas = document.createElement('canvas');
|
|||
|
|
canvas.width = 108;
|
|||
|
|
canvas.height = 108;
|
|||
|
|
const ctx = canvas.getContext('2d');
|
|||
|
|
|
|||
|
|
const img = new Image();
|
|||
|
|
img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
|
|||
|
|
|
|||
|
|
img.onload = () => {
|
|||
|
|
ctx.drawImage(img, 0, 0);
|
|||
|
|
|
|||
|
|
// 创建临时canvas进行大小优化
|
|||
|
|
const tempCanvas = document.createElement('canvas');
|
|||
|
|
tempCanvas.width = 108;
|
|||
|
|
tempCanvas.height = 108;
|
|||
|
|
const tempCtx = tempCanvas.getContext('2d');
|
|||
|
|
tempCtx.drawImage(canvas, 0, 0);
|
|||
|
|
|
|||
|
|
// 导出优化后的PNG
|
|||
|
|
const link = document.createElement('a');
|
|||
|
|
link.download = 'help-service-icon.png';
|
|||
|
|
link.href = tempCanvas.toDataURL('image/png', 0.8); // 优化压缩率以确保小于120kb
|
|||
|
|
link.click();
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
selectedCombo,
|
|||
|
|
iconRef,
|
|||
|
|
SCALE,
|
|||
|
|
BASE_SIZE,
|
|||
|
|
CORNER_RADIUS,
|
|||
|
|
ICON_SIZE,
|
|||
|
|
x,
|
|||
|
|
y,
|
|||
|
|
COLOR_COMBINATIONS,
|
|||
|
|
setSelectedCombo,
|
|||
|
|
downloadIcon
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|