diff --git a/.woodpecker.yml b/.woodpecker.yml index 10053cd..0401ba9 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -240,11 +240,10 @@ steps: volumes: - /var/run/docker.sock:/var/run/docker.sock when: - event: - - push - branch: - - release - - release/** + - event: push + branch: + - release + - release/** # ============================================ # 阶段5: 部署到生产环境 (release分支) @@ -400,6 +399,52 @@ steps: status: - success + # ============================================ + # 阶段7: 企业微信通知 + # ============================================ + notify-wechat: + image: alpine:latest + environment: + WECHAT_WEBHOOK: + from_secret: wechat_webhook + commands: + - echo "Sending notification to WeChat Work..." + - apk add --no-cache curl + - | + STATUS="${CI_PIPELINE_STATUS}" + BRANCH="${CI_COMMIT_BRANCH}" + COMMIT="${CI_COMMIT_SHA:0:7}" + MESSAGE="${CI_COMMIT_MESSAGE}" + AUTHOR="${CI_COMMIT_AUTHOR}" + PIPELINE_NUMBER="${CI_PIPELINE_NUMBER}" + PIPELINE_URL="https://ci.f.novalon.cn/repos/${CI_REPO_ID}/pipeline/${PIPELINE_NUMBER}" + + if [ "$STATUS" = "success" ]; then + STATUS_TEXT="成功" + STATUS_COLOR="info" + else + STATUS_TEXT="失败" + STATUS_COLOR="warning" + fi + + curl -X POST "$WECHAT_WEBHOOK" \ + -H 'Content-Type: application/json' \ + -d '{ + "msgtype": "markdown", + "markdown": { + "content": "'"## 🚀 Novalon Website 部署通知\n\n> **构建状态**: '"${STATUS_TEXT}"'\n\n**项目信息**\n> 分支: `'"${BRANCH}"'`\n> 提交: `'"${COMMIT}"'`\n> 作者: '"${AUTHOR}"'\n\n**提交信息**\n> '"${MESSAGE}"'\n\n**操作**\n> [查看构建详情]('"${PIPELINE_URL}"')\n\n---\n> 时间: $(date "+%Y-%m-%d %H:%M:%S")\n> Pipeline #${PIPELINE_NUMBER}"'" + } + }' + when: + event: + - push + branch: + - release + - release/** + status: + - success + - failure + # ============================================ # 工作区配置 # ============================================ diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index 731eb2b..ce7e95d 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -1,12 +1,18 @@ 'use client'; import { useState, useEffect } from 'react'; -import { - Save, +import { + Save, RefreshCw, Loader2, ChevronDown, - ChevronUp + Settings2, + Palette, + Globe, + SlidersHorizontal, + Check, + X, + AlertCircle, } from 'lucide-react'; interface ConfigItem { @@ -18,31 +24,69 @@ interface ConfigItem { updatedAt: string; } -const categoryLabels = { - feature: '功能配置', - style: '样式配置', - seo: 'SEO 配置', - general: '常规配置' -}; - -const categoryColors = { - feature: 'bg-blue-100 text-blue-800', - style: 'bg-purple-100 text-purple-800', - seo: 'bg-green-100 text-green-800', - general: 'bg-gray-100 text-gray-800' +const categoryConfig = { + feature: { + label: '功能配置', + description: '控制网站各功能模块的启用与参数', + icon: SlidersHorizontal, + color: 'from-blue-500 to-cyan-500', + bgColor: 'bg-blue-50', + borderColor: 'border-blue-200', + textColor: 'text-blue-700', + iconBg: 'bg-blue-100', + }, + style: { + label: '样式配置', + description: '自定义网站视觉风格和主题色彩', + icon: Palette, + color: 'from-purple-500 to-pink-500', + bgColor: 'bg-purple-50', + borderColor: 'border-purple-200', + textColor: 'text-purple-700', + iconBg: 'bg-purple-100', + }, + seo: { + label: 'SEO 配置', + description: '搜索引擎优化和元数据设置', + icon: Globe, + color: 'from-emerald-500 to-teal-500', + bgColor: 'bg-emerald-50', + borderColor: 'border-emerald-200', + textColor: 'text-emerald-700', + iconBg: 'bg-emerald-100', + }, + general: { + label: '常规配置', + description: '网站基本信息和通用设置', + icon: Settings2, + color: 'from-amber-500 to-orange-500', + bgColor: 'bg-amber-50', + borderColor: 'border-amber-200', + textColor: 'text-amber-700', + iconBg: 'bg-amber-100', + }, }; export default function SettingsPage() { const [configs, setConfigs] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(null); - const [expandedCategories, setExpandedCategories] = useState>(new Set(['feature', 'seo'])); + const [expandedCategories, setExpandedCategories] = useState>(new Set(['feature'])); const [editedValues, setEditedValues] = useState>>({}); + const [saveSuccess, setSaveSuccess] = useState(null); useEffect(() => { fetchConfigs(); }, []); + useEffect(() => { + if (saveSuccess) { + const timer = setTimeout(() => setSaveSuccess(null), 3000); + return () => clearTimeout(timer); + } + return undefined; + }, [saveSuccess]); + const fetchConfigs = async () => { try { setLoading(true); @@ -60,7 +104,7 @@ export default function SettingsPage() { const handleSave = async (configId: string) => { const editedValue = editedValues[configId]; - if (!editedValue) return; + if (!editedValue) {return;} try { setSaving(configId); @@ -79,6 +123,7 @@ export default function SettingsPage() { delete updated[configId]; return updated; }); + setSaveSuccess(configId); await fetchConfigs(); } } catch (error) { @@ -88,6 +133,14 @@ export default function SettingsPage() { } }; + const handleCancel = (configId: string) => { + setEditedValues(prev => { + const updated = { ...prev }; + delete updated[configId]; + return updated; + }); + }; + const toggleCategory = (category: string) => { setExpandedCategories(prev => { const updated = new Set(prev); @@ -129,149 +182,281 @@ export default function SettingsPage() { return acc; }, {} as Record); + const getFieldLabel = (field: string) => { + const labels: Record = { + enabled: '启用状态', + displayCount: '显示数量', + categories: '分类列表', + sortOrder: '排序方式', + showPricing: '显示价格', + featuredProducts: '推荐产品', + items: '项目列表', + title: '标题', + description: '描述', + keywords: '关键词', + }; + return labels[field] || field; + }; + + const renderFieldInput = (configItem: ConfigItem, field: string, value: any) => { + const currentValue = getConfigValue(configItem, field); + + if (typeof value === 'boolean') { + return ( +