Files
ykb-h5/pages/index/DownloadIcon.vue

239 lines
7.0 KiB
Vue
Raw Normal View History

2025-11-19 12:51:13 +08:00
<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>