commit
This commit is contained in:
@@ -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(防指纹识别)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "文章详情",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -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: '' // 可以设置文章封面图
|
||||
};
|
||||
}
|
||||
});
|
||||
115
miniprogram/miniprogram/pages/article-detail/article-detail.wxml
Normal file
115
miniprogram/miniprogram/pages/article-detail/article-detail.wxml
Normal file
@@ -0,0 +1,115 @@
|
||||
<!--pages/article-detail/article-detail.wxml-->
|
||||
<scroll-view class="container" scroll-y>
|
||||
<view class="article-cover" style="background-color: {{article.coverColor}}">
|
||||
<text class="cover-icon">{{article.coverIcon}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-content">
|
||||
<view class="detail-title">{{article.title}}</view>
|
||||
|
||||
<view class="article-meta-info">
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">批次ID:</text>
|
||||
<text class="meta-value">{{article.batch_id}}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">话题:</text>
|
||||
<text class="meta-value">{{article.topic}}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">部门:</text>
|
||||
<text class="meta-value">{{article.department_name || article.department}}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">渠道:</text>
|
||||
<text class="channel-tag channel-{{article.channel}}">{{article.channelText}}</text>
|
||||
</view>
|
||||
<view class="meta-item full-width" wx:if="{{article.coze_tag}}">
|
||||
<text class="meta-label">标签:</text>
|
||||
<text class="meta-value">{{article.coze_tag}}</text>
|
||||
</view>
|
||||
<view class="meta-item full-width" wx:if="{{article.review_comment}}">
|
||||
<text class="meta-label">审核意见:</text>
|
||||
<text class="meta-value">{{article.review_comment}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-author">
|
||||
<view class="detail-author-avatar" style="background-color: {{article.coverColor}}">
|
||||
{{article.author_name ? article.author_name[0] : 'A'}}
|
||||
</view>
|
||||
<view class="author-info">
|
||||
<view class="author-name">{{article.author_name || '匿名'}}</view>
|
||||
<view class="author-time">创建于 {{article.created_at}}</view>
|
||||
</view>
|
||||
<view class="article-status status-{{article.status}}">{{article.statusText}}</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-text">{{article.content}}</view>
|
||||
|
||||
<view class="detail-stats">
|
||||
<view class="detail-stat">
|
||||
<view class="detail-stat-value">{{article.word_count}}</view>
|
||||
<view class="detail-stat-label">字数</view>
|
||||
</view>
|
||||
<view class="detail-stat">
|
||||
<view class="detail-stat-value">{{article.image_count}}</view>
|
||||
<view class="detail-stat-label">图片</view>
|
||||
</view>
|
||||
<view class="detail-stat">
|
||||
<view class="detail-stat-value">{{article.timeText}}</view>
|
||||
<view class="detail-stat-label">创建时间</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-buttons" wx:if="{{showActions}}">
|
||||
<button
|
||||
class="action-btn approve-btn"
|
||||
wx:if="{{article.status === 'pending_review'}}"
|
||||
bindtap="handleApprove"
|
||||
>
|
||||
✓ 通过审核
|
||||
</button>
|
||||
<button
|
||||
class="action-btn reject-btn"
|
||||
wx:if="{{article.status === 'pending_review'}}"
|
||||
bindtap="handleReject"
|
||||
>
|
||||
✕ 驳回
|
||||
</button>
|
||||
<button
|
||||
class="action-btn publish-btn"
|
||||
wx:if="{{article.status === 'approved'}}"
|
||||
bindtap="handlePublish"
|
||||
>
|
||||
📤 发布文章
|
||||
</button>
|
||||
<button
|
||||
class="action-btn publish-btn disabled"
|
||||
wx:if="{{article.status === 'published'}}"
|
||||
disabled
|
||||
>
|
||||
✓ 已发布
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="review-section" wx:if="{{showReviewSection}}">
|
||||
<view class="review-title">审核意见</view>
|
||||
<textarea
|
||||
class="review-textarea"
|
||||
placeholder="{{reviewAction === 'reject' ? '请输入驳回原因(必填)' : '请输入审核意见(可选)'}}"
|
||||
value="{{reviewComment}}"
|
||||
bindinput="onCommentInput"
|
||||
maxlength="500"
|
||||
></textarea>
|
||||
<view class="action-buttons">
|
||||
<button class="action-btn approve-btn" bindtap="confirmReview">
|
||||
✓ 确认提交
|
||||
</button>
|
||||
<button class="action-btn cancel-btn" bindtap="cancelReview">
|
||||
✕ 取消
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
341
miniprogram/miniprogram/pages/article-detail/article-detail.wxss
Normal file
341
miniprogram/miniprogram/pages/article-detail/article-detail.wxss
Normal file
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "AI生成种草文章",
|
||||
"navigationBarBackgroundColor": "#ff2442",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
<!--pages/article-generate/article-generate.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 顶部标题 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" bindtap="goBack">
|
||||
<view class="back-icon"></view>
|
||||
</view>
|
||||
<text class="page-title">生成具体内容</text>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 文章内容 -->
|
||||
<scroll-view class="article-container" scroll-y enable-flex>
|
||||
<view class="article-wrapper">
|
||||
<!-- 图片列表 -->
|
||||
<view class="article-images" wx:if="{{article.images.length > 0}}">
|
||||
<view
|
||||
class="image-item"
|
||||
wx:for="{{article.images}}"
|
||||
wx:key="index"
|
||||
>
|
||||
<image
|
||||
class="article-image"
|
||||
src="{{item}}"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="delete-icon" bindtap="deleteImage" data-index="{{index}}">
|
||||
<text class="delete-text">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 标题和内容 -->
|
||||
<view class="article-header">
|
||||
<text class="article-title">{{article.title}}</text>
|
||||
</view>
|
||||
|
||||
<view class="article-content">
|
||||
<text class="content-text">{{article.content}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="action-bar">
|
||||
<button class="action-btn secondary" bindtap="regenerateArticle">
|
||||
<text class="btn-text">换一换</text>
|
||||
</button>
|
||||
<button class="action-btn primary" bindtap="publishArticle">
|
||||
<text class="btn-text">一键发布</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%231a1a1a"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>');
|
||||
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;
|
||||
}
|
||||
4
miniprogram/miniprogram/pages/index/index.json
Normal file
4
miniprogram/miniprogram/pages/index/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
}
|
||||
}
|
||||
54
miniprogram/miniprogram/pages/index/index.ts
Normal file
54
miniprogram/miniprogram/pages/index/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// index.ts
|
||||
// 获取应用实例
|
||||
const app = getApp<IAppOption>()
|
||||
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
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
27
miniprogram/miniprogram/pages/index/index.wxml
Normal file
27
miniprogram/miniprogram/pages/index/index.wxml
Normal file
@@ -0,0 +1,27 @@
|
||||
<!--index.wxml-->
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="container">
|
||||
<view class="userinfo">
|
||||
<block wx:if="{{canIUseNicknameComp && !hasUserInfo}}">
|
||||
<button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
|
||||
<image class="avatar" src="{{userInfo.avatarUrl}}"></image>
|
||||
</button>
|
||||
<view class="nickname-wrapper">
|
||||
<text class="nickname-label">昵称</text>
|
||||
<input type="nickname" class="nickname-input" placeholder="请输入昵称" bind:change="onInputChange" />
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{!hasUserInfo}}">
|
||||
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
|
||||
<view wx:else> 请使用2.10.4及以上版本基础库 </view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
|
||||
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
|
||||
</block>
|
||||
</view>
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
62
miniprogram/miniprogram/pages/index/index.wxss
Normal file
62
miniprogram/miniprogram/pages/index/index.wxss
Normal file
@@ -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;
|
||||
}
|
||||
@@ -53,7 +53,11 @@
|
||||
<label class="agreement-label">
|
||||
<checkbox value="agree" checked="{{agreed}}" color="#07c160" />
|
||||
<view class="agreement-text">
|
||||
<text class="normal-text">已阅读并同意《万花筒AI用户协议》和《隐私协议》,允许平台统一管理本人账号信息</text>
|
||||
<text class="normal-text">已阅读并同意</text>
|
||||
<text class="link-text" bindtap="goToUserAgreement">《万花筒AI用户协议》</text>
|
||||
<text class="normal-text">和</text>
|
||||
<text class="link-text" bindtap="goToPrivacyPolicy">《隐私协议》</text>
|
||||
<text class="normal-text">,允许平台统一管理本人账号信息</text>
|
||||
</view>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
|
||||
@@ -92,10 +92,3 @@
|
||||
<text class="toast-text">登录成功</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="toast-overlay" wx:if="{{showLoading}}">
|
||||
<view class="toast">
|
||||
<view class="toast-loading"></view>
|
||||
<text class="toast-text">获取验证中</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "环境切换",
|
||||
"navigationBarBackgroundColor": "#667eea",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<!--pages/profile/env-switch/env-switch.wxml-->
|
||||
<view class="container">
|
||||
<!-- 当前环境 -->
|
||||
<view class="current-env">
|
||||
<view class="env-label">当前环境</view>
|
||||
<view class="env-value">
|
||||
<text class="env-tag env-tag-{{currentEnv}}">{{currentEnv === 'dev' ? '开发环境' : currentEnv === 'test' ? '测试环境' : '生产环境'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 环境列表 -->
|
||||
<view class="env-list">
|
||||
<view
|
||||
class="env-item {{currentEnv === item.key ? 'active' : ''}}"
|
||||
wx:for="{{envList}}"
|
||||
wx:key="key"
|
||||
bindtap="switchEnvironment"
|
||||
data-env="{{item.key}}"
|
||||
>
|
||||
<view class="env-header">
|
||||
<view class="env-name">
|
||||
<text class="env-icon" style="background-color: {{item.color}}"></text>
|
||||
<text>{{item.name}}</text>
|
||||
</view>
|
||||
<view class="env-status">
|
||||
<text wx:if="{{currentEnv === item.key}}" class="current-tag">当前</text>
|
||||
<text wx:else class="switch-text">切换</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="env-info">
|
||||
<view class="info-item">
|
||||
<text class="info-label">主服务:</text>
|
||||
<text class="info-value" bindtap="copyURL" catchtap="copyURL" data-url="{{configs[item.key].baseURL}}">
|
||||
{{configs[item.key].baseURL}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-item" wx:if="{{configs[item.key].pythonURL}}">
|
||||
<text class="info-label">Python:</text>
|
||||
<text class="info-value" bindtap="copyURL" catchtap="copyURL" data-url="{{configs[item.key].pythonURL}}">
|
||||
{{configs[item.key].pythonURL}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 说明 -->
|
||||
<view class="tips">
|
||||
<view class="tips-title">⚠️ 温馨提示</view>
|
||||
<view class="tips-item">• 切换环境后会清除登录状态,需要重新登录</view>
|
||||
<view class="tips-item">• 开发环境用于本地开发调试</view>
|
||||
<view class="tips-item">• 测试环境用于服务器功能测试</view>
|
||||
<view class="tips-item">• 生产环境为正式线上环境</view>
|
||||
<view class="tips-item">• 点击地址可以复制</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="actions">
|
||||
<button class="action-btn" bindtap="restartApp">重启小程序</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -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({
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "社交账号绑定",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--pages/profile/social-binding/social-binding.wxml-->
|
||||
<view class="page-container">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
|
||||
<!-- 小红书 -->
|
||||
<view class="platform-item" bindtap="goToPlatformBind" data-platform="xiaohongshu">
|
||||
<view class="platform-left">
|
||||
<view class="platform-icon xiaohongshu"></view>
|
||||
<view class="platform-info">
|
||||
<text class="platform-name">小红书</text>
|
||||
<text class="platform-desc">{{xiaohongshuBinded ? xiaohongshuPhone : '未绑定'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="platform-right">
|
||||
<view class="bind-status {{xiaohongshuStatusClass}}">{{xiaohongshuStatusText}}</view>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微博 -->
|
||||
<view class="platform-item" bindtap="goToPlatformBind" data-platform="weibo">
|
||||
<view class="platform-left">
|
||||
<view class="platform-icon weibo"></view>
|
||||
<view class="platform-info">
|
||||
<text class="platform-name">微博</text>
|
||||
<text class="platform-desc">{{weiboBinded ? weiboPhone : '未绑定'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="platform-right">
|
||||
<view class="bind-status {{weiboBinded ? 'binded' : ''}}">{{weiboBinded ? '已绑定' : '去绑定'}}</view>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 抖音 -->
|
||||
<view class="platform-item" bindtap="goToPlatformBind" data-platform="douyin">
|
||||
<view class="platform-left">
|
||||
<view class="platform-icon douyin"></view>
|
||||
<view class="platform-info">
|
||||
<text class="platform-name">抖音</text>
|
||||
<text class="platform-desc">{{douyinBinded ? douyinPhone : '未绑定'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="platform-right">
|
||||
<view class="bind-status {{douyinBinded ? 'binded' : ''}}">{{douyinBinded ? '已绑定' : '去绑定'}}</view>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</scroll-view>
|
||||
</view>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "个人资料",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
171
miniprogram/miniprogram/pages/profile/user-info/user-info.ts
Normal file
171
miniprogram/miniprogram/pages/profile/user-info/user-info.ts
Normal file
@@ -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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
<!--pages/profile/user-info/user-info.wxml-->
|
||||
<view class="page-container">
|
||||
<form bindsubmit="onFormSubmit">
|
||||
<!-- 资料编辑区域 -->
|
||||
<view class="info-section">
|
||||
<!-- 头像,使用微信官方选择头像能力 -->
|
||||
<view class="info-item">
|
||||
<view class="item-label">头像</view>
|
||||
<view class="item-value">
|
||||
<button class="avatar-btn" open-type="chooseAvatar" bindchooseavatar="onChooseWechatAvatar">
|
||||
<view class="avatar-preview">
|
||||
<image wx:if="{{avatar}}" class="avatar-img-small" src="{{avatar}}" mode="aspectFill" />
|
||||
<text wx:else class="avatar-icon"></text>
|
||||
</view>
|
||||
</button>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 昵称,可编辑 -->
|
||||
<view class="info-item">
|
||||
<view class="item-label">昵称</view>
|
||||
<view class="item-value">
|
||||
<input class="value-input" name="nickname" type="nickname" value="{{nickname}}" placeholder="请输入昵称" maxlength="20" bindinput="onNicknameInput" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 邮箱,可编辑 -->
|
||||
<view class="info-item">
|
||||
<view class="item-label">邮箱</view>
|
||||
<view class="item-value">
|
||||
<input class="value-input" name="email" value="{{email}}" placeholder="请输入邮箱" bindinput="onEmailInput" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号,只读 -->
|
||||
<view class="info-item">
|
||||
<view class="item-label">手机号</view>
|
||||
<view class="item-value">
|
||||
<text class="value-text">{{phone || '未绑定'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 角色,只读 -->
|
||||
<view class="info-item">
|
||||
<view class="item-label">角色</view>
|
||||
<view class="item-value">
|
||||
<text class="value-text role">{{roleText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="save-section">
|
||||
<button class="save-btn" loading="{{saving}}" disabled="{{saving}}" form-type="submit">保存修改</button>
|
||||
</view>
|
||||
</form>
|
||||
</view>
|
||||
190
miniprogram/miniprogram/pages/profile/user-info/user-info.wxss
Normal file
190
miniprogram/miniprogram/pages/profile/user-info/user-info.wxss
Normal file
@@ -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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23ffffff"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<!--pages/profile/xhs-publish/xhs-publish.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 渐变红背景 + 动画效果 -->
|
||||
<view class="background">
|
||||
<view class="gradient-bg"></view>
|
||||
<view class="circle circle1"></view>
|
||||
<view class="circle circle2"></view>
|
||||
<view class="circle circle3"></view>
|
||||
</view>
|
||||
|
||||
<!-- 发布卡片 - 毛玻璃效果 -->
|
||||
<view class="publish-card">
|
||||
<view class="card-header">
|
||||
<text class="header-title">✨ 发布小红书笔记</text>
|
||||
<text class="header-subtitle">分享你的精彩时刻</text>
|
||||
</view>
|
||||
|
||||
<!-- 标题输入 -->
|
||||
<view class="input-group">
|
||||
<view class="input-label">
|
||||
<text class="label-icon">📝</text>
|
||||
<text class="label-text">标题</text>
|
||||
</view>
|
||||
<input
|
||||
class="input-field"
|
||||
type="text"
|
||||
placeholder="给你的笔记起个吸引人的标题"
|
||||
value="{{title}}"
|
||||
bindinput="onTitleInput"
|
||||
maxlength="30"
|
||||
/>
|
||||
<view class="char-count">{{title.length}}/30</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容输入 -->
|
||||
<view class="input-group">
|
||||
<view class="input-label">
|
||||
<text class="label-icon">✍️</text>
|
||||
<text class="label-text">正文内容</text>
|
||||
</view>
|
||||
<textarea
|
||||
class="textarea-field"
|
||||
placeholder="分享你的故事、经验或想法..."
|
||||
value="{{content}}"
|
||||
bindinput="onContentInput"
|
||||
maxlength="1000"
|
||||
auto-height
|
||||
></textarea>
|
||||
<view class="char-count">{{content.length}}/1000</view>
|
||||
</view>
|
||||
|
||||
<!-- 话题标签 -->
|
||||
<view class="input-group">
|
||||
<view class="input-label">
|
||||
<text class="label-icon">🏷️</text>
|
||||
<text class="label-text">话题标签</text>
|
||||
<text class="label-tip">(可选)</text>
|
||||
</view>
|
||||
<input
|
||||
class="input-field"
|
||||
type="text"
|
||||
placeholder="输入话题后按空格分隔,如:旅行 美食 生活"
|
||||
value="{{topicsText}}"
|
||||
bindinput="onTopicsInput"
|
||||
/>
|
||||
<view class="topics-preview" wx:if="{{topics.length > 0}}">
|
||||
<block wx:for="{{topics}}" wx:key="index">
|
||||
<view class="topic-tag">#{{item}}#</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view class="tips-box">
|
||||
<text class="tips-icon">💡</text>
|
||||
<text class="tips-text">发布前请确保已登录小红书账号</text>
|
||||
</view>
|
||||
|
||||
<!-- 发布按钮 -->
|
||||
<button
|
||||
class="publish-btn {{publishing ? 'disabled' : ''}}"
|
||||
bindtap="handlePublish"
|
||||
disabled="{{publishing}}"
|
||||
>
|
||||
<text wx:if="{{!publishing}}">🚀 立即发布</text>
|
||||
<text wx:else>发布中...</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- Toast提示 -->
|
||||
<view class="toast {{showToast ? 'show' : ''}}" wx:if="{{toastMessage}}">
|
||||
<text>{{toastMessage}}</text>
|
||||
</view>
|
||||
</view>
|
||||
17
start_all.sh
17
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
|
||||
|
||||
Reference in New Issue
Block a user