diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 448efc1..518be64 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -8,35 +8,39 @@ on: jobs: build-and-deploy: - runs-on: ubuntu + runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: https://gitea.com/actions/checkout@v3 - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - name: Build and push - uses: docker/build-push-action@v4 - with: - context: . - push: true - tags: ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest + run: | + docker build -t ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest . + docker push ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest - name: Deploy to Server - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.SERVER_HOST }} - username: ${{ secrets.SERVER_USERNAME }} - key: ${{ secrets.SERVER_KEY }} - script: | - docker pull ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest - docker stop wx-pyq || true - docker rm wx-pyq || true - docker run -d --name wx-pyq -p 80:80 ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest + run: | + # Install SSH client + if command -v apk > /dev/null; then + sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories + apk add --no-cache openssh-client + elif command -v apt-get > /dev/null; then + sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list + apt-get update && apt-get install -y openssh-client + fi + + # Setup SSH key + mkdir -p ~/.ssh + echo "${{ secrets.SERVER_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + + # Deploy + ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }} " + docker pull ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest && \ + (docker stop wx-pyq || true) && \ + (docker rm wx-pyq || true) && \ + docker run -d --name wx-pyq -p 3321:80 ${{ secrets.DOCKER_USERNAME }}/wx-pyq:latest + " diff --git a/Dockerfile b/Dockerfile index 64be04a..c332e56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,8 @@ FROM node:18-alpine as builder WORKDIR /app +RUN npm config set registry https://registry.npmmirror.com + COPY package*.json ./ RUN npm ci diff --git a/src/components/home/CopyCard.tsx b/src/components/home/CopyCard.tsx new file mode 100644 index 0000000..6004fbe --- /dev/null +++ b/src/components/home/CopyCard.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Button, Toast } from 'antd-mobile'; +import { Copy, Quote, Sparkles } from 'lucide-react'; +import { motion } from 'framer-motion'; + +interface CopyCardProps { + result: string; + loading: boolean; + scenario?: string; +} + +/** + * 分离出的文案显示组件 + * 负责文案的渲染、流式动画效果以及复制功能 + */ +export const CopyCard: React.FC = ({ result, loading, scenario }) => { + const handleCopy = () => { + if (!result) return; + navigator.clipboard.writeText(result).then(() => { + Toast.show({ content: '已复制文案', icon: 'success' }); + }); + }; + + return ( + + {scenario && ( +
+ + + {scenario} + +
+ )} + + + +
+ {result} + {loading && ( + + )} +
+ +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src/components/home/NineGrid.tsx b/src/components/home/NineGrid.tsx new file mode 100644 index 0000000..f1a767e --- /dev/null +++ b/src/components/home/NineGrid.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { motion } from 'framer-motion'; + +interface NineGridProps { + images: string[]; +} + +/** + * 专门用于 9 宫格布局的显示组件 + */ +export const NineGrid: React.FC = ({ images }) => { + // 如果没有图片,不显示任何内容 + if (!images || images.length === 0) return null; + + // 截取前9张图片 + const gridItems = images.slice(0, 9); + + return ( +
+

+ 图片预览 (九宫格) + 长按保存图片 +

+
+ {gridItems.map((url, index) => ( + + {`Grid { + // Optional: Click to preview or just let user long press + }} + /> + + ))} +
+
+ ); +}; \ No newline at end of file diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 3de77d4..72b8322 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,13 +1,14 @@ import React, { useState, useEffect, useRef } from 'react'; import { Button, Input, Toast, ErrorBlock } from 'antd-mobile'; -import { Copy, RefreshCw, Sparkles, Wand2, Quote, UserCircle2, Download, Image as ImageIcon } from 'lucide-react'; +import { RefreshCw, Sparkles, Wand2, UserCircle2 } from 'lucide-react'; import { useAppStore } from '../store/useAppStore'; import { generateCopyStream } from '../services/api'; import { Header } from '../components/Header'; import { Layout } from '../components/Layout'; import { Link } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; -import html2canvas from 'html2canvas'; +import { CopyCard } from '../components/home/CopyCard'; +import { NineGrid } from '../components/home/NineGrid'; export const Home: React.FC = () => { const { templates, preferences, setLastUsedName, posterUrls } = useAppStore(); @@ -16,11 +17,9 @@ export const Home: React.FC = () => { const [result, setResult] = useState(''); const [error, setError] = useState(''); const [currentScenario, setCurrentScenario] = useState(''); - const [currentPosterUrl, setCurrentPosterUrl] = useState(''); // Ref for auto-scrolling to bottom of result const resultEndRef = useRef(null); - const posterRef = useRef(null); const handleGenerate = async () => { if (!name.trim()) { @@ -33,14 +32,6 @@ export const Home: React.FC = () => { setError(''); setResult(''); - // Randomly select a poster if available - if (posterUrls.length > 0) { - const randomPoster = posterUrls[Math.floor(Math.random() * posterUrls.length)]; - setCurrentPosterUrl(randomPoster); - } else { - setCurrentPosterUrl(''); - } - try { // 1. Filter enabled templates const enabledTemplates = templates.filter(t => t.isEnabled); @@ -73,7 +64,7 @@ export const Home: React.FC = () => { }, (err) => { console.error(err); - setError(err.message || '生成失败,请检查网络或联系管理员'); + setError((err as Error).message || '生成失败,请检查网络或联系管理员'); setLoading(false); }, () => { @@ -88,59 +79,6 @@ export const Home: React.FC = () => { } }; - const copyToClipboard = () => { - if (!result) return; - navigator.clipboard.writeText(result).then(() => { - Toast.show({ content: '已复制文案', icon: 'success' }); - }); - }; - - const copyPosterImage = async () => { - if (!posterRef.current || !result) return; - try { - const canvas = await html2canvas(posterRef.current, { - useCORS: true, - scale: 2, - backgroundColor: null - }); - - canvas.toBlob(async (blob) => { - if (!blob) return; - try { - // Attempt to write to clipboard - const item = new ClipboardItem({ 'image/png': blob }); - await navigator.clipboard.write([item]); - Toast.show({ content: '海报已复制', icon: 'success' }); - } catch (err) { - console.error('Clipboard write failed:', err); - Toast.show({ content: '复制失败,请尝试保存', icon: 'fail' }); - } - }, 'image/png'); - } catch (err) { - console.error('Capture failed:', err); - Toast.show({ content: '生成图片失败', icon: 'fail' }); - } - }; - - const handleSaveImage = async () => { - if (!posterRef.current || !result) return; - try { - const canvas = await html2canvas(posterRef.current, { - useCORS: true, - scale: 2, - backgroundColor: null - }); - const link = document.createElement('a'); - link.download = `wx-moments-${Date.now()}.png`; - link.href = canvas.toDataURL('image/png'); - link.click(); - Toast.show({ content: '海报已保存', icon: 'success' }); - } catch (err) { - console.error('Save failed:', err); - Toast.show({ content: '保存失败,请重试', icon: 'fail' }); - } - }; - useEffect(() => { if (resultEndRef.current) { resultEndRef.current.scrollIntoView({ behavior: 'smooth' }); @@ -213,19 +151,14 @@ export const Home: React.FC = () => { {/* Right Section: Result */} - +
{error && ( )} @@ -240,123 +173,40 @@ export const Home: React.FC = () => { {(result || loading) && ( - {currentScenario && ( - - - - {currentScenario} - - - )} - - {/* Poster Container */} -
- {/* Background Image Layer */} - {currentPosterUrl && ( -
- Poster Background -
-
- )} + {/* 1. Text Card */} + +
-
- - -
- {result} - {loading && ( - - )} -
-
+ {/* 2. Nine Grid Posters */} + -
-
-
- Generated by AI -
-
- -
-
-
-
-
- - {/* Actions Bar (Outside of Poster Ref) */} -
- - - - - {currentPosterUrl && result && !loading && ( - <> - - - - - )} + {/* 3. Global Actions */} +
+
)} - +