diff --git a/backend/config.prod.yaml b/backend/config.prod.yaml index 440d4a3..1b741a7 100644 --- a/backend/config.prod.yaml +++ b/backend/config.prod.yaml @@ -27,7 +27,7 @@ browser_pool: # ========== 登录/绑定功能配置 ========== login: - headless: true # 登录/绑定时的浏览器模式: false=有头模式(方便用户操作),true=无头模式 + headless: false # 登录/绑定时的浏览器模式: false=有头模式(配合Xvfb避免被检测),true=无头模式 page: "home" # 登录页面类型: creator=创作者中心(creator.xiaohongshu.com/login), home=小红书首页(www.xiaohongshu.com) # ========== 定时发布调度器配置 ========== @@ -40,7 +40,7 @@ scheduler: max_failures_per_user_per_run: 3 # 每轮每个用户最大失败次数(达到后暂停本轮后续发布) max_daily_articles_per_user: 20 # 每个用户每日最大发文数(自动发布) max_hourly_articles_per_user: 3 # 每个用户每小时最大发文数(自动发布) - headless: true # 浏览器模式: false=有头模式(可调试),true=无头模式(生产环境) + headless: false # 浏览器模式: false=有头模式(配合Xvfb避免被检测),true=无头模式 # ========== 防封策略配置 ========== enable_random_ua: true # 启用随机User-Agent(防指纹识别) diff --git a/backend/start_prod.sh b/backend/start_prod.sh index 4592898..25c6982 100644 --- a/backend/start_prod.sh +++ b/backend/start_prod.sh @@ -21,6 +21,17 @@ echo "[Python] $(python --version)" echo "[路径] $(which python)" echo "" +# 检查是否安装了Xvfb +if command -v xvfb-run &> /dev/null; then + echo "[Xvfb] 检测到Xvfb,将使用虚拟显示运行(避免无头模式被检测)" + USE_XVFB=true +else + echo "[警告] 未检测到Xvfb,将使用无头模式(可能触发验证)" + echo "[提示] 安装Xvfb: sudo apt-get install -y xvfb" + USE_XVFB=false +fi +echo "" + echo "[启动] 正在启动Python服务(生产环境,端口8000)..." echo "[说明] 按Ctrl+C停止服务" echo "" @@ -28,5 +39,11 @@ echo "" # 设置环境为生产环境 export ENV=prod -# 启动服务(生产模式) -python -m uvicorn main:app --host 0.0.0.0 --port 8000 +# 启动服务(根据是否有Xvfb选择启动方式) +if [ "$USE_XVFB" = true ]; then + echo "[模式] 使用Xvfb虚拟显示 + 有头模式" + xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" python -m uvicorn main:app --host 0.0.0.0 --port 8000 +else + echo "[模式] 无头模式(可能被检测)" + python -m uvicorn main:app --host 0.0.0.0 --port 8000 +fi diff --git a/miniprogram/miniprogram/app.json b/miniprogram/miniprogram/app.json index 5b80988..5ef3a9e 100644 --- a/miniprogram/miniprogram/app.json +++ b/miniprogram/miniprogram/app.json @@ -1,10 +1,14 @@ { "pages": [ "pages/home/home", + "pages/article-generate/article-generate", "pages/login/login", "pages/login/phone-login", "pages/articles/articles", + "pages/article-detail/article-detail", "pages/profile/profile", + "pages/profile/user-info/user-info", + "pages/profile/social-binding/social-binding", "pages/profile/platform-bind/platform-bind", "pages/profile/xhs-login/xhs-login", "pages/profile/published/published", @@ -12,7 +16,9 @@ "pages/profile/about/about", "pages/profile/feedback/feedback", "pages/agreement/user-agreement/user-agreement", - "pages/agreement/privacy-policy/privacy-policy" + "pages/agreement/privacy-policy/privacy-policy", + "pages/index/index", + "pages/logs/logs" ], "window": { "navigationBarTextStyle": "white", diff --git a/miniprogram/miniprogram/pages/article-detail/article-detail.json b/miniprogram/miniprogram/pages/article-detail/article-detail.json new file mode 100644 index 0000000..2051df5 --- /dev/null +++ b/miniprogram/miniprogram/pages/article-detail/article-detail.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "文章详情", + "usingComponents": {} +} diff --git a/miniprogram/miniprogram/pages/article-detail/article-detail.ts b/miniprogram/miniprogram/pages/article-detail/article-detail.ts new file mode 100644 index 0000000..dec4445 --- /dev/null +++ b/miniprogram/miniprogram/pages/article-detail/article-detail.ts @@ -0,0 +1,91 @@ +// pages/article-detail/article-detail.ts +import { formatDate, getStatusInfo, getChannelInfo, getCoverColor, getCoverIcon } from '../../utils/util' +import { EmployeeService } from '../../services/employee' + +Page({ + data: { + article: {} as any, + copyId: 0, // 文案ID + productId: 0, // 产品ID + showClaimButton: true // 是否显示领取按钮 + }, + + onLoad(options: any) { + const copyId = parseInt(options.id); + const productId = parseInt(options.productId || '0'); + + this.setData({ + copyId, + productId + }); + + // 如果有copyId,显示文案详情 + if (copyId) { + this.setData({ + article: { + id: copyId, + title: '正在加载...', + content: '' + } + }); + } + }, + + // 领取文案 + async claimCopy() { + const { copyId, productId } = this.data; + + if (!copyId || !productId) { + wx.showToast({ + title: '参数错误', + icon: 'none' + }); + return; + } + + try { + const response = await EmployeeService.claimCopy(copyId, productId); + + if (response.code === 200 && response.data) { + wx.showToast({ + title: '领取成功', + icon: 'success' + }); + + // 隐藏领取按钮 + this.setData({ + showClaimButton: false + }); + + // 延迟后跳转到发布页面,传递领取信息 + setTimeout(() => { + const copy = response.data!.copy; + wx.redirectTo({ + url: `/pages/article-generate/article-generate?copyId=${copyId}&claimId=${response.data!.claim_id}&productId=${productId}&productName=${encodeURIComponent(copy.title)}&title=${encodeURIComponent(copy.title)}&content=${encodeURIComponent(copy.content)}` + }); + }, 1500); + } + } catch (error) { + console.error('领取文案失败:', error); + } + }, + + // 分享功能 + onShareAppMessage() { + const article = this.data.article; + return { + title: article.title || '精彩种草文案', + path: `/pages/article-detail/article-detail?id=${this.data.copyId}&productId=${this.data.productId}`, + imageUrl: '' // 可以设置文章封面图 + }; + }, + + // 分享到朋友圈 + onShareTimeline() { + const article = this.data.article; + return { + title: article.title || '精彩种草文案', + imageUrl: '' // 可以设置文章封面图 + }; + } +}); diff --git a/miniprogram/miniprogram/pages/article-detail/article-detail.wxml b/miniprogram/miniprogram/pages/article-detail/article-detail.wxml new file mode 100644 index 0000000..c1f7b9a --- /dev/null +++ b/miniprogram/miniprogram/pages/article-detail/article-detail.wxml @@ -0,0 +1,115 @@ + + + + {{article.coverIcon}} + + + + {{article.title}} + + + + + + {{article.author_name ? article.author_name[0] : 'A'}} + + + {{article.author_name || '匿名'}} + 创建于 {{article.created_at}} + + {{article.statusText}} + + + {{article.content}} + + + + {{article.word_count}} + 字数 + + + {{article.image_count}} + 图片 + + + {{article.timeText}} + 创建时间 + + + + + + + + + + + + 审核意见 + + + + + + + + diff --git a/miniprogram/miniprogram/pages/article-detail/article-detail.wxss b/miniprogram/miniprogram/pages/article-detail/article-detail.wxss new file mode 100644 index 0000000..404ef46 --- /dev/null +++ b/miniprogram/miniprogram/pages/article-detail/article-detail.wxss @@ -0,0 +1,341 @@ +/* pages/article-detail/article-detail.wxss */ +page { + height: 100vh; + width: 100vw; + background: linear-gradient(to bottom, #fff 0%, #f8f9fa 100%); + overflow: hidden; +} + +.container { + height: 100vh; + width: 100vw; + overflow-y: auto; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; +} + +.article-cover { + width: 100%; + height: 450rpx; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +.article-cover::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.15) 100%); +} + +.cover-icon { + font-size: 150rpx; + position: relative; + z-index: 1; + filter: drop-shadow(0 8rpx 24rpx rgba(0, 0, 0, 0.15)); +} + +.detail-content { + width: 100%; + padding: 50rpx 40rpx; + background: white; + border-radius: 40rpx 40rpx 0 0; + margin-top: -40rpx; + position: relative; + z-index: 10; + box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.06); + box-sizing: border-box; +} + +.detail-title { + font-size: 44rpx; + font-weight: bold; + color: #1a1a1a; + margin-bottom: 35rpx; + line-height: 1.5; + letter-spacing: 1rpx; + word-break: break-all; +} + +.article-meta-info { + display: flex; + flex-wrap: wrap; + gap: 24rpx; + margin-bottom: 35rpx; + padding: 35rpx; + background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%); + border-radius: 20rpx; + border: 1rpx solid #f0f0f0; + box-sizing: border-box; +} + +.meta-item { + flex: 0 0 auto; + font-size: 28rpx; +} + +.meta-item.full-width { + flex: 1 1 100%; +} + +.meta-label { + color: #999; + margin-right: 12rpx; + font-weight: 500; +} + +.meta-value { + color: #333; + font-weight: 600; +} + +.channel-tag { + padding: 6rpx 20rpx; + border-radius: 50rpx; + font-size: 24rpx; + font-weight: 600; + letter-spacing: 0.5rpx; +} + +.channel-1 { + background-color: #e6f7ff; + color: #1890ff; +} + +.channel-2 { + background-color: #fff7e6; + color: #fa8c16; +} + +.channel-3 { + background-color: #f0f9ff; + color: #07c160; +} + +.detail-author { + display: flex; + align-items: center; + margin-bottom: 40rpx; + padding: 30rpx; + background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%); + border-radius: 20rpx; + border: 1rpx solid #f0f0f0; +} + +.detail-author-avatar { + width: 88rpx; + height: 88rpx; + border-radius: 50%; + margin-right: 24rpx; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: bold; + font-size: 36rpx; + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15); +} + +.author-info { + flex: 1; +} + +.author-name { + font-size: 34rpx; + font-weight: 600; + margin-bottom: 10rpx; + color: #1a1a1a; +} + +.author-time { + font-size: 26rpx; + color: #999; +} + +.article-status { + padding: 10rpx 28rpx; + border-radius: 50rpx; + font-size: 26rpx; + font-weight: 600; + letter-spacing: 0.5rpx; +} + +.status-topic { + background-color: #f0f5ff; + color: #2f54eb; +} + +.status-cover_image { + background-color: #e6f7ff; + color: #1890ff; +} + +.status-generate { + background-color: #fff7e6; + color: #fa8c16; +} + +.status-generate_failed { + background-color: #fff1f0; + color: #f5222d; +} + +.status-draft { + background-color: #f5f5f5; + color: #8c8c8c; +} + +.status-pending_review { + background-color: #fff7e6; + color: #fa8c16; +} + +.status-approved { + background-color: #f6ffed; + color: #52c41a; +} + +.status-rejected { + background-color: #fff1f0; + color: #f5222d; +} + +.status-published_review { + background-color: #e6fffb; + color: #13c2c2; +} + +.status-published { + background-color: #f6ffed; + color: #52c41a; +} + +.status-failed { + background-color: #fff1f0; + color: #f5222d; +} + +.detail-text { + font-size: 32rpx; + line-height: 2; + color: #444; + margin-bottom: 40rpx; + white-space: pre-wrap; + padding: 30rpx; + background: #fafafa; + border-radius: 20rpx; + border-left: 4rpx solid #07c160; + word-break: break-all; + box-sizing: border-box; +} + +.detail-stats { + display: flex; + justify-content: space-around; + margin-bottom: 50rpx; + padding: 40rpx 0; + background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%); + border-radius: 20rpx; + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04); +} + +.detail-stat { + text-align: center; + flex: 1; +} + +.detail-stat-value { + font-size: 48rpx; + font-weight: bold; + color: #07c160; + display: block; + margin-bottom: 12rpx; +} + +.detail-stat-label { + font-size: 26rpx; + color: #999; + font-weight: 500; +} + +.action-buttons { + display: flex; + gap: 24rpx; + margin-top: 40rpx; + flex-wrap: wrap; +} + +.action-btn { + flex: 1; + min-width: 200rpx; + padding: 32rpx; + border-radius: 20rpx; + font-size: 32rpx; + font-weight: bold; + border: none; + color: white; + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12); + transition: all 0.3s; + box-sizing: border-box; +} + +.action-btn:active { + transform: scale(0.95); +} + +.approve-btn { + background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%); +} + +.reject-btn { + background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%); +} + +.publish-btn { + background: #07c160; +} + +.publish-btn.disabled { + background: #d9d9d9; + color: #999; + box-shadow: none; +} + +.cancel-btn { + background: linear-gradient(135deg, #d9d9d9 0%, #e8e8e8 100%); + color: #666; +} + +.review-section { + margin-top: 50rpx; + padding: 40rpx; + background: linear-gradient(135deg, #fff5f5 0%, #fff 100%); + border-radius: 20rpx; + border: 2rpx solid #ffebee; +} + +.review-title { + font-size: 34rpx; + font-weight: bold; + margin-bottom: 30rpx; + color: #1a1a1a; +} + +.review-textarea { + width: 100%; + min-height: 220rpx; + padding: 28rpx; + border: 2rpx solid #f0f0f0; + border-radius: 16rpx; + font-size: 30rpx; + box-sizing: border-box; + margin-bottom: 30rpx; + background: white; + line-height: 1.8; +} diff --git a/miniprogram/miniprogram/pages/article-generate/article-generate.json b/miniprogram/miniprogram/pages/article-generate/article-generate.json new file mode 100644 index 0000000..0d18ecd --- /dev/null +++ b/miniprogram/miniprogram/pages/article-generate/article-generate.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "AI生成种草文章", + "navigationBarBackgroundColor": "#ff2442", + "navigationBarTextStyle": "white" +} diff --git a/miniprogram/miniprogram/pages/article-generate/article-generate.ts b/miniprogram/miniprogram/pages/article-generate/article-generate.ts new file mode 100644 index 0000000..3aa6b4d --- /dev/null +++ b/miniprogram/miniprogram/pages/article-generate/article-generate.ts @@ -0,0 +1,243 @@ +// pages/article-generate/article-generate.ts +import { EmployeeService } from '../../services/employee'; + +interface Article { + title: string; + content: string; + tags: string[]; + images: string[]; +} + +Page({ + data: { + productId: 0, + productName: '', + copyId: 0, // 领取的文案ID + claimId: 0, // 领取记录ID + article: { + title: '', + content: '', + tags: [], + images: [] + } as Article, + generating: false, + isFromClaim: false // 是否来自领取的文案 + }, + + onLoad(options: any) { + const { productId, productName, copyId, claimId, title, content } = options; + + this.setData({ + productId: parseInt(productId || '0'), + productName: decodeURIComponent(productName || ''), + copyId: parseInt(copyId || '0'), + claimId: parseInt(claimId || '0'), + isFromClaim: !!copyId + }); + + // 如果有copyId,说明是从领取文案过来的 + if (this.data.copyId && this.data.claimId) { + // 如果有传递文案内容,直接显示 + if (title && content) { + this.setData({ + article: { + title: decodeURIComponent(title), + content: decodeURIComponent(content), + tags: ['种草分享', '好物推荐'], + images: [] + } + }); + } else { + // 否则生成模拟文案 + this.generateArticle(); + } + } else { + // 生成文章 + this.generateArticle(); + } + }, + + // 生成文章 + generateArticle() { + this.setData({ generating: true }); + + wx.showLoading({ + title: '生成中...', + mask: true + }); + + // 模拟AI生成文章 + setTimeout(() => { + const mockArticles = [ + { + title: `【种草分享】${this.data.productName}使用体验💕`, + content: `姐妹们!今天必须来跟大家分享一下我最近入手的宝藏好物——${this.data.productName}! + +✨使用感受: +用了一段时间真的太爱了!质感超级好,完全超出我的预期。包装也非常精致,送人自用都很合适。 + +🌟推荐理由: +1. 品质优秀,性价比超高 +2. 使用体验一级棒 +3. 颜值在线,拿出来超有面子 + +💰价格也很美丽,趁着活动入手真的很划算!强烈推荐给大家,绝对不会踩雷!`, + tags: ['种草分享', '好物推荐', '必买清单', '真实测评'], + images: [ + 'https://picsum.photos/id/237/600/400', + 'https://picsum.photos/id/152/600/400' + ] + }, + { + title: `真香警告!${this.data.productName}实测分享`, + content: `集美们看过来!今天给大家带来${this.data.productName}的真实使用感受~ + +🎯第一印象: +收到货的那一刻就被惊艳到了!包装精美,细节满满,完全是高端货的质感。 + +💫使用体验: +用了几天下来,真的是越用越喜欢!质量很好,用起来特别顺手,完全就是我想要的样子! + +⭐️总结: +这个价位能买到这么好的产品,真的是太值了!强烈安利给大家,闭眼入不会错!`, + tags: ['真实测评', '使用心得', '好物安利', '值得入手'], + images: [ + 'https://picsum.photos/id/292/600/400', + 'https://picsum.photos/id/365/600/400', + 'https://picsum.photos/id/180/600/400' + ] + } + ]; + + const randomArticle = mockArticles[Math.floor(Math.random() * mockArticles.length)]; + + this.setData({ + article: randomArticle, + generating: false + }); + + wx.hideLoading(); + }, 2000); + }, + + // 删除图片 + deleteImage(e: any) { + const index = e.currentTarget.dataset.index; + const images = this.data.article.images; + images.splice(index, 1); + + this.setData({ + 'article.images': images + }); + }, + + // 返回上一页 + goBack() { + wx.navigateBack(); + }, + + // 重新生成文章 + regenerateArticle() { + if (this.data.generating) return; + this.generateArticle(); + }, + + // 发布文章 + async publishArticle() { + wx.showModal({ + title: '确认发布', + content: '确定要发布这篇种草文章吗?', + confirmText: '发布', + confirmColor: '#ff2442', + success: async (res) => { + if (res.confirm) { + // 如果是从领取的文案,调用后端API + if (this.data.isFromClaim && this.data.copyId) { + try { + const response = await EmployeeService.publish({ + copy_id: this.data.copyId, + title: this.data.article.title, + content: this.data.article.content, + publish_link: '', // 可以后续添加发布链接输入 + xhs_note_id: '' // 小红书笔记ID + }); + + if (response.code === 200) { + wx.showToast({ + title: '发布成功', + icon: 'success', + duration: 2000 + }); + + // 保存到本地 + const articles = wx.getStorageSync('myArticles') || []; + articles.unshift({ + id: (response.data && response.data.record_id) || Date.now(), + productName: this.data.productName, + title: this.data.article.title, + content: this.data.article.content, + tags: this.data.article.tags, + createTime: new Date().toISOString(), + status: 'published' + }); + wx.setStorageSync('myArticles', articles); + + // 2秒后返回首页 + setTimeout(() => { + wx.navigateBack(); + }, 2000); + } else { + // 后端返回错误(如400) + wx.showToast({ + title: response.message || '发布失败', + icon: 'none', + duration: 3000 + }); + } + } catch (error: any) { + console.error('发布失败:', error); + wx.showToast({ + title: error.message || '发布失败,请重试', + icon: 'none', + duration: 3000 + }); + } + } else { + // 模拟发布(非领取文案的情况) + wx.showLoading({ + title: '发布中...', + mask: true + }); + + setTimeout(() => { + wx.hideLoading(); + wx.showToast({ + title: '发布成功', + icon: 'success', + duration: 2000 + }); + + // 保存到本地(模拟) + const articles = wx.getStorageSync('myArticles') || []; + articles.unshift({ + id: Date.now(), + productName: this.data.productName, + title: this.data.article.title, + content: this.data.article.content, + tags: this.data.article.tags, + createTime: new Date().toISOString(), + status: 'published' + }); + wx.setStorageSync('myArticles', articles); + + // 2秒后返回首页 + setTimeout(() => { + wx.navigateBack(); + }, 2000); + }, 1500); + } + } + } + }); + } +}); diff --git a/miniprogram/miniprogram/pages/article-generate/article-generate.wxml b/miniprogram/miniprogram/pages/article-generate/article-generate.wxml new file mode 100644 index 0000000..0809fb6 --- /dev/null +++ b/miniprogram/miniprogram/pages/article-generate/article-generate.wxml @@ -0,0 +1,52 @@ + + + + + + + + 生成具体内容 + + + + + + + + + + + + × + + + + + + {{article.title}} + + + + {{article.content}} + + + + + + + + + + diff --git a/miniprogram/miniprogram/pages/article-generate/article-generate.wxss b/miniprogram/miniprogram/pages/article-generate/article-generate.wxss new file mode 100644 index 0000000..486f111 --- /dev/null +++ b/miniprogram/miniprogram/pages/article-generate/article-generate.wxss @@ -0,0 +1,184 @@ +/* pages/article-generate/article-generate.wxss */ +page { + background: white; + height: 100%; +} + +.page-container { + height: 100vh; + display: flex; + flex-direction: column; + box-sizing: border-box; + padding-bottom: calc(136rpx + env(safe-area-inset-bottom)); +} + +/* 页面头部 */ +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx 30rpx; + background: white; + border-bottom: 1rpx solid #f0f0f0; +} + +.header-left, +.header-right { + width: 80rpx; +} + +.back-icon { + width: 40rpx; + height: 40rpx; + background-image: url('data:image/svg+xml;utf8,'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.page-title { + font-size: 32rpx; + font-weight: 600; + color: #1a1a1a; +} + +/* 文章容器 */ +.article-container { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + background: white; +} + +.article-wrapper { + padding: 30rpx; +} + +/* 图片列表 */ +.article-images { + display: flex; + flex-wrap: nowrap; + gap: 16rpx; + margin-bottom: 30rpx; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.article-images::-webkit-scrollbar { + display: none; +} + +.image-item { + flex-shrink: 0; + width: 200rpx; + height: 200rpx; + position: relative; + border-radius: 12rpx; + overflow: hidden; +} + +.article-image { + width: 100%; + height: 100%; + background: #f5f5f5; +} + +.delete-icon { + position: absolute; + top: 0; + right: 0; + width: 64rpx; + height: 64rpx; + background: rgba(0, 0, 0, 0.5); + border-radius: 0 12rpx 0 12rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.delete-text { + font-size: 40rpx; + color: white; + line-height: 1; + font-weight: 300; +} + +/* 标题区域 */ +.article-header { + margin-bottom: 24rpx; +} + +.article-title { + font-size: 36rpx; + color: #1a1a1a; + font-weight: bold; + line-height: 1.4; +} + +/* 内容区域 */ +.article-content { + margin-bottom: 24rpx; +} + +.content-text { + font-size: 28rpx; + color: #333; + line-height: 1.8; + white-space: pre-line; +} + +/* 操作栏 */ +.action-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + display: flex; + gap: 20rpx; + padding: 20rpx 30rpx; + background: white; + box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08); + border-top: 1rpx solid #f0f0f0; + padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); +} + +.action-btn { + flex: 1; + height: 96rpx; + border: none; + border-radius: 48rpx; + font-size: 32rpx; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s; +} + +.action-btn::after { + border: none; +} + +.action-btn.secondary { + background: white; + color: #07c160; + border: 2rpx solid #07c160; +} + +.action-btn.secondary:active { + background: #f0f9f4; +} + +.action-btn.primary { + background: #07c160; + color: white; + box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.3); +} + +.action-btn.primary:active { + transform: scale(0.98); +} + +.btn-text { + font-size: 32rpx; +} diff --git a/miniprogram/miniprogram/pages/index/index.json b/miniprogram/miniprogram/pages/index/index.json new file mode 100644 index 0000000..b55b5a2 --- /dev/null +++ b/miniprogram/miniprogram/pages/index/index.json @@ -0,0 +1,4 @@ +{ + "usingComponents": { + } +} \ No newline at end of file diff --git a/miniprogram/miniprogram/pages/index/index.ts b/miniprogram/miniprogram/pages/index/index.ts new file mode 100644 index 0000000..c7aaf97 --- /dev/null +++ b/miniprogram/miniprogram/pages/index/index.ts @@ -0,0 +1,54 @@ +// index.ts +// 获取应用实例 +const app = getApp() +const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0' + +Component({ + data: { + motto: 'Hello World', + userInfo: { + avatarUrl: defaultAvatarUrl, + nickName: '', + }, + hasUserInfo: false, + canIUseGetUserProfile: wx.canIUse('getUserProfile'), + canIUseNicknameComp: wx.canIUse('input.type.nickname'), + }, + methods: { + // 事件处理函数 + bindViewTap() { + wx.navigateTo({ + url: '../logs/logs', + }) + }, + onChooseAvatar(e: any) { + const { avatarUrl } = e.detail + const { nickName } = this.data.userInfo + this.setData({ + "userInfo.avatarUrl": avatarUrl, + hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl, + }) + }, + onInputChange(e: any) { + const nickName = e.detail.value + const { avatarUrl } = this.data.userInfo + this.setData({ + "userInfo.nickName": nickName, + hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl, + }) + }, + getUserProfile() { + // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 + wx.getUserProfile({ + desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 + success: (res) => { + console.log(res) + this.setData({ + userInfo: res.userInfo, + hasUserInfo: true + }) + } + }) + }, + }, +}) diff --git a/miniprogram/miniprogram/pages/index/index.wxml b/miniprogram/miniprogram/pages/index/index.wxml new file mode 100644 index 0000000..0721ba0 --- /dev/null +++ b/miniprogram/miniprogram/pages/index/index.wxml @@ -0,0 +1,27 @@ + + + + + + + + 昵称 + + + + + + 请使用2.10.4及以上版本基础库 + + + + {{userInfo.nickName}} + + + + {{motto}} + + + diff --git a/miniprogram/miniprogram/pages/index/index.wxss b/miniprogram/miniprogram/pages/index/index.wxss new file mode 100644 index 0000000..1ebed4b --- /dev/null +++ b/miniprogram/miniprogram/pages/index/index.wxss @@ -0,0 +1,62 @@ +/**index.wxss**/ +page { + height: 100vh; + display: flex; + flex-direction: column; +} +.scrollarea { + flex: 1; + overflow-y: hidden; +} + +.userinfo { + display: flex; + flex-direction: column; + align-items: center; + color: #aaa; + width: 80%; +} + +.userinfo-avatar { + overflow: hidden; + width: 128rpx; + height: 128rpx; + margin: 20rpx; + border-radius: 50%; +} + +.usermotto { + margin-top: 200px; +} + +.avatar-wrapper { + padding: 0; + width: 56px !important; + border-radius: 8px; + margin-top: 40px; + margin-bottom: 40px; +} + +.avatar { + display: block; + width: 56px; + height: 56px; +} + +.nickname-wrapper { + display: flex; + width: 100%; + padding: 16px; + box-sizing: border-box; + border-top: .5px solid rgba(0, 0, 0, 0.1); + border-bottom: .5px solid rgba(0, 0, 0, 0.1); + color: black; +} + +.nickname-label { + width: 105px; +} + +.nickname-input { + flex: 1; +} diff --git a/miniprogram/miniprogram/pages/login/login.wxml b/miniprogram/miniprogram/pages/login/login.wxml index 65b7043..2414dbc 100644 --- a/miniprogram/miniprogram/pages/login/login.wxml +++ b/miniprogram/miniprogram/pages/login/login.wxml @@ -53,7 +53,11 @@ diff --git a/miniprogram/miniprogram/pages/login/phone-login.wxml b/miniprogram/miniprogram/pages/login/phone-login.wxml index 958255c..6fe0aea 100644 --- a/miniprogram/miniprogram/pages/login/phone-login.wxml +++ b/miniprogram/miniprogram/pages/login/phone-login.wxml @@ -92,10 +92,3 @@ 登录成功 - - - - - 获取验证中 - - diff --git a/miniprogram/miniprogram/pages/profile/env-switch/env-switch.json b/miniprogram/miniprogram/pages/profile/env-switch/env-switch.json new file mode 100644 index 0000000..0b3a051 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/env-switch/env-switch.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "环境切换", + "navigationBarBackgroundColor": "#667eea", + "navigationBarTextStyle": "white" +} diff --git a/miniprogram/miniprogram/pages/profile/env-switch/env-switch.wxml b/miniprogram/miniprogram/pages/profile/env-switch/env-switch.wxml new file mode 100644 index 0000000..61c6480 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/env-switch/env-switch.wxml @@ -0,0 +1,62 @@ + + + + + 当前环境 + + {{currentEnv === 'dev' ? '开发环境' : currentEnv === 'test' ? '测试环境' : '生产环境'}} + + + + + + + + + + {{item.name}} + + + 当前 + 切换 + + + + + + 主服务: + + {{configs[item.key].baseURL}} + + + + Python: + + {{configs[item.key].pythonURL}} + + + + + + + + + ⚠️ 温馨提示 + • 切换环境后会清除登录状态,需要重新登录 + • 开发环境用于本地开发调试 + • 测试环境用于服务器功能测试 + • 生产环境为正式线上环境 + • 点击地址可以复制 + + + + + + + diff --git a/miniprogram/miniprogram/pages/profile/profile.ts b/miniprogram/miniprogram/pages/profile/profile.ts index b9a6af5..03de69a 100644 --- a/miniprogram/miniprogram/pages/profile/profile.ts +++ b/miniprogram/miniprogram/pages/profile/profile.ts @@ -328,6 +328,20 @@ Page({ }); }, + // 数据统计 + goToStats() { + wx.navigateTo({ + url: '/pages/profile/stats/stats' + }); + }, + + // 我的文章 + goToMyArticles() { + wx.navigateTo({ + url: '/pages/profile/my-articles/my-articles' + }); + }, + // 已发布文章 goToPublished() { wx.navigateTo({ @@ -335,6 +349,34 @@ Page({ }); }, + // 收藏夹 + goToFavorites() { + wx.navigateTo({ + url: '/pages/profile/favorites/favorites' + }); + }, + + // 消息通知 + goToNotifications() { + wx.navigateTo({ + url: '/pages/profile/notifications/notifications' + }); + }, + + // 个人资料 + goToUserInfo() { + wx.navigateTo({ + url: '/pages/profile/user-info/user-info' + }); + }, + + // 社交账号绑定 + goToSocialBinding() { + wx.navigateTo({ + url: '/pages/profile/social-binding/social-binding' + }); + }, + // 意见反馈 goToFeedback() { wx.navigateTo({ diff --git a/miniprogram/miniprogram/pages/profile/social-binding/social-binding.json b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.json new file mode 100644 index 0000000..ca7ff3c --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "社交账号绑定", + "navigationBarBackgroundColor": "#07c160", + "navigationBarTextStyle": "white" +} diff --git a/miniprogram/miniprogram/pages/profile/social-binding/social-binding.ts b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.ts new file mode 100644 index 0000000..898432b --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.ts @@ -0,0 +1,188 @@ +import { EmployeeService } from '../../../services/employee'; + +Page({ + data: { + // 小红书 + xiaohongshuBinded: false, + xiaohongshuPhone: '', + xiaohongshuAccount: '', + xiaohongshuCookieExpired: false, // Cookie是否失效 + xiaohongshuStatusText: '去绑定', + xiaohongshuStatusClass: '', + + // 微博 + weiboBinded: false, + weiboPhone: '', + + // 抖音 + douyinBinded: false, + douyinPhone: '' + }, + + onLoad() { + this.loadBindingStatus(); + }, + + onShow() { + // 每次显示时重新加载绑定状态 + this.loadBindingStatus(); + // 同时从后端加载 + this.loadUserProfile(); + }, + + // 从BACKEND加载用户信息(包含小红书绑定状态) + async loadUserProfile() { + try { + const response = await EmployeeService.getProfile(); + + if (response.code === 200 && response.data) { + const userInfo = response.data; + const isBound = userInfo.is_bound_xhs === 1; + // 使用has_xhs_cookie字段判断,而不是xhs_cookie + const hasCookie = userInfo.has_xhs_cookie === true; + + // 判断状态 + let statusText = '去绑定'; + let statusClass = ''; + let cookieExpired = false; + + if (isBound) { + if (hasCookie) { + // 已绑定且Cookie有效 + statusText = '已绑定'; + statusClass = 'binded'; + } else { + // 已绑定但Cookie失效 + statusText = '已失效'; + statusClass = 'expired'; + cookieExpired = true; + } + } + + this.setData({ + xiaohongshuBinded: isBound, + xiaohongshuAccount: userInfo.xhs_account || '', + xiaohongshuPhone: userInfo.xhs_phone || '', + xiaohongshuCookieExpired: cookieExpired, + xiaohongshuStatusText: statusText, + xiaohongshuStatusClass: statusClass + }); + + // 更新本地存储 + if (isBound) { + const bindings = wx.getStorageSync('socialBindings') || {}; + bindings.xiaohongshu = { + phone: userInfo.xhs_phone, + xhs_account: userInfo.xhs_account, + bindTime: userInfo.bound_at || new Date().getTime(), + cookieExpired: cookieExpired + }; + wx.setStorageSync('socialBindings', bindings); + } + } + } catch (error) { + console.error('加载用户信息失败:', error); + } + }, + + // 加载绑定状态 + loadBindingStatus() { + const bindings = wx.getStorageSync('socialBindings') || {}; + + this.setData({ + xiaohongshuBinded: !!bindings.xiaohongshu, + xiaohongshuPhone: (bindings.xiaohongshu && bindings.xiaohongshu.phone) || '', + + weiboBinded: !!bindings.weibo, + weiboPhone: (bindings.weibo && bindings.weibo.phone) || '', + + douyinBinded: !!bindings.douyin, + douyinPhone: (bindings.douyin && bindings.douyin.phone) || '' + }); + }, + + // 跳转到平台绑定页面 + goToPlatformBind(e: any) { + const platform = e.currentTarget.dataset.platform; + + // 如果是小红书 + if (platform === 'xiaohongshu') { + // Cookie失效,直接跳转重新绑定 + if (this.data.xiaohongshuCookieExpired) { + wx.navigateTo({ + url: '/pages/profile/xhs-login/xhs-login' + }); + return; + } + + // 已绑定且Cookie有效,显示解绑确认 + if (this.data.xiaohongshuBinded && !this.data.xiaohongshuCookieExpired) { + this.handleUnbindXHS(); + return; + } + + // 未绑定,跳转到绑定页 + wx.navigateTo({ + url: '/pages/profile/xhs-login/xhs-login' + }); + return; + } + + wx.navigateTo({ + url: `/pages/profile/platform-bind/platform-bind?platform=${platform}` + }); + }, + + // 解绑小红书 + handleUnbindXHS() { + wx.showModal({ + title: '确认解绑', + content: '确定要解绑小红书账号吗?', + confirmText: '解绑', + confirmColor: '#FF6B6B', + success: async (res) => { + if (res.confirm) { + try { + console.log('开始解绑小红书...'); + const response = await EmployeeService.unbindXHS(); + console.log('解绑响应:', response); + + if (response.code === 200) { + wx.showToast({ + title: '解绑成功', + icon: 'success' + }); + + // 清除本地存储 + const bindings = wx.getStorageSync('socialBindings') || {}; + delete bindings.xiaohongshu; + wx.setStorageSync('socialBindings', bindings); + + // 刷新页面 + this.setData({ + xiaohongshuBinded: false, + xiaohongshuPhone: '', + xiaohongshuAccount: '' + }); + + // 重新加载用户信息 + this.loadUserProfile(); + } else { + console.error('解绑失败:', response.message); + wx.showToast({ + title: response.message || '解绑失败', + icon: 'none' + }); + } + } catch (error) { + console.error('解绑失败:', error); + wx.showToast({ + title: '解绑失败', + icon: 'none' + }); + } + } + } + }); + } +}); \ No newline at end of file diff --git a/miniprogram/miniprogram/pages/profile/social-binding/social-binding.wxml b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.wxml new file mode 100644 index 0000000..de2a179 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.wxml @@ -0,0 +1,51 @@ + + + + + + + + + + 小红书 + {{xiaohongshuBinded ? xiaohongshuPhone : '未绑定'}} + + + + {{xiaohongshuStatusText}} + + + + + + + + + + 微博 + {{weiboBinded ? weiboPhone : '未绑定'}} + + + + {{weiboBinded ? '已绑定' : '去绑定'}} + + + + + + + + + + 抖音 + {{douyinBinded ? douyinPhone : '未绑定'}} + + + + {{douyinBinded ? '已绑定' : '去绑定'}} + + + + + + diff --git a/miniprogram/miniprogram/pages/profile/social-binding/social-binding.wxss b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.wxss new file mode 100644 index 0000000..af907d0 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/social-binding/social-binding.wxss @@ -0,0 +1,112 @@ +/* pages/profile/social-binding/social-binding.wxss */ +page { + background: #f8f8f8; + height: 100%; +} + +.page-container { + height: 100vh; + width: 100%; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +.content-scroll { + flex: 1; + width: 100%; + padding: 20rpx; + box-sizing: border-box; +} + +/* 平台列表项 */ +.platform-item { + background: white; + border-radius: 16rpx; + padding: 32rpx; + margin-bottom: 20rpx; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04); + transition: all 0.3s ease; +} + +.platform-item:active { + transform: scale(0.98); + background: #fafafa; +} + +.platform-left { + display: flex; + align-items: center; + gap: 20rpx; + flex: 1; +} + +.platform-icon { + width: 88rpx; + height: 88rpx; + border-radius: 16rpx; + flex-shrink: 0; +} + +.platform-icon.xiaohongshu { + background: #07c160; +} + +.platform-icon.weibo { + background: linear-gradient(135deg, #ff8200 0%, #ffb84d 100%); +} + +.platform-icon.douyin { + background: linear-gradient(135deg, #000000 0%, #333333 100%); +} + +.platform-info { + display: flex; + flex-direction: column; + gap: 8rpx; + flex: 1; +} + +.platform-name { + font-size: 32rpx; + font-weight: 600; + color: #1a1a1a; +} + +.platform-desc { + font-size: 24rpx; + color: #999; +} + +.platform-right { + display: flex; + align-items: center; + gap: 12rpx; +} + +.bind-status { + padding: 8rpx 20rpx; + border-radius: 20rpx; + font-size: 24rpx; + background: #f5f5f5; + color: #999; +} + +.bind-status.binded { + background: #e8f5e9; + color: #4caf50; +} + +.bind-status.expired { + background: #fff3e0; + color: #ff9800; +} + +.item-arrow { + font-size: 40rpx; + color: #d0d0d0; + font-weight: 300; +} diff --git a/miniprogram/miniprogram/pages/profile/user-info/user-info.json b/miniprogram/miniprogram/pages/profile/user-info/user-info.json new file mode 100644 index 0000000..d924707 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/user-info/user-info.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "个人资料", + "navigationBarBackgroundColor": "#07c160", + "navigationBarTextStyle": "white" +} diff --git a/miniprogram/miniprogram/pages/profile/user-info/user-info.ts b/miniprogram/miniprogram/pages/profile/user-info/user-info.ts new file mode 100644 index 0000000..64f60c7 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/user-info/user-info.ts @@ -0,0 +1,171 @@ +// pages/profile/user-info/user-info.ts +import { EmployeeService } from '../../../services/employee'; + +Page({ + data: { + username: '', + nickname: '', + enterpriseName: '', + avatar: '', + phone: '', + email: '', + role: '', + roleText: '', + saving: false + }, + + async onLoad() { + await this.loadUserInfo(); + }, + + async loadUserInfo() { + try { + const response = await EmployeeService.getProfile(); + if (response.code === 200 && response.data) { + const userInfo: any = response.data; + const role = userInfo.role || ''; + const nickname = userInfo.nickname || userInfo.name || userInfo.username || ''; + + this.setData({ + username: userInfo.name || '', + nickname, + enterpriseName: userInfo.enterprise_name || '', + avatar: userInfo.avatar || '', + phone: userInfo.phone || '', + email: userInfo.email || '', + role, + roleText: this.translateRole(role) + }); + } + } catch (error) { + console.error('加载个人信息失败:', error); + } + }, + + translateRole(role: string): string { + switch (role) { + case 'admin': + return '管理员'; + case 'reviewer': + return '审核员'; + case 'publisher': + return '发布员'; + default: + return '内容管理员'; + } + }, + + onNicknameInput(e: any) { + this.setData({ nickname: e.detail.value }); + }, + + onEmailInput(e: any) { + this.setData({ email: e.detail.value }); + }, + + async onChooseWechatAvatar(e: any) { + if (this.data.saving) return; + + const avatarUrl = e.detail.avatarUrl; + if (!avatarUrl) { + return; + } + + try { + wx.showLoading({ title: '上传中...', mask: true }); + const uploadRes = await EmployeeService.uploadImage(avatarUrl); + wx.hideLoading(); + + if (uploadRes.code === 200 && uploadRes.data) { + this.setData({ avatar: uploadRes.data.image_url }); + } else { + wx.showToast({ title: uploadRes.message || '上传失败', icon: 'none' }); + } + } catch (error: any) { + wx.hideLoading(); + if (error && error.errMsg && error.errMsg.indexOf('cancel') !== -1) { + return; + } + console.error('选择头像失败:', error); + wx.showToast({ title: '选择头像失败', icon: 'none' }); + } + }, + + onUseWechatNickname() { + if (this.data.saving) return; + + wx.getUserProfile({ + desc: '用于完善个人资料', + success: (res) => { + const nickname = (res.userInfo && res.userInfo.nickName) || ''; + + if (!nickname) { + wx.showToast({ title: '未获取到微信昵称', icon: 'none' }); + return; + } + + this.setData({ + nickname + }); + }, + fail: (error) => { + if (error && error.errMsg && error.errMsg.indexOf('auth deny') !== -1) { + wx.showToast({ title: '未授权微信昵称', icon: 'none' }); + } else { + wx.showToast({ title: '获取微信昵称失败', icon: 'none' }); + } + } + }); + }, + + onFormSubmit(e: any) { + const { nickname, email } = e.detail.value || {}; + + this.setData({ + nickname, + email + }, () => { + this.handleSave(); + }); + }, + + async handleSave() { + if (this.data.saving) return; + + const { nickname, email, avatar } = this.data; + + if (!nickname || nickname.trim().length === 0) { + wx.showToast({ title: '昵称不能为空', icon: 'none' }); + return; + } + + if (email && email.indexOf('@') === -1) { + wx.showToast({ title: '邮箱格式不正确', icon: 'none' }); + return; + } + + this.setData({ saving: true }); + wx.showLoading({ title: '保存中...', mask: true }); + + try { + const res = await EmployeeService.updateProfile({ + nickname: nickname.trim(), + email: email ? email.trim() : undefined, + avatar + }); + + wx.hideLoading(); + if (res.code === 200) { + wx.showToast({ title: '保存成功', icon: 'success' }); + } else { + wx.showToast({ title: res.message || '保存失败', icon: 'none' }); + } + } catch (error) { + wx.hideLoading(); + console.error('保存个人资料失败:', error); + wx.showToast({ title: '保存失败', icon: 'none' }); + } finally { + this.setData({ saving: false }); + } + } +}); diff --git a/miniprogram/miniprogram/pages/profile/user-info/user-info.wxml b/miniprogram/miniprogram/pages/profile/user-info/user-info.wxml new file mode 100644 index 0000000..3cae5b2 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/user-info/user-info.wxml @@ -0,0 +1,57 @@ + + +
+ + + + + 头像 + + + + + + + + + 昵称 + + + + + + + + 邮箱 + + + + + + + + 手机号 + + {{phone || '未绑定'}} + + + + + + 角色 + + {{roleText}} + + + + + + + +
+
diff --git a/miniprogram/miniprogram/pages/profile/user-info/user-info.wxss b/miniprogram/miniprogram/pages/profile/user-info/user-info.wxss new file mode 100644 index 0000000..a07150d --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/user-info/user-info.wxss @@ -0,0 +1,190 @@ +/* pages/profile/user-info/user-info.wxss */ +page { + background: #f8f8f8; +} + +.page-container { + min-height: 100vh; + padding-bottom: 120rpx; +} + +/* 头部区域,统一为绿色背景,与个人中心一致 */ +.header-section { + width: 100%; + background: #07c160; + padding: 50rpx 30rpx 70rpx; + box-sizing: border-box; +} + +.user-card { + display: flex; + align-items: center; +} + +.avatar { + width: 120rpx; + height: 120rpx; + border-radius: 60rpx; + background: #ffffff; + border: 4rpx solid rgba(255, 255, 255, 0.3); + margin-right: 24rpx; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.avatar-img { + width: 100%; + height: 100%; + border-radius: 60rpx; +} + +.user-info { + flex: 1; + display: flex; + flex-direction: column; +} + +.username { + font-size: 36rpx; + font-weight: bold; + color: #ffffff; + margin-bottom: 8rpx; +} + +.enterprise-name { + font-size: 24rpx; + color: rgba(255, 255, 255, 0.9); +} + +.info-section { + width: 100%; + background: #ffffff; + margin-top: 20rpx; +} + +.info-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 32rpx 30rpx; + border-bottom: 1rpx solid #f0f0f0; +} + +.info-item:last-child { + border-bottom: none; +} + +.item-label { + font-size: 28rpx; + color: #333; + font-weight: 500; +} + +.item-value { + display: flex; + align-items: center; + justify-content: flex-end; + flex: 1; + gap: 0; +} + +.value-text { + font-size: 28rpx; + color: #666; +} + +.value-text.role { + color: #07c160; + font-weight: 600; +} + +.value-input { + min-width: 360rpx; + text-align: right; + font-size: 28rpx; + color: #333; +} + +.item-arrow { + font-size: 36rpx; + color: #ccc; +} + +.avatar-preview { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + background: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; +} + +.avatar-img-small { + width: 100%; + height: 100%; + border-radius: 50%; +} + +.avatar-btn { + padding: 0; + margin: 0; + border: none; + background: transparent; +} + +.avatar-btn::after { + border: none; +} + +.avatar-icon { + width: 50rpx; + height: 50rpx; + background-image: url('data:image/svg+xml;utf8,'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.wechat-sync-btn { + padding: 12rpx 24rpx; + font-size: 24rpx; + color: #07c160; + border-radius: 999rpx; + border: 1rpx solid #07c160; + background-color: #ffffff; +} + +.wechat-sync-btn::after { + border: none; +} + +.save-section { + width: 100%; + padding: 40rpx 30rpx; +} + +.save-btn { + width: 80%; + max-width: 600rpx; + margin: 0 auto; + background: #07c160; + color: #ffffff; + border: none; + border-radius: 999rpx; + padding: 24rpx 0; + font-size: 32rpx; + font-weight: 600; + box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3); +} + +.save-btn::after { + border: none; +} + +.save-btn:active { + opacity: 0.9; + transform: translateY(2rpx); +} diff --git a/miniprogram/miniprogram/pages/profile/xhs-publish/xhs-publish.wxml b/miniprogram/miniprogram/pages/profile/xhs-publish/xhs-publish.wxml new file mode 100644 index 0000000..6ebee33 --- /dev/null +++ b/miniprogram/miniprogram/pages/profile/xhs-publish/xhs-publish.wxml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + ✨ 发布小红书笔记 + 分享你的精彩时刻 + + + + + + 📝 + 标题 + + + {{title.length}}/30 + + + + + + ✍️ + 正文内容 + + + {{content.length}}/1000 + + + + + + 🏷️ + 话题标签 + (可选) + + + + + #{{item}}# + + + + + + + 💡 + 发布前请确保已登录小红书账号 + + + + + + + + + {{toastMessage}} + + diff --git a/start_all.sh b/start_all.sh index 10dafdf..a89fe3f 100644 --- a/start_all.sh +++ b/start_all.sh @@ -113,9 +113,20 @@ echo "[2/2] 正在启动Python服务(生产环境)..." cd backend source venv/bin/activate export ENV=prod -nohup python main.py > ../logs/python_backend_prod.log 2>&1 & -PYTHON_PID=$! -echo "[Python服务] PID: $PYTHON_PID" + +# 检查是否安装了Xvfb +if command -v xvfb-run &> /dev/null; then + echo "[Xvfb] 检测到Xvfb,Python服务将使用虚拟显示运行(避免无头模式被检测)" + nohup xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" python main.py > ../logs/python_backend_prod.log 2>&1 & + PYTHON_PID=$! + echo "[Python服务] PID: $PYTHON_PID (使用Xvfb虚拟显示)" +else + echo "[警告] 未检测到Xvfb,Python服务将使用无头模式(可能触发验证)" + echo "[提示] 安装Xvfb: sudo apt-get install -y xvfb" + nohup python main.py > ../logs/python_backend_prod.log 2>&1 & + PYTHON_PID=$! + echo "[Python服务] PID: $PYTHON_PID (无头模式)" +fi cd .. echo