Files
ykb-h5/pages/index/DownloadIcon.vue
2025-11-19 12:51:13 +08:00

239 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>