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}}
+
+
+
+ 批次ID:
+ {{article.batch_id}}
+
+
+ 话题:
+ {{article.topic}}
+
+
+ 部门:
+ {{article.department_name || article.department}}
+
+
+ 渠道:
+ {{article.channelText}}
+
+
+ 标签:
+ {{article.coze_tag}}
+
+
+ 审核意见:
+ {{article.review_comment}}
+
+
+
+
+
+ {{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' ? '测试环境' : '生产环境'}}
+
+
+
+
+
+
+
+
+
+
+ 主服务:
+
+ {{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 @@
+
+
+
+
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