AI项目:名言壁纸生成器

一、AI生成玩具

大概的提示词:帮我生成一个炫彩的名言桌面壁纸生成器,支持从这些api里获取,支持多渠道,支持列表模式和壁纸模式,支持点击按钮保存图片。带上想要用的api,从里面拷贝

二、可玩性

  1. 挂cf变成在线,nginx等
  2. mac 使用Plash 变成桌面壁纸,保存为index.html,add website → local

三、代码

展开
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多渠道名言壁纸生成器</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            background: #000;
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            color: white;
            overflow: hidden;
        }
        
        #canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 1;
        }
        
        #controls {
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 10;
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            transition: opacity 0.3s ease;
            background: rgba(0, 0, 0, 0.7);
            padding: 15px;
            border-radius: 10px;
            max-width: 90%;
            justify-content: center;
        }
        
        button {
            padding: 10px 20px;
            font-size: 16px;
            border: none;
            border-radius: 5px;
            background: #4CAF50;
            color: white;
            cursor: pointer;
            transition: 0.3s;
        }
        
        button:hover {
            background: #45a049;
        }
        
        .loading {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 20px;
            border-radius: 10px;
            display: none;
            z-index: 100;
        }
        
        #quoteCount {
            width: 60px;
            padding: 10px;
            border-radius: 5px;
            border: none;
            background: #f1f1f1;
            color: #333;
            text-align: center;
        }
        
        #quoteCount::-webkit-inner-spin-button,
        #quoteCount::-webkit-outer-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }
        
        #toggleControls {
            position: fixed;
            right: 20px;
            top: 20px;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background: rgba(76, 175, 80, 0.8);
            color: white;
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 20px;
            z-index: 100;
            transition: 0.3s;
        }
        
        #toggleControls:hover {
            background: rgba(76, 175, 80, 1);
        }
        
        #controls.hidden {
            opacity: 0;
            pointer-events: none;
        }
        
        .source-options {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            margin-bottom: 10px;
            width: 100%;
        }
        
        .source-checkbox {
            display: flex;
            align-items: center;
            background: rgba(255, 255, 255, 0.1);
            padding: 5px 10px;
            border-radius: 5px;
            cursor: pointer;
            transition: 0.3s;
        }
        
        .source-checkbox:hover {
            background: rgba(255, 255, 255, 0.2);
        }
        
        .source-checkbox input {
            margin-right: 5px;
        }
        
        #quotesContainer {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.95);
            padding: 20px;
            box-sizing: border-box;
            max-height: 100vh;
            overflow-y: auto;
            display: none;
            z-index: 5;
        }
        
        .quote-card {
            width: 100%;
            max-width: 800px;
            margin-bottom: 15px;
            padding: 15px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 8px;
            transition: 0.3s;
            position: relative;
        }
        
        .quote-card:hover {
            background: rgba(255, 255, 255, 0.15);
            transform: translateY(-2px);
            cursor: pointer;
        }
        
        .quote-content {
            font-size: 18px;
            line-height: 1.5;
            margin-bottom: 10px;
        }
        
        .quote-author {
            font-size: 14px;
            text-align: right;
            font-style: italic;
            color: rgba(255, 255, 255, 0.7);
        }
        
        .quote-source {
            position: absolute;
            top: 10px;
            right: 10px;
            background: rgba(76, 175, 80, 0.7);
            padding: 3px 8px;
            border-radius: 20px;
            font-size: 12px;
        }
        
        .view-toggle {
            margin: 10px 0;
            display: flex;
            gap: 10px;
        }
        
        .selected {
            background: #1E88E5;
        }
        
        .selected:hover {
            background: #1976D2;
        }
        
        #fontSizeControl {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 10px;
        }
        
        #fontSize {
            width: 100px;
        }
        
        /* 滚动条样式 */
        #quotesContainer::-webkit-scrollbar {
            width: 8px;
        }
        
        #quotesContainer::-webkit-scrollbar-track {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 4px;
        }
        
        #quotesContainer::-webkit-scrollbar-thumb {
            background-color: rgba(76, 175, 80, 0.7);
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <div id="quotesContainer"></div>
    <div class="loading">加载中...</div>
    <button id="toggleControls">⚙️</button>
    <div id="controls">
        <div class="source-options">
            <label class="source-checkbox">
                <input type="checkbox" name="source" value="one" checked> ONE一个
            </label>
            <label class="source-checkbox">
                <input type="checkbox" name="source" value="iciba" checked> 金山词霸
            </label>
            <label class="source-checkbox">
                <input type="checkbox" name="source" value="shanbay" checked> 扇贝单词
            </label>
            <label class="source-checkbox">
                <input type="checkbox" name="source" value="hitokoto" checked> 一言
            </label>
            <label class="source-checkbox">
                <input type="checkbox" name="source" value="jinrishici" checked> 今日诗词
            </label>
        </div>
        <div id="fontSizeControl">
            <span>字体大小:</span>
            <input type="range" id="fontSize" min="20" max="60" value="36">
            <span id="fontSizeValue">36px</span>
        </div>
        <div>
            <span>每个来源获取:</span>
            <input type="number" id="quoteCount" min="1" max="20" value="3" title="每个来源获取名言数量">
            <span>条</span>
        </div>
        <div class="view-toggle">
            <button id="btnWallpaper" class="selected" onclick="toggleView('wallpaper')">壁纸模式</button>
            <button id="btnList" onclick="toggleView('list')">列表模式</button>
        </div>
        <div>
            <button onclick="fetchAllQuotes()">获取名言</button>
            <button onclick="generateNew()">随机壁纸</button>
            <button onclick="saveImage()">保存图片</button>
        </div>
    </div>

    <script>
        // 配置API
        const APIs = {
            one: {
                url: 'http://v3.wufazhuce.com:8000/api/channel/one/0/0', // 替换为备用API,因原API可能存在跨域问题
                transform: data => {
                    return {
                        content: data.data.content_list[0].forward,
                        author: data.from || 'ONE一个',
                        source: 'one'
                    };
                }
            },
            iciba: {
                // CORS error,地址加:https://cors-anywhere.herokuapp.com/ 可以解决
                url: 'https://cors-anywhere.herokuapp.com/https://open.iciba.com/dsapi/', // 替换为备用API,因原API可能存在跨域问题
                transform: data => {
                    return {
                        content: data.note,
                        author: data.caption || '金山词霸每日一句',
                        source: 'iciba'
                    };
                }
            },
            shanbay: {
                url: 'https://cors-anywhere.herokuapp.com/https://apiv3.shanbay.com/weapps/dailyquote/quote', // 替换为备用API,因原API可能存在跨域问题
                transform: data => {
                    return {
                        content: data.translation,
                        author: data.author || '扇贝单词',
                        source: 'shanbay'
                    };
                }
            },
            hitokoto: {
                url: 'https://v1.hitokoto.cn/?c=a',
                transform: data => {
                    return {
                        content: data.hitokoto,
                        author: data.from,
                        source: 'hitokoto'
                    };
                }
            },
            jinrishici: {
                url: 'https://v1.jinrishici.com/all.json',
                transform: data => {
                    return {
                        content: data.content,
                        author: `${data.origin ? data.origin : ''} · ${data.author ? data.author : ''} · ${data.category ? data.category : ''}`,
                        source: 'jinrishici'
                    };
                }
            }
        };

        const CONFIG = {
            cacheKey: 'wallpaper_quotes_cache',
            cacheDuration: 60 * 60 * 1000, // 1小时
            retryTimes: 3,
            retryDelay: 1000,
            defaultQuoteCount: 3,
            sourceLabels: {
                one: 'ONE一个',
                iciba: '金山词霸',
                shanbay: '扇贝单词',
                hitokoto: '一言',
                jinrishici: '今日诗词'
            },
            sourceColors: {
                one: '#FF5722',
                iciba: '#2196F3',
                shanbay: '#9C27B0',
                hitokoto: '#FF9800',
                jinrishici: '#4CAF50'
            }
        };

        class QuotesManager {
            static async fetchWithRetry(url, times = CONFIG.retryTimes) {
                for (let i = 0; i < times; i++) {
                    try {
                        const response = await fetch(url);
                        if (!response.ok) throw new Error('Network response was not ok');
                        return await response.json();
                    } catch (error) {
                        if (i === times - 1) throw error;
                        await new Promise(resolve => setTimeout(resolve, CONFIG.retryDelay));
                    }
                }
            }

            static async fetchQuote(source) {
                try {
                    const api = APIs[source];
                    if (!api) throw new Error(`Invalid source: ${source}`);
                    
                    const data = await this.fetchWithRetry(api.url);
                    return api.transform(data);
                } catch (error) {
                    console.error(`Error fetching quote from ${source}:`, error);
                    throw error;
                }
            }

            static async fetchQuotes(source, count) {
                const quotes = [];
                const uniqueContents = new Set();
                
                // 为了防止无限循环,设置最大尝试次数
                let attempts = 0;
                const maxAttempts = count * 3;
                
                while (quotes.length < count && attempts < maxAttempts) {
                    try {
                        const quote = await this.fetchQuote(source);
                        
                        // 去重逻辑
                        if (!uniqueContents.has(quote.content)) {
                            uniqueContents.add(quote.content);
                            quotes.push(quote);
                        }
                    } catch (error) {
                        console.error(`Error fetching quote ${quotes.length + 1} from ${source}:`, error);
                    }
                    attempts++;
                }
                return quotes;
            }

            static async fetchAllSelectedQuotes() {
                const quotesContainer = document.getElementById('quotesContainer');
                quotesContainer.innerHTML = '';
                
                const loading = document.querySelector('.loading');
                loading.style.display = 'block';
                
                const selectedSources = Array.from(document.querySelectorAll('input[name="source"]:checked'))
                    .map(cb => cb.value);
                
                if (selectedSources.length === 0) {
                    alert('请至少选择一个名言来源');
                    loading.style.display = 'none';
                    return [];
                }
                
                const count = parseInt(document.getElementById('quoteCount').value) || CONFIG.defaultQuoteCount;
                const allQuotes = [];
                
                for (const source of selectedSources) {
                    try {
                        const quotes = await this.fetchQuotes(source, count);
                        allQuotes.push(...quotes);
                        
                        // 即时更新到列表中
                        quotes.forEach(quote => {
                            this.addQuoteToList(quote);
                        });
                    } catch (error) {
                        console.error(`Failed to fetch quotes from ${source}:`, error);
                    }
                }
                
                loading.style.display = 'none';
                
                // 保存到缓存
                localStorage.setItem(CONFIG.cacheKey, JSON.stringify({
                    quotes: allQuotes,
                    timestamp: Date.now()
                }));
                
                return allQuotes;
            }
            
            static addQuoteToList(quote) {
                const quotesContainer = document.getElementById('quotesContainer');
                
                const quoteCard = document.createElement('div');
                quoteCard.className = 'quote-card';
                
                const sourceTag = document.createElement('div');
                sourceTag.className = 'quote-source';
                sourceTag.textContent = CONFIG.sourceLabels[quote.source] || quote.source;
                sourceTag.style.backgroundColor = CONFIG.sourceColors[quote.source] || '#4CAF50';
                
                const content = document.createElement('div');
                content.className = 'quote-content';
                content.textContent = quote.content;
                
                const author = document.createElement('div');
                author.className = 'quote-author';
                author.textContent = quote.author ? `—— ${quote.author}` : '';
                
                quoteCard.appendChild(sourceTag);
                quoteCard.appendChild(content);
                quoteCard.appendChild(author);
                
                // 点击卡片使用这个名言生成壁纸
                quoteCard.addEventListener('click', () => {
                    drawQuote(quote);
                    toggleView('wallpaper');
                });
                
                quotesContainer.appendChild(quoteCard);
            }

            static getRandomQuote() {
                try {
                    const cached = localStorage.getItem(CONFIG.cacheKey);
                    if (cached) {
                        const {quotes} = JSON.parse(cached);
                        if (quotes && quotes.length > 0) {
                            return quotes[Math.floor(Math.random() * quotes.length)];
                        }
                    }
                    return null;
                } catch (error) {
                    console.error('Error getting random quote:', error);
                    return null;
                }
            }
        }

        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');

        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        }

        function generateGradient() {
            // 生成渐变背景
            const hue1 = Math.random() * 360;
            const hue2 = (hue1 + 30 + Math.random() * 60) % 360; // 相近但不同的色调
            
            const colors = [
                `hsl(${hue1}, 70%, 60%)`,
                `hsl(${hue2}, 70%, 60%)`
            ];
            
            // 随机决定渐变方向
            let gradient;
            const direction = Math.floor(Math.random() * 4);
            
            switch (direction) {
                case 0: // 从上到下
                    gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
                    break;
                case 1: // 从左到右
                    gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
                    break;
                case 2: // 从左上到右下
                    gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
                    break;
                case 3: // 从右上到左下
                    gradient = ctx.createLinearGradient(canvas.width, 0, 0, canvas.height);
                    break;
            }
            
            colors.forEach((color, i) => {
                gradient.addColorStop(i / (colors.length - 1), color);
            });
            
            return gradient;
        }

        function drawQuote(quote) {
            // 清空画布
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // 绘制背景
            ctx.fillStyle = generateGradient();
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // 添加纹理效果
            ctx.globalAlpha = 0.05;
            for (let i = 0; i < 5; i++) {
                ctx.beginPath();
                ctx.arc(
                    Math.random() * canvas.width, 
                    Math.random() * canvas.height,
                    Math.random() * 200 + 50,
                    0, 
                    Math.PI * 2
                );
                ctx.fillStyle = "#ffffff";
                ctx.fill();
            }
            ctx.globalAlpha = 1;
            
            // 获取字体大小 - 根据屏幕大小调整
            const baseFontSize = parseInt(document.getElementById('fontSize').value) || 36;
            const fontSize = Math.min(baseFontSize, window.innerWidth / 20); // 调整字体大小以适应屏幕
            
            // 处理多行文本
            const sourceLabel = CONFIG.sourceLabels[quote.source] || quote.source;
            const lines = quote.content.split('\n');
            const lineHeight = fontSize * 1.5;
            
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            
            // 绘制小标签
            ctx.font = `bold ${fontSize * 0.6}px 'Microsoft YaHei'`;
            ctx.fillStyle = CONFIG.sourceColors[quote.source] || '#4CAF50';
            
            // 创建一个圆角矩形背景
            const tagText = sourceLabel;
            const tagWidth = ctx.measureText(tagText).width + 20;
            const tagHeight = fontSize * 0.8;
            const tagX = canvas.width / 2 - tagWidth / 2;
            const tagY = canvas.height / 4 - tagHeight / 2;
            
            roundRect(ctx, tagX, tagY, tagWidth, tagHeight, 15);
            ctx.fill();
            
            // 绘制标签文字
            ctx.fillStyle = 'white';
            ctx.fillText(tagText, canvas.width / 2, canvas.height / 4);
            
            // 绘制名言内容 - 处理长文本
            ctx.font = `bold ${fontSize}px 'Microsoft YaHei'`;
            ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
            
            // 处理长句子自动换行
            const processedLines = [];
            const maxWidth = canvas.width * 0.8; // 文本最大宽度为画布宽度的80%
            
            lines.forEach(line => {
                if (ctx.measureText(line).width <= maxWidth) {
                    processedLines.push(line);
                } else {
                    // 分词处理长句子
                    let currentLine = '';
                    const words = line.split('');
                    
                    for (let i = 0; i < words.length; i++) {
                        const testLine = currentLine + words[i];
                        if (ctx.measureText(testLine).width > maxWidth) {
                            processedLines.push(currentLine);
                            currentLine = words[i];
                        } else {
                            currentLine = testLine;
                        }
                    }
                    
                    if (currentLine.length > 0) {
                        processedLines.push(currentLine);
                    }
                }
            });
            
            const startY = canvas.height / 2 - (processedLines.length - 1 + (quote.author ? 1 : 0)) * lineHeight / 2;
            
            processedLines.forEach((line, index) => {
                ctx.fillText(line, canvas.width / 2, startY + index * lineHeight);
            });
            
            // 绘制作者
            if (quote.author) {
                ctx.font = `italic ${fontSize * 0.7}px 'Microsoft YaHei'`;
                ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
                ctx.fillText(`—— ${quote.author}`, canvas.width / 2, startY + processedLines.length * lineHeight);
            }
        }

        // 辅助函数:绘制圆角矩形
        function roundRect(ctx, x, y, width, height, radius) {
            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();
        }

        async function fetchAllQuotes() {
            await QuotesManager.fetchAllSelectedQuotes();
            if (document.getElementById('btnList').classList.contains('selected')) {
                document.getElementById('quotesContainer').style.display = 'flex';
            }
        }

        async function generateNew() {
            const loading = document.querySelector('.loading');
            loading.style.display = 'block';

            try {
                let quote = QuotesManager.getRandomQuote();
                
                if (!quote) {
                    // 如果没有缓存的名言,尝试获取新的
                    const quotes = await QuotesManager.fetchAllSelectedQuotes();
                    if (quotes.length > 0) {
                        quote = quotes[Math.floor(Math.random() * quotes.length)];
                    } else {
                        throw new Error('没有可用的名言');
                    }
                }
                
                drawQuote(quote);
                toggleView('wallpaper');
            } catch (error) {
                console.error('Error generating wallpaper:', error);
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.fillStyle = 'black';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                
                ctx.font = '20px Microsoft YaHei';
                ctx.fillStyle = 'white';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText("获取名言失败,请稍后重试", canvas.width / 2, canvas.height / 2);
            } finally {
                loading.style.display = 'none';
            }
        }

        function saveImage() {
            const link = document.createElement('a');
            link.download = '名言壁纸.png';
            link.href = canvas.toDataURL('image/png');
            link.click();
        }

        function toggleView(view) {
            const wallpaperBtn = document.getElementById('btnWallpaper');
            const listBtn = document.getElementById('btnList');
            const quotesContainer = document.getElementById('quotesContainer');
            const canvas = document.getElementById('canvas');
            
            if (view === 'wallpaper') {
                wallpaperBtn.classList.add('selected');
                listBtn.classList.remove('selected');
                quotesContainer.style.display = 'none';
                canvas.style.display = 'block';
            } else {
                wallpaperBtn.classList.remove('selected');
                listBtn.classList.add('selected');
                quotesContainer.style.display = 'flex';
                canvas.style.display = 'none';
            }
        }

        document.getElementById('toggleControls').addEventListener('click', () => {
            const controls = document.getElementById('controls');
            controls.classList.toggle('hidden');
            // 保存状态到 localStorage
            localStorage.setItem('controlsHidden', controls.classList.contains('hidden'));
        });

        document.getElementById('fontSize').addEventListener('input', function() {
            document.getElementById('fontSizeValue').textContent = this.value + 'px';
            // 如果当前有显示的名言,重新绘制
            const quote = QuotesManager.getRandomQuote();
            if (quote) {
                drawQuote(quote);
            }
        });

        window.addEventListener('resize', () => {
            resizeCanvas();
            const quote = QuotesManager.getRandomQuote();
            if (quote) {
                drawQuote(quote);
            } else {
                generateNew();
            }
        });

        window.addEventListener('load', async () => {
            resizeCanvas();
            
            // 恢复控制面板状态
            const controls = document.getElementById('controls');
            const isHidden = localStorage.getItem('controlsHidden') === 'true';
            if (isHidden) {
                controls.classList.add('hidden');
            }
            
            // 尝试从缓存加载名言
            const cached = localStorage.getItem(CONFIG.cacheKey);
            if (cached) {
                try {
                    const {quotes, timestamp} = JSON.parse(cached);
                    if (Date.now() - timestamp < CONFIG.cacheDuration && quotes.length > 0) {
                        // 显示缓存的引言
                        quotes.forEach(quote => {
                            QuotesManager.addQuoteToList(quote);
                        });
                        
                        // 随机选择一个显示到壁纸
                        const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
                        drawQuote(randomQuote);
                        
                        return;
                    }
                } catch (error) {
                    console.error('Error loading from cache:', error);
                }
            }
            
            // 如果没有缓存或缓存已过期,获取新的名言
            await generateNew();
        });
    </script>
</body>
</html>

四、效果


五、FAQ

代码不是最完美的可能还有bug,大佬别喷,哈哈哈。

Q1: 金山、扇贝等会跨域

CORS error,地址加:https://cors-anywhere.herokuapp.com/ 可以解决

Q2: 默认生成没有全屏

AI已优化解决。


10 Likes

感谢大佬

1 Like

不错的 :face_savoring_food:

此话题已在最后回复的 30 天后被自动关闭。不再允许新回复。