first commit

This commit is contained in:
sjk
2025-12-19 22:36:48 +08:00
commit 6802624e59
185 changed files with 43430 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
# 小程序多环境配置说明
## 📝 配置文件
文件位置: `miniprogram/miniprogram/config/api.ts`
## 🎯 环境说明
小程序支持三种环境,会根据运行版本**自动切换**:
| 小程序版本 | 环境 | 说明 |
|-----------|------|------|
| 开发版 (develop) | `dev` | 微信开发者工具、真机调试 |
| 体验版 (trial) | `test` | 上传体验版后自动使用 |
| 正式版 (release) | `prod` | 发布正式版后自动使用 |
## ⚙️ 配置方法
### 修改后端地址
编辑 `api.ts` 文件中的 `API_CONFIG` 配置:
```typescript
const API_CONFIG: Record<EnvType, EnvConfig> = {
// 开发环境 - 本地开发
dev: {
baseURL: 'http://localhost:8080', // 本地Go服务
pythonURL: 'http://localhost:8000', // 本地Python服务
timeout: 30000
},
// 测试环境 - 服务器测试
test: {
baseURL: 'http://8.149.233.36:8070', // 测试服务器Go服务
pythonURL: 'http://8.149.233.36:8000', // 测试服务器Python服务
timeout: 30000
},
// 生产环境
prod: {
baseURL: 'https://api.yourdomain.com', // 生产环境Go服务 (需修改)
pythonURL: 'https://python.yourdomain.com', // 生产环境Python服务 (需修改)
timeout: 30000
}
};
```
### 配置项说明
- **baseURL**: 主服务地址 (Go Backend)
- **pythonURL**: Python 服务地址 (可选,用于小红书发布等功能)
- **timeout**: 请求超时时间 (毫秒)
## 🚀 使用示例
### 1. 本地开发
- 在微信开发者工具中打开项目
- 自动使用 `dev` 环境配置
- 后端地址: `http://localhost:8080`
### 2. 服务器测试
- 点击"上传"按钮上传代码
- 在微信公众平台设置为体验版
- 自动使用 `test` 环境配置
- 后端地址: `http://8.149.233.36:8070`
### 3. 正式发布
- 提交审核并发布
- 自动使用 `prod` 环境配置
- 后端地址: `https://api.yourdomain.com` (**需先修改配置**)
## 🔍 调试信息
启动小程序时,会在控制台输出当前环境信息:
```
[API Config] 当前环境: dev
[API Config] 主服务: http://localhost:8080
[API Config] Python服务: http://localhost:8000
```
可在微信开发者工具的控制台查看。
## ⚠️ 注意事项
1. **生产环境必须修改**: 发布前务必将 `prod` 配置中的域名修改为实际的生产服务器地址
2. **域名配置**: 小程序只能访问已在微信公众平台配置的合法域名
- 登录 [微信公众平台](https://mp.weixin.qq.com/)
- 进入"开发" → "开发管理" → "开发设置"
- 在"服务器域名"中添加你的域名
3. **HTTPS要求**: 正式版必须使用 HTTPS 协议 (开发工具可以关闭校验)
4. **环境隔离**: 不同环境使用不同的数据库,避免测试数据污染生产数据
## 📋 快速检查清单
发布前检查:
- [ ] 已修改 `prod.baseURL` 为实际生产地址
- [ ] 已修改 `prod.pythonURL` 为实际生产地址 (如果使用)
- [ ] 生产域名已在微信公众平台配置
- [ ] 生产服务器使用 HTTPS 协议
- [ ] Go 后端服务已部署并启动
- [ ] Python 服务已部署并启动 (如果使用)
## 🛠 技术说明
### 环境自动检测
通过微信 API 自动检测:
```typescript
function detectEnvironment(): EnvType {
const accountInfo = wx.getAccountInfoSync();
const envVersion = accountInfo.miniProgram.envVersion;
switch (envVersion) {
case 'develop': return 'dev';
case 'trial': return 'test';
case 'release': return 'prod';
default: return 'dev';
}
}
```
### 动态配置加载
所有 API 调用都会动态获取当前环境的配置:
```typescript
export const API = {
get baseURL(): string {
return currentConfig().baseURL;
},
// ...
};
```
这样可以确保始终使用正确环境的后端地址。

110
miniprogram/README.md Normal file
View File

@@ -0,0 +1,110 @@
# AI文章审核小程序
根据 index.html 页面原型完成的微信小程序界面。
## 功能页面
### 1. 登录页面 (pages/login)
- 微信一键登录
- 登录成功后自动跳转到文章列表页
### 2. 文章列表页 (pages/articles)
- 状态筛选:全部、待审核、已通过、已驳回、已发布
- 显示文章卡片信息:
- 封面(根据话题类型自动生成颜色和图标)
- 标题
- 作者信息
- 渠道标签(百度/头条/微信)
- 状态标签
- 统计信息(字数、图片数、创建时间)
- 点击文章卡片进入详情页
### 3. 文章详情页 (pages/article-detail)
- 显示完整文章信息:
- 封面
- 标题
- 元信息批次ID、话题、部门、渠道、标签等
- 作者信息和状态
- 文章内容
- 统计数据
- 审核功能:
- 待审核状态:可通过或驳回
- 已通过状态:可发布文章
- 已发布状态:显示已发布
- 审核意见输入
### 4. 我的页面 (pages/index)
- 保留原有功能(可后续扩展)
## 技术特点
1. **TypeScript 开发**:完整的类型支持
2. **模拟数据**:内置 6 篇文章数据,包含所有状态
3. **工具函数**
- formatDate智能日期格式化
- getStatusInfo状态信息映射
- getChannelInfo渠道信息映射
- getCoverColor封面颜色生成
- getCoverIcon封面图标生成
4. **UI 设计**
- 品牌色:#ff2442(小红书红)
- 响应式布局
- 丰富的状态样式
- 优雅的交互动画
5. **状态管理**
- 11 种文章状态
- 3 种发布渠道
- 完整的审核流程
## 使用说明
1. 首次打开小程序,显示登录页
2. 点击"微信一键登录"进行授权
3. 登录成功后自动跳转到文章列表
4. 在文章列表页:
- 点击顶部标签切换筛选条件
- 点击文章卡片查看详情
5. 在文章详情页:
- 待审核文章可以通过或驳回
- 已通过文章可以发布
- 驳回时必须填写原因
## 数据说明
当前使用模拟数据,包含:
- 2 篇待审核文章
- 1 篇已通过文章
- 1 篇已驳回文章
- 1 篇已发布文章
- 1 篇草稿文章
实际使用时需要:
1. 将模拟数据替换为 API 调用
2. 实现真实的登录认证
3. 添加网络请求处理
4. 实现数据持久化
## 目录结构
```
miniprogram/
├── pages/
│ ├── login/ # 登录页
│ ├── articles/ # 文章列表页
│ ├── article-detail/ # 文章详情页
│ └── index/ # 我的页面
├── utils/
│ └── util.ts # 工具函数
├── app.ts # 小程序入口
├── app.json # 小程序配置
└── app.wxss # 全局样式
```
## 注意事项
1. TabBar 图标需要自行准备images 目录)
2. 登录功能需要配置小程序 AppID
3. 审核操作目前只是模拟,未真实保存数据
4. 建议在真实环境中添加加载状态和错误处理

View File

@@ -0,0 +1,219 @@
# 小程序分享功能说明
## 📋 功能概述
万花筒AI助手小程序已支持**分享给好友**和**分享到朋友圈**功能,用户可以将精彩内容分享给微信好友或发布到朋友圈。
## ✨ 已实现页面
### 1. **首页** (`pages/home/home`)
- **分享标题**: 万花筒AI助手 - 智能生成种草文案
- **分享路径**: `/pages/home/home`
- **应用场景**: 用户浏览产品列表时可分享小程序首页
### 2. **文章详情页** (`pages/article-detail/article-detail`)
- **分享标题**: 动态显示文章标题
- **分享路径**: 带文章ID和产品ID参数
- **应用场景**: 用户查看文案详情时可分享特定文案
### 3. **文章列表页** (`pages/articles/articles`)
- **分享标题**: 动态显示当前文案标题
- **分享路径**: 带产品ID参数
- **分享封面**: 使用产品图片
- **应用场景**: 用户浏览文案列表时分享
### 4. **我的发布** (`pages/profile/published/published`)
- **分享标题**: 我的发布记录 - 万花筒AI助手
- **分享路径**: `/pages/home/home`
- **应用场景**: 用户查看自己的发布记录时分享
## 🎯 分享方式
### 方式一: 右上角菜单分享
用户点击小程序右上角的 `...` 按钮,可以看到:
- **转发** - 分享给微信好友/群
- **分享到朋友圈** - 发布到微信朋友圈
### 方式二: 页面内分享按钮(可选)
可以在页面中添加自定义分享按钮:
```html
<!-- WXML -->
<button open-type="share" class="share-btn">
分享给好友
</button>
```
## 💡 技术实现
### 1. 分享给好友 (onShareAppMessage)
```typescript
onShareAppMessage() {
return {
title: '分享标题', // 分享卡片标题
path: '/pages/home/home', // 分享路径
imageUrl: '' // 分享封面图(可选)
};
}
```
### 2. 分享到朋友圈 (onShareTimeline)
```typescript
onShareTimeline() {
return {
title: '分享标题', // 朋友圈显示标题
imageUrl: '' // 分享封面图(可选)
};
}
```
## 📝 配置要求
### app.json 配置
确保启用朋友圈分享:
```json
{
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
}
}
```
### 页面配置
在需要分享的页面.json中可配置:
```json
{
"navigationBarTitleText": "页面标题",
"enableShareAppMessage": true, // 允许分享给好友(默认true)
"enableShareTimeline": true // 允许分享到朋友圈(默认true)
}
```
## 🎨 优化建议
### 1. 自定义分享封面
为提升分享效果,建议添加分享封面图:
```typescript
onShareAppMessage() {
return {
title: '精彩种草文案',
path: '/pages/home/home',
imageUrl: '/images/share-cover.jpg' // 建议尺寸: 5:4
};
}
```
**推荐尺寸**:
- 分享给好友: 5:4 (例如: 500x400)
- 分享到朋友圈: 1:1 (例如: 500x500)
### 2. 动态分享内容
根据页面内容动态生成分享信息:
```typescript
onShareAppMessage() {
const currentArticle = this.data.currentArticle;
return {
title: currentArticle.title,
path: `/pages/article-detail/article-detail?id=${currentArticle.id}`,
imageUrl: currentArticle.coverImage
};
}
```
### 3. 分享数据统计
可以在分享时记录统计数据:
```typescript
onShareAppMessage() {
// 记录分享事件
wx.reportAnalytics('share_action', {
page: 'home',
content_type: 'product_list'
});
return {
title: '万花筒AI助手',
path: '/pages/home/home'
};
}
```
## 🔍 分享参数传递
### 场景还原
用户通过分享卡片进入小程序时,可以获取分享参数:
```typescript
onLoad(options: any) {
// 获取分享参数
const productId = options.productId;
const articleId = options.articleId;
if (productId) {
// 直接加载对应产品
this.loadProduct(productId);
}
}
```
### 分享路径示例
```typescript
// 带参数的分享路径
path: `/pages/articles/articles?productId=${this.data.productId}&from=share`
// 多个参数
path: `/pages/detail/detail?id=123&type=article&source=timeline`
```
## ⚠️ 注意事项
1. **路径限制**: 分享路径必须是已在 `app.json` 中注册的页面路径
2. **参数长度**: 分享路径总长度不能超过 128 字节
3. **图片要求**:
- 分享图片支持本地路径和网络图片
- 网络图片需要先下载到本地
4. **朋友圈限制**: 分享到朋友圈时,`title` 不能超过 32 个字符
5. **用户取消**: 用户可能取消分享,无法强制分享
## 📊 分享效果监控
### 分享回调
小程序没有直接的分享成功回调,但可以通过以下方式间接监控:
1. **分享参数标记**: 在分享路径中添加 `from=share` 参数
2. **数据统计**: 统计带分享参数的访问量
3. **微信数据助手**: 在微信公众平台查看分享数据
## 🚀 未来优化方向
1. **个性化分享**: 根据用户行为推荐分享内容
2. **分享激励**: 添加分享奖励机制
3. **社交裂变**: 设计分享裂变活动
4. **A/B测试**: 测试不同分享文案的效果
## 📱 测试方法
### 开发环境测试
1. 打开微信开发者工具
2. 点击右上角 `...` 菜单
3. 选择 "转发" 或 "分享到朋友圈"
4. 查看分享卡片预览效果
### 真机测试
1. 使用体验版或开发版小程序
2. 在真实微信环境中测试分享
3. 验证分享卡片样式和跳转
## 📚 相关文档
- [微信官方文档 - 转发](https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onShareAppMessage-Object-object)
- [微信官方文档 - 分享到朋友圈](https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onShareTimeline)

View File

@@ -0,0 +1,96 @@
/**
* TabBar 图标下载脚本
* 从 Iconfont 下载图标并自动配置
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
// 图标配置
const icons = [
{
name: 'home',
url: 'https://img.icons8.com/ios/100/999999/home--v1.png',
desc: '首页-未选中'
},
{
name: 'home-active',
url: 'https://img.icons8.com/ios-filled/100/07c160/home--v1.png',
desc: '首页-选中'
},
{
name: 'article',
url: 'https://img.icons8.com/ios/100/999999/document--v1.png',
desc: '我的发布-未选中'
},
{
name: 'article-active',
url: 'https://img.icons8.com/ios-filled/100/07c160/document--v1.png',
desc: '我的发布-选中'
},
{
name: 'user',
url: 'https://img.icons8.com/ios/100/999999/user--v1.png',
desc: '我的-未选中'
},
{
name: 'user-active',
url: 'https://img.icons8.com/ios-filled/100/07c160/user--v1.png',
desc: '我的-选中'
}
];
// 创建目标目录
const targetDir = path.join(__dirname, 'miniprogram', 'images', 'tabbar');
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
console.log('✅ 创建目录:', targetDir);
}
// 下载单个图标
function downloadIcon(icon) {
return new Promise((resolve, reject) => {
const filePath = path.join(targetDir, `${icon.name}.png`);
const file = fs.createWriteStream(filePath);
https.get(icon.url, (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
console.log(`✅ 下载成功: ${icon.desc} (${icon.name}.png)`);
resolve();
});
}).on('error', (err) => {
fs.unlink(filePath, () => {});
console.error(`❌ 下载失败: ${icon.desc}`, err.message);
reject(err);
});
});
}
// 批量下载
async function downloadAll() {
console.log('\n🚀 开始下载 TabBar 图标...\n');
try {
for (const icon of icons) {
await downloadIcon(icon);
// 延迟避免请求过快
await new Promise(resolve => setTimeout(resolve, 500));
}
console.log('\n✨ 所有图标下载完成!\n');
console.log('📁 图标位置:', targetDir);
console.log('\n📝 下一步:');
console.log('1. 打开 miniprogram/app.json');
console.log('2. 在 tabBar.list 中添加图标路径');
console.log('3. 重新编译小程序\n');
} catch (error) {
console.error('\n❌ 下载过程出错:', error);
}
}
// 执行下载
downloadAll();

View File

@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TabBar 图标生成器</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin: 30px 0;
}
.icon-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}
canvas {
border: 1px solid #ddd;
margin: 10px 0;
}
button {
background: #07c160;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #06ad56;
}
.info {
background: #e6f7ed;
padding: 15px;
border-radius: 4px;
margin: 20px 0;
border-left: 4px solid #07c160;
}
</style>
</head>
<body>
<h1>🎨 TabBar 图标生成器</h1>
<div class="info">
<strong>使用说明:</strong><br>
1. 点击每个图标下方的"下载"按钮<br>
2. 将下载的 PNG 文件重命名并保存到 <code>miniprogram/images/tabbar/</code> 目录<br>
3. 在 app.json 中配置图标路径
</div>
<div class="icon-grid">
<div class="icon-card">
<h3>🏠 首页 (未选中)</h3>
<canvas id="home" width="81" height="81"></canvas>
<br>
<button onclick="downloadIcon('home', 'home.png')">下载 home.png</button>
</div>
<div class="icon-card">
<h3>🏠 首页 (选中)</h3>
<canvas id="home-active" width="81" height="81"></canvas>
<br>
<button onclick="downloadIcon('home-active', 'home-active.png')">下载 home-active.png</button>
</div>
<div class="icon-card">
<h3>📄 我的发布 (未选中)</h3>
<canvas id="article" width="81" height="81"></canvas>
<br>
<button onclick="downloadIcon('article', 'article.png')">下载 article.png</button>
</div>
<div class="icon-card">
<h3>📄 我的发布 (选中)</h3>
<canvas id="article-active" width="81" height="81"></canvas>
<br>
<button onclick="downloadIcon('article-active', 'article-active.png')">下载 article-active.png</button>
</div>
<div class="icon-card">
<h3>👤 我的 (未选中)</h3>
<canvas id="user" width="81" height="81"></canvas>
<br>
<button onclick="downloadIcon('user', 'user.png')">下载 user.png</button>
</div>
<div class="icon-card">
<h3>👤 我的 (选中)</h3>
<canvas id="user-active" width="81" height="81"></canvas>
<br>
<button onclick="downloadIcon('user-active', 'user-active.png')">下载 user-active.png</button>
</div>
</div>
<script>
// 绘制房子图标
function drawHome(ctx, color) {
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// 房顶
ctx.beginPath();
ctx.moveTo(20, 40);
ctx.lineTo(40, 25);
ctx.lineTo(60, 40);
ctx.stroke();
// 房子主体
ctx.strokeRect(25, 38, 30, 25);
// 门
ctx.fillRect(35, 48, 10, 15);
}
// 绘制文档图标
function drawDocument(ctx, color) {
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// 文档外框
ctx.beginPath();
ctx.moveTo(28, 22);
ctx.lineTo(28, 59);
ctx.lineTo(52, 59);
ctx.lineTo(52, 32);
ctx.lineTo(42, 22);
ctx.closePath();
ctx.stroke();
// 折角
ctx.beginPath();
ctx.moveTo(42, 22);
ctx.lineTo(42, 32);
ctx.lineTo(52, 32);
ctx.stroke();
// 横线
ctx.beginPath();
ctx.moveTo(33, 40);
ctx.lineTo(47, 40);
ctx.moveTo(33, 47);
ctx.lineTo(47, 47);
ctx.stroke();
}
// 绘制用户图标
function drawUser(ctx, color) {
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// 头部
ctx.beginPath();
ctx.arc(40, 32, 8, 0, Math.PI * 2);
ctx.stroke();
// 身体
ctx.beginPath();
ctx.arc(40, 58, 15, Math.PI, 0, true);
ctx.stroke();
}
// 初始化所有图标
function initIcons() {
// 首页图标
drawHome(document.getElementById('home').getContext('2d'), '#999999');
drawHome(document.getElementById('home-active').getContext('2d'), '#07c160');
// 文档图标
drawDocument(document.getElementById('article').getContext('2d'), '#999999');
drawDocument(document.getElementById('article-active').getContext('2d'), '#07c160');
// 用户图标
drawUser(document.getElementById('user').getContext('2d'), '#999999');
drawUser(document.getElementById('user-active').getContext('2d'), '#07c160');
}
// 下载图标
function downloadIcon(canvasId, filename) {
const canvas = document.getElementById(canvasId);
const url = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = filename;
link.href = url;
link.click();
}
// 页面加载完成后初始化
window.onload = initIcons;
</script>
</body>
</html>

View File

@@ -0,0 +1,56 @@
{
"pages": [
"pages/home/home",
"pages/article-generate/article-generate",
"pages/login/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",
"pages/profile/article-detail/article-detail",
"pages/profile/about/about",
"pages/profile/feedback/feedback",
"pages/agreement/user-agreement/user-agreement",
"pages/agreement/privacy-policy/privacy-policy",
"pages/index/index",
"pages/logs/logs"
],
"window": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "万花筒AI助手",
"navigationBarBackgroundColor": "#07c160",
"backgroundColor": "#f5f5f5"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#07c160",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "images/tabbar/home.png",
"selectedIconPath": "images/tabbar/home-active.png"
},
{
"pagePath": "pages/profile/published/published",
"text": "我的发布",
"iconPath": "images/tabbar/article.png",
"selectedIconPath": "images/tabbar/article-active.png"
},
{
"pagePath": "pages/profile/profile",
"text": "我的",
"iconPath": "images/tabbar/user.png",
"selectedIconPath": "images/tabbar/user-active.png"
}
]
},
"style": "v2",
"lazyCodeLoading": "requiredComponents"
}

View File

@@ -0,0 +1,73 @@
// app.ts
import { API } from './config/api';
interface IAppInstance {
globalData: Record<string, any>;
showEnvironmentTip: () => void;
}
App<IAppOption & IAppInstance>({
globalData: {},
onLaunch() {
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 显示当前环境提示
this.showEnvironmentTip();
// 登录
wx.login({
success: res => {
console.log(res.code)
// 发送 res.code 到后台换取 openId, sessionKey, unionId
},
})
},
// 显示环境提示
showEnvironmentTip() {
try {
const accountInfo = wx.getAccountInfoSync();
const envVersion = accountInfo.miniProgram.envVersion;
let envName = '';
let envColor = '#07c160';
switch (envVersion) {
case 'develop':
envName = '开发环境';
envColor = '#52c41a';
break;
case 'trial':
envName = '体验环境';
envColor = '#faad14';
break;
case 'release':
envName = '生产环境';
envColor = '#1677ff';
break;
default:
envName = '未知环境';
}
// 输出到控制台
console.log(`[App] 当前环境: ${envName}`);
console.log(`[App] 后端地址: ${API.baseURL}`);
// 显示弹窗提示 (仅开发和体验环境)
if (envVersion === 'develop' || envVersion === 'trial') {
wx.showModal({
title: '环境提示',
content: `当前运行在${envName}\n后端地址: ${API.baseURL}`,
showCancel: false,
confirmText: '知道了',
confirmColor: envColor
});
}
} catch (error) {
console.error('[App] 获取环境信息失败:', error);
}
}
})

View File

@@ -0,0 +1,14 @@
/**app.wxss**/
/* 全局样式 */
page {
width: 100%;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 按钮样式重置 */
button::after {
border: none;
}

View File

@@ -0,0 +1,142 @@
// API配置文件
// 统一管理后端接口地址
/**
* 环境类型
* dev: 开发环境(本地)
* test: 测试环境(服务器测试)
* prod: 生产环境
*/
type EnvType = 'dev' | 'test' | 'prod';
/**
* 环境配置接口
*/
interface EnvConfig {
baseURL: string; // 主服务地址
pythonURL?: string; // Python服务地址(可选)
timeout: number; // 请求超时时间
}
/**
* 多环境配置
* 修改这里的配置即可切换不同环境的后端地址
*/
const API_CONFIG: Record<EnvType, EnvConfig> = {
// 开发环境 - 本地开发
dev: {
baseURL: 'http://localhost:8080', // 本地Go服务
pythonURL: 'http://localhost:8000', // 本地Python服务
timeout: 90000
},
// 测试环境 - 服务器测试
test: {
baseURL: 'https://lehang.tech', // 测试服务器Go服务
pythonURL: 'https://lehang.tech', // 测试服务器Python服务
timeout: 90000
},
// 生产环境
prod: {
baseURL: 'https://lehang.tech', // 生产环境Go服务
pythonURL: 'https://lehang.tech', // 生产环境Python服务
timeout: 90000
}
};
/**
* 自动检测环境
* 根据小程序的运行环境自动判断:
* - 开发版(develop) → dev
* - 体验版(trial) → test
* - 正式版(release) → prod
*/
function detectEnvironment(): EnvType {
try {
const accountInfo = wx.getAccountInfoSync();
const envVersion = accountInfo.miniProgram.envVersion;
switch (envVersion) {
case 'develop':
return 'dev'; // 开发版
case 'trial':
return 'test'; // 体验版
case 'release':
return 'prod'; // 正式版
default:
return 'dev';
}
} catch (error) {
console.warn('无法检测环境,使用默认开发环境', error);
return 'dev';
}
}
// 当前环境(自动检测)
const currentEnv: EnvType = detectEnvironment();
const currentConfig = (): EnvConfig => API_CONFIG[currentEnv];
// 输出环境信息
console.log(`[API Config] 当前环境: ${currentEnv}`);
console.log(`[API Config] 主服务: ${currentConfig().baseURL}`);
if (currentConfig().pythonURL) {
console.log(`[API Config] Python服务: ${currentConfig().pythonURL}`);
}
// API端点
export const API = {
// 基础配置(动态获取)
get baseURL(): string {
return currentConfig().baseURL;
},
get pythonURL(): string | undefined {
return currentConfig().pythonURL;
},
get timeout(): number {
return currentConfig().timeout;
},
// 登录接口
auth: {
wechatLogin: '/api/login/wechat', // 微信登录
phoneLogin: '/api/login/phone' // 手机号登录
},
// 员工端接口
employee: {
profile: '/api/employee/profile', // 获取个人信息
bindXHS: '/api/employee/bind-xhs', // 绑定小红书
unbindXHS: '/api/employee/unbind-xhs', // 解绑小红书
availableCopies: '/api/employee/available-copies', // 获取可领取文案
claimCopy: '/api/employee/claim-copy', // 领取文案
claimRandomCopy: '/api/employee/claim-random-copy', // 随机领取文案
publish: '/api/employee/publish', // 发布内容
myPublishRecords: '/api/employee/my-publish-records' // 我的发布记录
},
// 公开接口(不需要登录)
public: {
products: '/api/products' // 获取产品列表
},
// 小红书相关接口
xhs: {
sendCode: '/api/xhs/send-code' // 发送小红书验证码
}
};
// 构建完整URL
export function buildURL(path: string, usePython = false): string {
const baseURL = usePython ? (API.pythonURL || API.baseURL) : API.baseURL;
return `${baseURL}${path}`;
}
// 获取请求头
export function getHeaders(): Record<string, string> {
const token = wx.getStorageSync('token') || '';
return {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
};
}

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"t-tab-bar": "tdesign-miniprogram/tab-bar/tab-bar",
"t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item"
}
}

View File

@@ -0,0 +1 @@
/* custom-tab-bar/index.wxss */

View File

@@ -0,0 +1,111 @@
# TabBar 图标添加指南
## 📋 当前状态
- TabBar 已配置完成,只显示文字,暂无图标
- 需要手动添加 PNG/JPG 格式的图标文件
## 🎨 图标要求
- **格式**PNG推荐支持透明背景或 JPG
- **尺寸**81x81 px推荐或 40x40 px最小
- **背景**透明PNG格式
- **颜色**
- 未选中:灰色 (#999999)
- 选中:绿色 (#07c160)
## 📁 文件命名和位置
创建 `images/tabbar/` 目录,添加以下 6 个图标文件:
```
miniprogram/
└── miniprogram/
└── images/
└── tabbar/
├── home.png # 首页 - 未选中
├── home-active.png # 首页 - 选中
├── article.png # 我的发布 - 未选中
├── article-active.png # 我的发布 - 选中
├── user.png # 我的 - 未选中
└── user-active.png # 我的 - 选中
```
## 🔧 配置步骤
### 1. 准备图标文件
可以使用以下方式获取图标:
#### 方案1使用 Iconfont推荐
1. 访问 https://www.iconfont.cn/
2. 搜索以下图标:
- 首页:搜索 "home" 或 "房子"
- 文章:搜索 "file" 或 "文档"
- 用户:搜索 "user" 或 "用户"
3. 下载 PNG 格式81x81 px
4. 使用在线工具修改颜色:
- https://www.peko-step.com/zh/tool/pngcolor.html
- 未选中版本改为灰色 #999999
- 选中版本改为绿色 #07c160
#### 方案2使用 Icons8
1. 访问 https://icons8.com/icons
2. 搜索图标并下载
3. 选择 PNG 格式81x81 px
4. 选择颜色(灰色/绿色)
#### 方案3使用 Flaticon
1. 访问 https://www.flaticon.com/
2. 搜索并下载图标
3. 编辑颜色
### 2. 修改 app.json 配置
`app.json``tabBar.list` 中添加图标路径:
```json
{
"tabBar": {
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "images/tabbar/home.png",
"selectedIconPath": "images/tabbar/home-active.png"
},
{
"pagePath": "pages/profile/published/published",
"text": "我的发布",
"iconPath": "images/tabbar/article.png",
"selectedIconPath": "images/tabbar/article-active.png"
},
{
"pagePath": "pages/profile/profile",
"text": "我的",
"iconPath": "images/tabbar/user.png",
"selectedIconPath": "images/tabbar/user-active.png"
}
]
}
}
```
### 3. 重新编译小程序
保存配置后,重新编译即可看到图标
## 🎯 快速方案:使用简单色块图标
如果暂时找不到合适的图标,可以使用纯色方块作为临时图标:
1. 使用 Photoshop、Sketch 或在线工具创建 81x81 px 的图片
2. 首页:蓝色方块
3. 我的发布:橙色方块
4. 我的:绿色方块
5. 选中状态:同色但更亮或加边框
## ⚠️ 注意事项
- 图标文件大小建议 < 40KB
- 确保图片路径正确相对于 miniprogram 目录
- PNG 格式推荐使用透明背景
- 图标设计要简洁清晰避免过于复杂的图案
- 选中和未选中状态要有明显区别
## 📞 需要帮助?
如果需要我帮你生成简单的图标文件请告诉我我可以提供 base64 编码或其他方案

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 24 24" fill="none" stroke="#07c160" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 24 24" fill="none" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,40 @@
# TabBar 图标说明
由于文件系统限制,无法直接创建二进制图标文件。
## 方案:使用 iconfont 或在线图标
建议使用以下方式添加 TabBar 图标:
### 方案1使用阿里 iconfont
1. 访问 https://www.iconfont.cn/
2. 搜索并下载以下图标81x81 px
- 首页图标home
- 文章图标article/document
- 用户图标user/profile
3. 每个图标需要两个版本:
- 普通状态(灰色 #999999
- 选中状态(绿色 #07c160
### 方案2临时方案 - 使用 emoji 或纯文字
在 app.json 的 tabBar 配置中暂时不设置 iconPath只显示文字
### 方案3使用在线图标生成工具
可以使用以下工具快速生成图标:
- https://icon-icons.com/
- https://www.flaticon.com/
- https://icons8.com/
## 图标要求
- 尺寸81x81 px推荐或 40x40 px最小
- 格式PNG
- 背景:透明
- 命名规范:
```
images/tab-home.png
images/tab-home-active.png
images/tab-article.png
images/tab-article-active.png
images/tab-user.png
images/tab-user-active.png
```

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 24 24" fill="none" stroke="#07c160" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 24 24" fill="none" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 24 24" fill="none" stroke="#07c160" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 24 24" fill="none" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "隐私政策",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,10 @@
// pages/agreement/privacy-policy/privacy-policy.ts
Page({
data: {
},
onLoad() {
}
});

View File

@@ -0,0 +1,92 @@
<!--pages/agreement/privacy-policy/privacy-policy.wxml-->
<view class="agreement-container">
<scroll-view class="content-scroll" scroll-y>
<view class="agreement-title">隐私政策</view>
<view class="update-time">更新时间2024年12月5日</view>
<view class="section">
<view class="section-title">一、信息收集</view>
<view class="section-content">
我们会收集您在使用服务时主动提供或因使用服务而产生的信息,包括但不限于:\n
1. 微信授权信息(昵称、头像等)\n
2. 设备信息\n
3. 日志信息\n
4. 位置信息(如您授权)
</view>
</view>
<view class="section">
<view class="section-title">二、信息使用</view>
<view class="section-content">
我们收集和使用您的个人信息用于:\n
1. 为您提供、维护和改进我们的服务\n
2. 与您沟通,包括发送通知和更新\n
3. 保护服务的安全性和完整性\n
4. 遵守法律法规要求
</view>
</view>
<view class="section">
<view class="section-title">三、信息共享</view>
<view class="section-content">
我们不会与第三方共享您的个人信息,除非:\n
1. 获得您的明确同意\n
2. 法律法规要求\n
3. 与我们的服务提供商共享(仅限于提供服务所必需)\n
4. 保护我们或他人的合法权益
</view>
</view>
<view class="section">
<view class="section-title">四、信息安全</view>
<view class="section-content">
我们采取各种安全措施来保护您的个人信息,包括:\n
1. 使用加密技术保护数据传输\n
2. 限制员工访问个人信息\n
3. 定期审查信息收集、存储和处理实践\n
4. 采取物理和技术措施防止未经授权的访问
</view>
</view>
<view class="section">
<view class="section-title">五、您的权利</view>
<view class="section-content">
您对自己的个人信息享有以下权利:\n
1. 访问和更新您的个人信息\n
2. 删除您的个人信息\n
3. 撤回授权同意\n
4. 注销账号
</view>
</view>
<view class="section">
<view class="section-title">六、Cookie使用</view>
<view class="section-content">
我们可能使用Cookie和类似技术来改善用户体验。您可以通过浏览器设置拒绝Cookie但这可能影响某些功能的使用。
</view>
</view>
<view class="section">
<view class="section-title">七、未成年人保护</view>
<view class="section-content">
我们重视未成年人的个人信息保护。如果您是未成年人,请在监护人的陪同下阅读本政策,并在监护人同意后使用我们的服务。
</view>
</view>
<view class="section">
<view class="section-title">八、政策更新</view>
<view class="section-content">
我们可能会不时更新本隐私政策。更新后的政策将在平台上公布,请您定期查看。
</view>
</view>
<view class="section">
<view class="section-title">九、联系我们</view>
<view class="section-content">
如您对本隐私政策有任何疑问,请通过以下方式联系我们:\n
客服邮箱privacy@example.com\n
客服电话400-888-8888
</view>
</view>
</scroll-view>
</view>

View File

@@ -0,0 +1,53 @@
/* pages/agreement/privacy-policy/privacy-policy.wxss */
page {
background: #FFFFFF;
height: 100%;
}
.agreement-container {
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
background: #FFFFFF;
}
.content-scroll {
flex: 1;
width: 100%;
padding: 30rpx 40rpx 60rpx;
box-sizing: border-box;
}
.agreement-title {
font-size: 44rpx;
font-weight: bold;
color: #1a1a1a;
text-align: center;
margin-bottom: 16rpx;
}
.update-time {
font-size: 24rpx;
color: #999;
text-align: center;
margin-bottom: 40rpx;
}
.section {
margin-bottom: 40rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.section-content {
font-size: 28rpx;
color: #666;
line-height: 1.8;
white-space: pre-line;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "用户协议",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,10 @@
// pages/agreement/user-agreement/user-agreement.ts
Page({
data: {
},
onLoad() {
}
});

View File

@@ -0,0 +1,71 @@
<!--pages/agreement/user-agreement/user-agreement.wxml-->
<view class="agreement-container">
<scroll-view class="content-scroll" scroll-y>
<view class="agreement-title">用户协议</view>
<view class="update-time">更新时间2024年12月5日</view>
<view class="section">
<view class="section-title">一、协议的接受</view>
<view class="section-content">
欢迎使用AI文章审核平台。本协议是您与本平台之间关于使用本平台服务所订立的协议。请您仔细阅读本协议您使用本平台服务即表示您已阅读并同意本协议的全部内容。
</view>
</view>
<view class="section">
<view class="section-title">二、服务说明</view>
<view class="section-content">
本平台为用户提供AI文章生成、审核和管理服务。我们致力于为用户提供高效、便捷的内容管理工具。
</view>
</view>
<view class="section">
<view class="section-title">三、用户账号</view>
<view class="section-content">
1. 您需要通过微信授权登录使用本平台服务。\n
2. 您应妥善保管您的账号信息,对账号下的所有活动负责。\n
3. 如发现账号被盗用,请及时联系我们。
</view>
</view>
<view class="section">
<view class="section-title">四、用户行为规范</view>
<view class="section-content">
您在使用本平台服务时应遵守相关法律法规,不得利用本平台从事违法违规活动,包括但不限于:\n
1. 发布违法违规内容\n
2. 侵犯他人知识产权\n
3. 传播虚假信息\n
4. 其他危害平台安全的行为
</view>
</view>
<view class="section">
<view class="section-title">五、知识产权</view>
<view class="section-content">
本平台的所有内容,包括但不限于文字、图片、软件、程序等,均受著作权法等法律法规保护。未经授权,不得擅自使用。
</view>
</view>
<view class="section">
<view class="section-title">六、免责声明</view>
<view class="section-content">
本平台对因不可抗力或不可归责于本平台的原因导致的服务中断或其他缺陷不承担责任。用户理解并同意自行承担使用本平台服务的风险。
</view>
</view>
<view class="section">
<view class="section-title">七、协议修改</view>
<view class="section-content">
我们有权根据需要修改本协议,修改后的协议将在平台上公布。您继续使用本平台服务即表示同意修改后的协议。
</view>
</view>
<view class="section">
<view class="section-title">八、联系我们</view>
<view class="section-content">
如您对本协议有任何疑问,请通过以下方式联系我们:\n
客服邮箱support@example.com\n
客服电话400-888-8888
</view>
</view>
</scroll-view>
</view>

View File

@@ -0,0 +1,53 @@
/* pages/agreement/user-agreement/user-agreement.wxss */
page {
background: #FFFFFF;
height: 100%;
}
.agreement-container {
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
background: #FFFFFF;
}
.content-scroll {
flex: 1;
width: 100%;
padding: 30rpx 40rpx 60rpx;
box-sizing: border-box;
}
.agreement-title {
font-size: 44rpx;
font-weight: bold;
color: #1a1a1a;
text-align: center;
margin-bottom: 16rpx;
}
.update-time {
font-size: 24rpx;
color: #999;
text-align: center;
margin-bottom: 40rpx;
}
.section {
margin-bottom: 40rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.section-content {
font-size: 28rpx;
color: #666;
line-height: 1.8;
white-space: pre-line;
}

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "文章详情",
"usingComponents": {}
}

View File

@@ -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: '' // 可以设置文章封面图
};
}
});

View 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>

View 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;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "AI生成种草文章",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,231 @@
// 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);
}
} catch (error) {
console.error('发布失败:', error);
}
} 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);
}
}
}
});
}
});

View File

@@ -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>

View File

@@ -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: 8rpx;
right: 8rpx;
width: 40rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delete-text {
font-size: 36rpx;
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;
}

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "首页",
"usingComponents": {}
}

View File

@@ -0,0 +1,291 @@
// pages/articles/articles.ts
import { formatDate, getStatusInfo, getChannelInfo, getCoverColor, getCoverIcon } from '../../utils/util'
import { EmployeeService } from '../../services/employee'
Page({
data: {
productId: 0, // 产品ID
productName: '', // 产品名称
productImage: '', // 产品图片
allCopies: [] as any[], // 所有文案
currentIndex: 0, // 当前显示的文案索引
currentCopy: null as any, // 当前显示的文案
loading: false,
claiming: false, // 领取中
publishing: false // 发布中
},
onLoad(options: any) {
// 检查登录状态和Token
const token = wx.getStorageSync('token');
if (!token) {
wx.showModal({
title: '未登录',
content: '请先登录后再查看文案',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
wx.redirectTo({
url: '/pages/login/login'
});
} else {
wx.navigateBack();
}
}
});
return;
}
// 检查小红书绑定状态
this.checkXHSBinding().then(isBound => {
if (!isBound) {
wx.showModal({
title: '未绑定小红书',
content: '查看文案前需要先绑定小红书账号',
confirmText: '去绑定',
success: (res) => {
if (res.confirm) {
wx.redirectTo({
url: '/pages/profile/social-binding/social-binding'
});
} else {
wx.navigateBack();
}
}
});
return;
}
// 获取产品ID参数
if (options.productId) {
this.setData({
productId: parseInt(options.productId)
});
}
this.loadCopies();
});
},
// 检查小红书绑定状态
async checkXHSBinding(): Promise<boolean> {
try {
const response = await EmployeeService.getProfile();
if (response.code === 200 && response.data) {
return response.data.is_bound_xhs === 1;
}
return false;
} catch (error) {
console.error('检查绑定状态失败:', error);
return false;
}
},
onShow() {
// 每次显示页面时刷新数据
if (this.data.productId) {
this.loadCopies();
}
},
// 加载文案列表
async loadCopies() {
const productId = this.data.productId;
if (!productId) {
wx.showToast({
title: '请先选择产品',
icon: 'none'
});
return;
}
this.setData({ loading: true });
try {
// 从后端API获取可领取文案
const response = await EmployeeService.getAvailableCopies(productId);
console.log('===== 文案列表响应 =====');
console.log('response:', response);
if (response.code === 200 && response.data) {
const copies = response.data.copies || [];
const product = response.data.product || {};
console.log('文案数量:', copies.length);
if (copies.length > 0) {
console.log('第一篇文案:', copies[0]);
console.log('图片数量:', (copies[0].images && copies[0].images.length) || 0);
console.log('标签数量:', (copies[0].tags && copies[0].tags.length) || 0);
}
this.setData({
allCopies: copies,
productName: product.name || '',
productImage: product.image || '',
currentIndex: 0,
currentCopy: copies.length > 0 ? copies[0] : null,
loading: false
});
console.log('setData后的 currentCopy:', this.data.currentCopy);
if (copies.length === 0) {
wx.showToast({
title: '暂无可领取文案',
icon: 'none'
});
}
}
} catch (error) {
console.error('加载文案失败:', error);
this.setData({ loading: false });
wx.showToast({
title: '加载文案失败',
icon: 'none'
});
}
},
// 换一个文案
changeArticle() {
if (this.data.claiming || this.data.publishing) return;
const { allCopies, currentIndex } = this.data;
if (allCopies.length === 0) {
wx.showToast({
title: '暂无更多文案',
icon: 'none'
});
return;
}
// 切换到下一个文案
const nextIndex = (currentIndex + 1) % allCopies.length;
this.setData({
currentIndex: nextIndex,
currentCopy: allCopies[nextIndex]
});
wx.showToast({
title: `${nextIndex + 1}/${allCopies.length}`,
icon: 'none',
duration: 1000
});
},
// 一键发布(先领取,再发布)
async publishArticle() {
if (!this.data.currentCopy) {
wx.showToast({
title: '请先选择文案',
icon: 'none'
});
return;
}
wx.showModal({
title: '确认发布',
content: '确定要领取并发布这篇文案吗?',
confirmText: '发布',
confirmColor: '#07c160',
success: async (res) => {
if (res.confirm) {
await this.claimAndPublish();
}
}
});
},
// 领取并发布
async claimAndPublish() {
this.setData({ claiming: true });
try {
// 1. 先领取文案
const claimResponse = await EmployeeService.claimCopy(
this.data.currentCopy.id,
this.data.productId
);
if (claimResponse.code !== 200) {
throw new Error(claimResponse.message || '领取失败');
}
const claimId = (claimResponse.data && claimResponse.data.claim_id) || null;
const copyData = (claimResponse.data && claimResponse.data.copy) || null;
this.setData({ claiming: false, publishing: true });
// 2. 再发布
const publishResponse = await EmployeeService.publish({
copy_id: this.data.currentCopy.id,
title: (copyData && copyData.title) || this.data.currentCopy.title,
content: (copyData && copyData.content) || this.data.currentCopy.content,
publish_link: '',
xhs_note_id: ''
});
if (publishResponse.code === 200) {
wx.showToast({
title: '发布成功',
icon: 'success',
duration: 2000
});
this.setData({ publishing: false });
// 2秒后返回
setTimeout(() => {
wx.navigateBack();
}, 2000);
} else {
throw new Error(publishResponse.message || '发布失败');
}
} catch (error: any) {
console.error('发布失败:', error);
this.setData({
claiming: false,
publishing: false
});
wx.showToast({
title: error.message || '操作失败',
icon: 'none'
});
}
},
// 返回上一页
goBack() {
wx.navigateBack();
},
goToProfile() {
wx.redirectTo({
url: '/pages/profile/profile'
});
},
// 分享功能
onShareAppMessage() {
const currentCopy = this.data.currentCopy;
return {
title: currentCopy ? currentCopy.title : '精彩种草文案',
path: `/pages/articles/articles?productId=${this.data.productId}`,
imageUrl: this.data.productImage || '' // 使用产品图片作为分享封面
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: `${this.data.productName} - 精彩种草文案`,
imageUrl: this.data.productImage || ''
};
}
});

View File

@@ -0,0 +1,71 @@
<!--pages/articles/articles.wxml-->
<view class="page-container">
<!-- 文案内容 -->
<scroll-view class="article-container" scroll-y enable-flex wx:if="{{currentCopy}}">
<view class="article-wrapper">
<!-- 文章图片 -->
<view class="article-images" wx:if="{{currentCopy.images && currentCopy.images.length > 0}}">
<view class="image-item" wx:for="{{currentCopy.images}}" wx:key="id">
<image class="article-image" src="{{item.image_url || item}}" mode="aspectFill" />
</view>
</view>
<!-- 文章标签 -->
<view class="article-tags" wx:if="{{currentCopy.tags && currentCopy.tags.length > 0}}">
<view class="tag-item" wx:for="{{currentCopy.tags}}" wx:key="index">
<text>{{item}}</text>
</view>
</view>
<view class="article-header">
<text class="article-title">{{currentCopy.title}}</text>
</view>
<view class="article-content">
<text class="content-text">{{currentCopy.content}}</text>
</view>
<view class="article-meta">
<view class="meta-item">
<text class="meta-label">主题:</text>
<text class="meta-value">{{currentCopy.topic}}</text>
</view>
<view class="meta-item">
<text class="meta-label">字数:</text>
<text class="meta-value">{{currentCopy.content.length}}字</text>
</view>
</view>
</view>
</scroll-view>
<!-- 空状态 -->
<view class="empty-container" wx:if="{{!currentCopy && !loading}}">
<text class="empty-icon">📝</text>
<text class="empty-text">暂无可领取文案</text>
<button class="empty-btn" bindtap="goBack">返回首页</button>
</view>
<!-- 加载状态 -->
<view class="loading-container" wx:if="{{loading}}">
<text class="loading-text">加载中...</text>
</view>
<!-- 底部操作栏 -->
<view class="action-bar" wx:if="{{currentCopy}}">
<button
class="action-btn secondary"
bindtap="changeArticle"
disabled="{{claiming || publishing}}"
>
<text class="btn-text">换一换</text>
</button>
<button
class="action-btn primary"
bindtap="publishArticle"
disabled="{{claiming || publishing}}"
loading="{{claiming || publishing}}"
>
<text class="btn-text">{{claiming ? '领取中...' : (publishing ? '发布中...' : '一键发布')}}</text>
</button>
</view>
</view>

View File

@@ -0,0 +1,245 @@
/* pages/articles/articles.wxss */
page {
background: #f8f8f8;
height: 100%;
}
.page-container {
height: 100vh;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding-bottom: calc(136rpx + env(safe-area-inset-bottom));
}
/* 文案容器 */
.article-container {
flex: 1;
background: white;
overflow-y: auto;
overflow-x: hidden;
}
.article-wrapper {
padding: 30rpx;
}
/* 文章图片 - 3列网格布局 */
.article-images {
margin-bottom: 24rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
box-sizing: border-box;
width: 100%;
}
.image-item {
width: 100%;
padding-bottom: 100%; /* 1:1 比例 */
position: relative;
border-radius: 8rpx;
overflow: hidden;
background: #f5f5f5;
}
.article-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* 标签样式 */
.article-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
padding-bottom: 20rpx;
margin-bottom: 24rpx;
}
.tag-item {
padding: 8rpx 20rpx;
background: linear-gradient(135deg, #e6f7ed 0%, #d0f0de 100%);
border-radius: 20rpx;
font-size: 24rpx;
color: #07c160;
font-weight: 500;
}
.article-header {
margin-bottom: 24rpx;
padding-bottom: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.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;
}
.article-meta {
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.meta-item {
display: flex;
align-items: center;
font-size: 26rpx;
}
.meta-label {
color: #999;
min-width: 80rpx;
}
.meta-value {
color: #333;
font-weight: 500;
}
/* 空状态 */
.empty-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 24rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-bottom: 40rpx;
}
.empty-btn {
padding: 20rpx 60rpx;
background: #07c160;
color: white;
border-radius: 50rpx;
font-size: 28rpx;
border: none;
}
.empty-btn::after {
border: none;
}
/* 加载状态 */
.loading-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
/* 操作栏 */
.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 {
height: 80rpx;
border: none;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
transition: all 0.3s;
}
.action-btn::after {
border: none;
}
.action-btn.secondary {
flex: 0 0 180rpx;
background: #f5f5f5;
color: #333;
}
.action-btn.secondary:active {
background: #e8e8e8;
}
.action-btn.primary {
flex: 1;
background: #07c160;
color: white;
}
.action-btn.primary:active {
opacity: 0.9;
}
.action-btn[disabled] {
opacity: 0.6;
}
.btn-icon {
width: 36rpx;
height: 36rpx;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
/* 刷新图标 */
.icon-refresh {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23ff2442"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>');
}
/* 确认图标 */
.icon-check {
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="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>');
}
.btn-text {
font-size: 28rpx;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "商品选择",
"navigationBarBackgroundColor": "#07c160",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,312 @@
// pages/home/home.ts
import { EmployeeService, Product as ApiProduct } from '../../services/employee';
interface Product {
id: number;
name: string;
price: number;
sales: number;
image: string;
category: string;
tags: string[];
hotLevel: number; // 1-5 热度等级
description?: string;
available_copies?: number;
}
interface Category {
id: string;
name: string;
}
Page({
data: {
selectedProduct: 0 as number, // 选中的商品ID
selectedProductName: '' as string, // 选中的商品名称
products: [] as Product[],
hasMore: true,
loading: false,
page: 1,
pageSize: 6
},
onLoad() {
// 不在onLoad检查登录允许用户浏览首页
this.loadProducts();
},
// 加载商品列表
async loadProducts() {
if (this.data.loading || !this.data.hasMore) return;
this.setData({ loading: true });
try {
// 从后端API获取产品列表公开接口不需要登录
const response = await EmployeeService.getProducts();
if (response.code === 200 && response.data) {
const apiProducts = response.data.list.map((product: ApiProduct, index: number) => ({
id: product.id,
name: product.name,
price: 0, // 后端暂无价格字段
sales: product.available_copies || 0,
image: product.image || `https://picsum.photos/id/${237 + index}/300/400`,
category: 'beauty', // 后端暂无分类字段
tags: ['种草', '推荐'],
hotLevel: product.available_copies > 5 ? 5 : 3,
description: product.description,
available_copies: product.available_copies
}));
this.setData({
products: apiProducts,
loading: false,
hasMore: false
});
return;
}
} catch (error) {
console.error('加载产品失败:', error);
// API失败使用模拟数据
}
// 如果API失败使用模拟数据
const allMockProducts = [
{
id: 1,
name: '兰蔻小黑瓶精华液',
price: 680,
sales: 10234,
image: 'https://picsum.photos/id/237/300/400',
category: 'beauty',
tags: ['美白', '抗老', '保湿'],
hotLevel: 5
},
{
id: 2,
name: 'SK-II神仙水',
price: 1299,
sales: 8765,
image: 'https://picsum.photos/id/152/300/400',
category: 'beauty',
tags: ['补水', '缩毛孔', '提亮'],
hotLevel: 5
},
{
id: 3,
name: '雅诗兰黛小棕瓶',
price: 790,
sales: 9432,
image: 'https://picsum.photos/id/292/300/400',
category: 'beauty',
tags: ['修复', '抗氧化', '紧致'],
hotLevel: 4
},
{
id: 4,
name: 'Dior烈艳蓝金口红',
price: 320,
sales: 15678,
image: 'https://picsum.photos/id/365/300/400',
category: 'beauty',
tags: ['段色', '滑顺', '持久'],
hotLevel: 5
},
{
id: 5,
name: 'AirPods Pro 2',
price: 1899,
sales: 23456,
image: 'https://picsum.photos/id/180/300/400',
category: 'digital',
tags: ['降噪', '音质', '舒适'],
hotLevel: 5
},
{
id: 6,
name: 'iPhone 15 Pro',
price: 7999,
sales: 34567,
image: 'https://picsum.photos/id/119/300/400',
category: 'digital',
tags: ['性能', '拍照', '长续航'],
hotLevel: 5
},
{
id: 7,
name: '海蓝之谜精华面霜',
price: 890,
sales: 7654,
image: 'https://picsum.photos/id/225/300/400',
category: 'beauty',
tags: ['保湿', '修复', '舒缓'],
hotLevel: 4
},
{
id: 8,
name: '迪奥烈影精华',
price: 1680,
sales: 5432,
image: 'https://picsum.photos/id/177/300/400',
category: 'beauty',
tags: ['抗衰老', '紧致', '提亮'],
hotLevel: 5
},
{
id: 9,
name: 'Zara复古风衣',
price: 299,
sales: 12345,
image: 'https://picsum.photos/id/111/300/400',
category: 'fashion',
tags: ['复古', '百搭', '时尚'],
hotLevel: 4
},
{
id: 10,
name: 'Uniqlo羊绒衫',
price: 199,
sales: 23456,
image: 'https://picsum.photos/id/222/300/400',
category: 'fashion',
tags: ['保暖', '舒适', '百搭'],
hotLevel: 5
},
{
id: 11,
name: '星巴克咖啡豆',
price: 88,
sales: 34567,
image: 'https://picsum.photos/id/431/300/400',
category: 'food',
tags: ['香浓', '精品', '中烘'],
hotLevel: 5
},
{
id: 12,
name: '三只松鼠零食礼盒',
price: 158,
sales: 19876,
image: 'https://picsum.photos/id/326/300/400',
category: 'food',
tags: ['零食', '礼盒', '美味'],
hotLevel: 4
}
];
// 模拟分页加载
setTimeout(() => {
const start = (this.data.page - 1) * this.data.pageSize;
const end = start + this.data.pageSize;
const newProducts = allMockProducts.slice(start, end);
this.setData({
products: [...this.data.products, ...newProducts],
hasMore: end < allMockProducts.length,
loading: false,
page: this.data.page + 1
});
}, 800);
},
// 切换商品选中状态
toggleProduct(e: any) {
const productId = e.currentTarget.dataset.id;
const productName = e.currentTarget.dataset.name;
this.setData({
selectedProduct: productId === this.data.selectedProduct ? 0 : productId,
selectedProductName: productId === this.data.selectedProduct ? '' : productName
});
},
// 去生成内容
async goToGenerate() {
if (!this.data.selectedProduct) {
wx.showToast({
title: '请先选择商品',
icon: 'none'
});
return;
}
// 1. 检查登录状态
const token = wx.getStorageSync('token');
if (!token) {
wx.showModal({
title: '未登录',
content: '请先登录后再查看文案',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
wx.redirectTo({
url: '/pages/login/login'
});
}
}
});
return;
}
// 2. 检查小红书绑定状态
try {
const response = await EmployeeService.getProfile();
if (response.code === 200 && response.data) {
const isBoundXHS = response.data.is_bound_xhs;
if (!isBoundXHS) {
// 未绑定小红书,提示并跳转
wx.showModal({
title: '未绑定小红书',
content: '查看文案前需要先绑定小红书账号',
confirmText: '去绑定',
success: (res) => {
if (res.confirm) {
wx.navigateTo({
url: '/pages/profile/social-binding/social-binding'
});
}
}
});
return;
}
// 登录且已绑定,跳转到文案列表页面
wx.navigateTo({
url: `/pages/articles/articles?productId=${this.data.selectedProduct}`
});
} else {
throw new Error('获取用户信息失败');
}
} catch (error) {
console.error('检查绑定状态失败:', error);
wx.showToast({
title: '获取用户信息失败',
icon: 'none'
});
}
},
// 滚动到底部加载更多
onScrollToLower() {
this.loadProducts();
},
// 分享功能
onShareAppMessage() {
return {
title: '万花筒AI助手 - 智能生成种草文案',
path: '/pages/home/home',
imageUrl: '' // 可以设置分享图片
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: '万花筒AI助手 - 智能生成种草文案',
imageUrl: '' // 可以设置分享图片
};
}
});

View File

@@ -0,0 +1,54 @@
<!--pages/home/home.wxml-->
<view class="page-container">
<!-- 页面标题 -->
<view class="page-header">
<text class="page-title">选择商品</text>
<text class="page-subtitle">运营提供</text>
</view>
<!-- 商品网格列表 -->
<scroll-view
class="product-scroll"
scroll-y
bindscrolltolower="onScrollToLower"
lower-threshold="100"
>
<view class="product-grid">
<view
class="product-card {{selectedProduct === item.id ? 'selected' : ''}}"
wx:for="{{products}}"
wx:key="id"
bindtap="toggleProduct"
data-id="{{item.id}}"
data-name="{{item.name}}"
>
<view class="product-image-wrapper">
<image class="product-image" src="{{item.image}}" mode="aspectFill" />
<view class="select-indicator" wx:if="{{selectedProduct === item.id}}">
<view class="check-icon"></view>
</view>
</view>
<text class="product-name">{{item.name}}</text>
</view>
</view>
<!-- 加载提示 -->
<view class="loading-more" wx:if="{{hasMore}}">
<text class="loading-text">加载更多...</text>
</view>
<view class="no-more" wx:else>
<text class="no-more-text">没有更多了</text>
</view>
</scroll-view>
<!-- 底部按钮 -->
<view class="bottom-action">
<button
class="generate-btn"
bindtap="goToGenerate"
disabled="{{!selectedProduct}}"
>
去生成内容
</button>
</view>
</view>

View File

@@ -0,0 +1,170 @@
/* pages/home/home.wxss */
page {
background: #f8f8f8;
height: 100%;
}
.page-container {
height: 100vh;
display: flex;
flex-direction: column;
background: white;
}
/* 页面头部 */
.page-header {
padding: 40rpx 30rpx 30rpx;
background: white;
border-bottom: 1rpx solid #f0f0f0;
}
.page-title {
display: block;
font-size: 40rpx;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 8rpx;
}
.page-subtitle {
display: block;
font-size: 24rpx;
color: #999;
}
/* 商品滚动容器 */
.product-scroll {
flex: 1;
padding: 0 0 110rpx 0;
background: white;
}
/* 商品网格布局 */
.product-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 20rpx;
}
/* 商品卡片 */
.product-card {
background: white;
border-radius: 12rpx;
overflow: hidden;
border: 2rpx solid #f0f0f0;
transition: all 0.3s;
position: relative;
}
.product-card.selected {
border-color: #07c160;
box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.2);
}
.product-image-wrapper {
width: 100%;
height: 330rpx;
background: #f5f5f5;
position: relative;
overflow: hidden;
}
.product-image {
width: 100%;
height: 100%;
}
/* 选中指示器 */
.select-indicator {
position: absolute;
top: 12rpx;
right: 12rpx;
width: 48rpx;
height: 48rpx;
background: #07c160;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.4);
}
.check-icon {
width: 24rpx;
height: 24rpx;
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="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.product-name {
display: block;
padding: 20rpx;
font-size: 28rpx;
color: #1a1a1a;
font-weight: 500;
line-height: 1.4;
height: 78rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
/* 加载提示 */
.loading-more,
.no-more {
text-align: center;
padding: 40rpx 0;
}
.loading-text,
.no-more-text {
font-size: 24rpx;
color: #999;
}
/* 底部操作栏 */
.bottom-action {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 8rpx 30rpx;
background: white;
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.05);
border-top: 1rpx solid #f0f0f0;
padding-bottom: calc(8rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
}
.generate-btn {
width: 100%;
height: 80rpx;
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
color: white;
border: none;
border-radius: 40rpx;
font-size: 30rpx;
font-weight: 500;
box-shadow: 0 2rpx 8rpx rgba(7, 193, 96, 0.25);
transition: all 0.3s;
}
.generate-btn::after {
border: none;
}
.generate-btn:active {
transform: scale(0.98);
}
.generate-btn[disabled] {
background: #ddd;
color: #999;
box-shadow: none;
}

View File

@@ -0,0 +1,4 @@
{
"usingComponents": {
}
}

View 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
})
}
})
},
},
})

View 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>

View 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;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "万花筒AI助手",
"navigationBarBackgroundColor": "#07c160",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,145 @@
// pages/login/login.ts
import { post } from '../../utils/request';
import { API } from '../../config/api';
Page({
data: {
loginLoading: false,
agreed: false
},
onLoad() {
// 检查是否已登录检查token
const token = wx.getStorageSync('token');
if (token) {
wx.switchTab({
url: '/pages/home/home'
});
}
},
// 同意协议
onAgreeChange(e: any) {
this.setData({
agreed: e.detail.value.length > 0
});
},
// 跳转到用户协议
goToUserAgreement() {
wx.navigateTo({
url: '/pages/agreement/user-agreement/user-agreement'
});
},
// 跳转到隐私政策
goToPrivacyPolicy() {
wx.navigateTo({
url: '/pages/agreement/privacy-policy/privacy-policy'
});
},
// 未同意协议时点击登录
handleAgreeFirst() {
if (this.data.loginLoading) return
// 弹窗提示用户同意协议
wx.showModal({
title: '用户协议',
content: '请阅读并同意《用户协议》和《隐私政策》后再登录',
confirmText: '同意',
cancelText: '不同意',
success: (res) => {
if (res.confirm) {
// 用户点击了同意,勾选协议
this.setData({ agreed: true });
}
}
});
},
// 微信登录(已同意协议后触发)
handleWechatLogin(e: any) {
if (this.data.loginLoading) return
// 检查用户是否授权了手机号
if (e.detail.errMsg && e.detail.errMsg !== 'getPhoneNumber:ok') {
// 用户拒绝授权手机号
console.log('用户拒绝授权手机号:', e.detail.errMsg);
wx.showToast({
title: '需要授权手机号才能登录',
icon: 'none',
duration: 2000
});
return;
}
// 用户已授权手机号,直接登录
this.performLogin(e.detail);
},
// 执行登录逻辑
async performLogin(phoneDetail?: any) {
this.setData({ loginLoading: true });
try {
// 1. 调用微信登录获取code
const loginRes = await wx.login();
if (!loginRes.code) {
throw new Error('获取微信code失败');
}
// 2. 处理手机号(如果用户授权了)
let phone = '';
if (phoneDetail && phoneDetail.code) {
// 用户授权了手机号,将 code 发送给后端解密
phone = phoneDetail.code; // 这是加密的code需要后端解密
} else if (phoneDetail && phoneDetail.errMsg && phoneDetail.errMsg !== 'getPhoneNumber:ok') {
// 用户拒绝授权手机号
console.log('用户拒绝授权手机号');
}
// 3. 调用后端登录API
const response = await post(API.auth.wechatLogin, {
code: loginRes.code,
phone_code: phone // 将手机号code发送给后端
}, false);
if (response.code === 200 && response.data) {
// 保存token
wx.setStorageSync('token', response.data.token);
// 保存员工信息
if (response.data.employee) {
wx.setStorageSync('employeeInfo', response.data.employee);
wx.setStorageSync('username', response.data.employee.name);
}
wx.showToast({
title: '登录成功',
icon: 'success',
duration: 1500
});
setTimeout(() => {
wx.switchTab({
url: '/pages/home/home'
});
}, 1500);
} else {
throw new Error(response.message || '登录失败');
}
} catch (error: any) {
console.error('登录失败:', error);
this.setData({ loginLoading: false });
wx.showToast({
title: error.errMsg || error.message || '登录失败',
icon: 'none'
});
}
}
})

View File

@@ -0,0 +1,58 @@
<!--pages/login/login.wxml-->
<view class="login-container">
<!-- 顶部Logo区域 -->
<view class="logo-section">
<view class="logo-box">
<view class="logo-icon"></view>
</view>
<text class="app-name">AI文章审核平台</text>
<text class="app-slogan">智能内容生成与审核管理</text>
</view>
<!-- 登录区域 -->
<view class="login-section">
<view class="welcome-text">
<text class="welcome-title">欢迎使用</text>
<text class="welcome-subtitle">请使用微信授权登录</text>
</view>
<!-- 微信登录按钮(未同意协议) -->
<button
wx:if="{{!agreed}}"
class="login-btn {{loginLoading ? 'loading' : ''}}"
bindtap="handleAgreeFirst"
loading="{{loginLoading}}"
hover-class="btn-hover"
>
<text class="btn-icon wechat-icon"></text>
<text class="btn-text">微信登录</text>
</button>
<!-- 微信登录按钮(已同意协议,获取手机号) -->
<button
wx:else
class="login-btn {{loginLoading ? 'loading' : ''}}"
open-type="getPhoneNumber"
bindgetphonenumber="handleWechatLogin"
loading="{{loginLoading}}"
hover-class="btn-hover"
>
<text class="btn-icon wechat-icon"></text>
<text class="btn-text">微信登录</text>
</button>
<view class="agreement-section">
<checkbox-group bindchange="onAgreeChange">
<label class="agreement-label">
<checkbox value="agree" checked="{{agreed}}" color="#ff2442" />
<view class="agreement-text">
<text class="normal-text">我已阅读并同意</text>
<text class="link-text" catchtap="goToUserAgreement">《用户协议》</text>
<text class="normal-text">和</text>
<text class="link-text" catchtap="goToPrivacyPolicy">《隐私政策》</text>
</view>
</label>
</checkbox-group>
</view>
</view>
</view>

View File

@@ -0,0 +1,236 @@
/* pages/login/login.wxss */
page {
height: 100vh;
background: #07c160;
overflow: hidden;
position: relative;
}
page::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: rotate 30s linear infinite;
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.login-container {
display: flex;
flex-direction: column;
min-height: 100vh;
background: transparent;
position: relative;
z-index: 1;
}
/* 顶部Logo区域 */
.logo-section {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 60rpx;
}
.logo-box {
width: 160rpx;
height: 160rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
backdrop-filter: blur(10rpx);
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10rpx);
}
}
.logo-icon {
width: 80rpx;
height: 80rpx;
background: white;
border-radius: 16rpx;
}
.app-name {
font-size: 48rpx;
font-weight: bold;
color: white;
margin-bottom: 16rpx;
letter-spacing: 2rpx;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
}
.app-slogan {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 1rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
}
/* 登录区域 - 毛玻璃效果 */
.login-section {
background: rgba(255, 255, 255, 0.85);
border-radius: 40rpx 40rpx 0 0;
padding: 60rpx 40rpx 80rpx;
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.15);
backdrop-filter: blur(40rpx);
-webkit-backdrop-filter: blur(40rpx);
position: relative;
overflow: hidden;
border: 1rpx solid rgba(255, 255, 255, 0.3);
}
.login-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2rpx;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.8), transparent);
}
.welcome-text {
margin-bottom: 60rpx;
}
.welcome-title {
display: block;
font-size: 40rpx;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 12rpx;
}
.welcome-subtitle {
display: block;
font-size: 28rpx;
color: rgba(0, 0, 0, 0.5);
}
.login-btn {
width: 80%;
max-width: 600rpx;
background: linear-gradient(135deg, #07c160 0%, #2dd573 100%);
color: white;
border: none;
border-radius: 48rpx;
padding: 36rpx;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.25);
transition: all 0.3s;
margin: 0 auto 32rpx;
}
.login-btn::after {
border: none;
}
.btn-hover {
opacity: 0.9;
transform: translateY(2rpx);
box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.2);
}
.login-btn.loading {
opacity: 0.7;
pointer-events: none;
}
.btn-icon {
margin-right: 12rpx;
position: relative;
z-index: 1;
}
.wechat-icon {
width: 44rpx;
height: 44rpx;
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTcuNSAxMi41QzExLjE1IDEyLjUgNiAxNy4xIDYgMjIuOEM2IDI2LjEgNy42NSAyOSAxMC4yIDMxTDkgMzUuNUwxMy41IDMzLjJDMTQuOCAzMy43IDE2LjEgMzQgMTcuNSAzNEMyMy44NSAzNCAyOSAyOS40IDI5IDIzLjdDMjkgMTguMSAyMy44NSAxMi41IDE3LjUgMTIuNVpNMTQuNSAyNkMxMy4xMiAyNiAxMiAyNC44OCAxMiAyMy41QzEyIDIyLjEyIDEzLjEyIDIxIDE0LjUgMjFDMTUuODggMjEgMTcgMjIuMTIgMTcgMjMuNUMxNyAyNC44OCAxNS44OCAyNiAxNC41IDI2Wk0yMC41IDI2QzE5LjEyIDI2IDE4IDI0Ljg4IDE4IDIzLjVDMTggMjIuMTIgMTkuMTIgMjEgMjAuNSAyMUMyMS44OCAyMSAyMyAyMi4xMiAyMyAyMy41QzIzIDI0Ljg4IDIxLjg4IDI2IDIwLjUgMjZaTTMxLjUgMjJDMzEuMiAyMiAzMC45IDIyIDMwLjYgMjIuMUMzMS41IDE5LjkgMzEuOCAxNy41IDMxLjMgMTUuMkMzNy4yIDE2LjYgNDEuNSAyMS4yIDQxLjUgMjYuN0M0MS41IDI5LjMgNDAuMiAzMS43IDM4LjEgMzMuNEwzOSAzNy41TDM1LjEgMzUuNkMzNCAxNi4wNSAzMi4yNSAzNi40IDMxLjUgMzYuNEMyNi4xNSAzNi40IDIxLjcgMzIuNSAyMS43IDI3LjdDMjEuNyAyNC40IDI0IDIxLjYgMjcuNSAyMC4xQzI4LjcgMjAuOSAzMC4xIDIxLjQgMzEuNSAyMS40VjIyWk0yNy41IDMwQzI2LjEyIDMwIDI1IDI4Ljg4IDI1IDI3LjVDMjUgMjYuMTIgMjYuMTIgMjUgMjcuNSAyNUMyOC44OCAyNSAzMCAyNi4xMiAzMCAyNy41QzMwIDI4Ljg4IDI4Ljg4IDMwIDI3LjUgMzBaTTM1LjUgMzBDMzQuMTIgMzAgMzMgMjguODggMzMgMjcuNUMzMyAyNi4xMiAzNC4xMiAyNSAzNS41IDI1QzM2Ljg4IDI1IDM4IDI2LjEyIDM4IDI3LjVDMzggMjguODggMzYuODggMzAgMzUuNSAzMFoiIGZpbGw9IndoaXRlIi8+PC9zdmc+');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.btn-text {
position: relative;
z-index: 1;
}
.login-tip {
margin-top: 40rpx;
font-size: 24rpx;
text-align: center;
color: #999;
line-height: 1.6;
}
.tip-text {
color: #999;
}
.tip-link {
color: #07c160;
font-weight: 500;
}
/* 协议同意区域 */
.agreement-section {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.agreement-label {
display: flex;
align-items: center;
gap: 12rpx;
}
checkbox {
transform: scale(0.7);
margin: 0;
padding: 0;
flex-shrink: 0;
}
.agreement-text {
flex: 1;
font-size: 24rpx;
line-height: 1.6;
color: rgba(0, 0, 0, 0.6);
}
.normal-text {
color: rgba(0, 0, 0, 0.6);
}
.link-text {
color: #07c160;
font-weight: 500;
}

View File

@@ -0,0 +1,4 @@
{
"usingComponents": {
}
}

View File

@@ -0,0 +1,21 @@
// logs.ts
// const util = require('../../utils/util.js')
import { formatTime } from '../../utils/util'
Component({
data: {
logs: [],
},
lifetimes: {
attached() {
this.setData({
logs: (wx.getStorageSync('logs') || []).map((log: string) => {
return {
date: formatTime(new Date(log)),
timeStamp: log
}
}),
})
}
},
})

View File

@@ -0,0 +1,6 @@
<!--logs.wxml-->
<scroll-view class="scrollarea" scroll-y type="list">
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
<view class="log-item">{{index + 1}}. {{log.date}}</view>
</block>
</scroll-view>

View File

@@ -0,0 +1,16 @@
page {
height: 100vh;
display: flex;
flex-direction: column;
}
.scrollarea {
flex: 1;
overflow-y: hidden;
}
.log-item {
margin-top: 20rpx;
text-align: center;
}
.log-item:last-child {
padding-bottom: env(safe-area-inset-bottom);
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "关于我们",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,10 @@
// pages/profile/about/about.ts
Page({
data: {
},
onLoad() {
}
});

View File

@@ -0,0 +1,45 @@
<!--pages/profile/about/about.wxml-->
<view class="page-container">
<view class="logo-section">
<view class="app-logo"></view>
<text class="app-name">AI文章审核平台</text>
<text class="app-version">v1.0.0</text>
</view>
<view class="info-section">
<view class="info-card">
<view class="card-title">产品介绍</view>
<text class="card-content">AI文章审核平台是一款智能内容生成与审核管理系统通过AI技术帮助用户高效管理文章内容提升内容审核效率。</text>
</view>
<view class="info-card">
<view class="card-title">联系我们</view>
<view class="contact-item">
<text class="contact-label">客服邮箱:</text>
<text class="contact-value">support@example.com</text>
</view>
<view class="contact-item">
<text class="contact-label">客服电话:</text>
<text class="contact-value">400-888-8888</text>
</view>
<view class="contact-item">
<text class="contact-label">工作时间:</text>
<text class="contact-value">周一至周五 9:00-18:00</text>
</view>
</view>
<view class="info-card">
<view class="card-title">更新日志</view>
<view class="log-item">
<text class="log-version">v1.0.0</text>
<text class="log-date">2024-12-05</text>
<text class="log-content">· 初始版本发布\n· 支持文章管理和审核\n· 优化用户体验</text>
</view>
</view>
</view>
<view class="footer-section">
<text class="copyright">© 2024 AI文章审核平台</text>
<text class="copyright">All Rights Reserved</text>
</view>
</view>

View File

@@ -0,0 +1,171 @@
/* pages/profile/about/about.wxss */
page {
background: #f8f8f8;
height: 100%;
width: 100%;
}
.page-container {
min-height: 100vh;
width: 100%;
padding-bottom: 60rpx;
box-sizing: border-box;
overflow-x: hidden;
}
.logo-section {
width: 100%;
background: #07c160;
padding: 80rpx 30rpx;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
}
.app-logo {
width: 160rpx;
height: 160rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
backdrop-filter: blur(10rpx);
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
position: relative;
margin-bottom: 30rpx;
}
.app-logo::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80rpx;
height: 80rpx;
background: white;
border-radius: 50%;
}
.app-logo::after {
content: '';
position: absolute;
width: 32rpx;
height: 32rpx;
background: #07c160;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.app-name {
font-size: 40rpx;
font-weight: bold;
color: white;
margin-bottom: 16rpx;
}
.app-version {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
.info-section {
width: 100%;
padding: 20rpx 30rpx;
box-sizing: border-box;
}
.info-card {
width: 100%;
background: white;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.card-content {
font-size: 28rpx;
color: #666;
line-height: 1.8;
word-wrap: break-word;
word-break: break-all;
}
.contact-item {
display: flex;
margin-bottom: 16rpx;
word-wrap: break-word;
word-break: break-all;
}
.contact-item:last-child {
margin-bottom: 0;
}
.contact-label {
font-size: 28rpx;
color: #999;
min-width: 140rpx;
}
.contact-value {
font-size: 28rpx;
color: #333;
word-wrap: break-word;
word-break: break-all;
flex: 1;
}
.log-item {
display: flex;
flex-direction: column;
}
.log-version {
font-size: 30rpx;
font-weight: 600;
color: #07c160;
margin-bottom: 8rpx;
}
.log-date {
font-size: 24rpx;
color: #999;
margin-bottom: 16rpx;
}
.log-content {
font-size: 28rpx;
color: #666;
line-height: 1.8;
white-space: pre-line;
}
.footer-section {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 30rpx;
box-sizing: border-box;
}
.copyright {
font-size: 24rpx;
color: #999;
line-height: 1.6;
}

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "文章详情",
"usingComponents": {}
}

View File

@@ -0,0 +1,114 @@
// pages/profile/article-detail/article-detail.ts
import { EmployeeService } from '../../../services/employee';
Page({
data: {
articleId: 0,
productName: '',
article: {
title: '',
content: '',
topic: '',
publishLink: '', // 小红书链接
images: [] as Array<{
id: number;
image_url: string;
image_thumb_url: string;
sort_order: number;
keywords_name: string;
}>,
tags: [] as string[]
},
loading: true
},
onLoad(options: any) {
if (options.id) {
this.setData({
articleId: parseInt(options.id)
});
this.loadArticleDetail();
}
},
async loadArticleDetail() {
try {
// 调用专门的详情API
const response = await EmployeeService.getPublishRecordDetail(this.data.articleId);
console.log('=== 详情响应 ===', response);
console.log('response.data:', response.data);
if (response.code === 200 && response.data) {
console.log('product_name:', response.data.product_name);
console.log('title:', response.data.title);
console.log('content:', response.data.content);
console.log('images:', response.data.images);
console.log('tags:', response.data.tags);
console.log('topic:', response.data.topic);
this.setData({
productName: response.data.product_name || '未知商品',
article: {
title: response.data.title || '',
content: response.data.content || '',
topic: response.data.topic || '',
publishLink: response.data.publish_link || '', // 获取小红书链接
images: response.data.images || [], // 获取图片列表
tags: response.data.tags || [] // 获取标签列表
},
loading: false
});
console.log('=== 设置后的数据 ===');
console.log('productName:', this.data.productName);
console.log('article:', this.data.article);
} else {
throw new Error(response.message || '加载失败');
}
} catch (error: any) {
console.error('加载文章详情失败:', error);
this.setData({ loading: false });
wx.showToast({
title: error.message || '加载失败',
icon: 'none'
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
},
// 分享链接(自动复制)
shareArticle() {
const publishLink = this.data.article.publishLink;
if (!publishLink) {
wx.showToast({
title: '暂无发布链接',
icon: 'none'
});
return;
}
// 复制到剪贴板
wx.setClipboardData({
data: publishLink,
success: () => {
wx.showToast({
title: '链接已复制',
icon: 'success',
duration: 2000
});
},
fail: () => {
wx.showToast({
title: '复制失败',
icon: 'none'
});
}
});
}
});

View File

@@ -0,0 +1,78 @@
<!--pages/profile/article-detail/article-detail.wxml-->
<view class="page-container">
<!-- 商品信息卡片 -->
<view class="product-card">
<text class="card-title">选中商品</text>
<text class="product-name">{{productName}}</text>
</view>
<!-- 加载状态 -->
<view class="loading-container" wx:if="{{loading}}">
<text class="loading-text">加载中...</text>
</view>
<!-- 生成的文章 -->
<scroll-view class="article-container" scroll-y enable-flex wx:if="{{!loading}}">
<view class="article-wrapper">
<view class="article-header">
<text class="article-title" user-select>{{article.title}}</text>
</view>
<view class="article-content" wx:if="{{article.content}}">
<text class="content-text" user-select>{{article.content}}</text>
</view>
<!-- 文章图片 -->
<view class="article-images" wx:if="{{article.images && article.images.length > 0}}">
<view
class="image-item"
wx:for="{{article.images}}"
wx:key="id"
>
<image
class="article-image"
src="{{item.image_thumb_url || item.image_url}}"
mode="aspectFill"
lazy-load
/>
</view>
</view>
<!-- 文章标签 -->
<view class="article-tags" wx:if="{{article.tags && article.tags.length > 0}}">
<text
class="tag-item"
wx:for="{{article.tags}}"
wx:key="index"
user-select
>
#{{item}}
</text>
</view>
<!-- Topic标签如果tags为空则显示topic -->
<view class="article-tags" wx:elif="{{article.topic}}">
<text class="tag-item" user-select>#{{article.topic}}</text>
</view>
<!-- 小红书链接 -->
<view class="xhs-link-section" wx:if="{{article.publishLink}}">
<view class="link-label">
<text class="iconfont icon-link link-icon"></text>
<text class="link-text">小红书链接</text>
</view>
<view class="link-value">
<text user-select>{{article.publishLink}}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="action-bar" wx:if="{{!loading}}">
<button class="share-btn" bindtap="shareArticle">
<view class="btn-icon icon-share"></view>
<text class="btn-text">分享链接</text>
</button>
</view>
</view>

View File

@@ -0,0 +1,254 @@
/* pages/profile/article-detail/article-detail.wxss */
page {
background: #f8f8f8;
height: 100%;
width: 100%;
overflow-x: hidden; /* 防止横向滚动 */
}
.page-container {
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding-bottom: 120rpx; /* 为底部操作栏留出空间 */
overflow-x: hidden; /* 防止横向滚动 */
}
/* 商品卡片 */
.product-card {
background: linear-gradient(135deg, #ff2442 0%, #ff6b8b 100%);
border-radius: 16rpx;
padding: 30rpx;
margin: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(255, 36, 66, 0.2);
box-sizing: border-box;
width: calc(100% - 40rpx); /* 确保不超出屏幕 */
}
.card-title {
display: block;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12rpx;
}
.product-name {
display: block;
font-size: 32rpx;
color: white;
font-weight: bold;
}
/* 加载状态 */
.loading-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
/* 文章容器 */
.article-container {
flex: 1;
background: white;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
margin: 0 20rpx 20rpx 20rpx;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
width: calc(100% - 40rpx); /* 确保不超出屏幕 */
}
.article-wrapper {
padding: 30rpx;
box-sizing: border-box;
width: 100%;
overflow-x: hidden; /* 防止内容溢出 */
}
.article-header {
margin-bottom: 24rpx;
padding-bottom: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
box-sizing: border-box;
width: 100%;
}
.article-title {
font-size: 36rpx;
color: #1a1a1a;
font-weight: bold;
line-height: 1.4;
word-wrap: break-word; /* 允许换行 */
word-break: break-word; /* 允许换行 */
}
.article-content {
margin-bottom: 24rpx;
box-sizing: border-box;
width: 100%;
}
.content-text {
font-size: 28rpx;
color: #333;
line-height: 1.8;
white-space: pre-line;
word-wrap: break-word; /* 允许换行 */
word-break: break-word; /* 允许换行 */
}
/* 文章图片 */
.article-images {
margin: 24rpx 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
box-sizing: border-box;
width: 100%;
}
.image-item {
width: 100%;
padding-bottom: 100%; /* 1:1 比例 */
position: relative;
border-radius: 8rpx;
overflow: hidden;
background: #f5f5f5;
}
.article-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* 标签 */
.article-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
padding-bottom: 20rpx;
margin-bottom: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
box-sizing: border-box;
width: 100%;
}
.tag-item {
padding: 4rpx 16rpx;
background: linear-gradient(135deg, #fff5f7 0%, #ffe8ec 100%);
border-radius: 16rpx;
font-size: 24rpx;
color: #ff2442;
font-weight: 500;
line-height: 1.5;
word-wrap: break-word; /* 允许换行 */
}
/* 小红书链接区域 */
.xhs-link-section {
background: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
margin-top: 24rpx;
box-sizing: border-box;
width: 100%;
overflow-x: hidden; /* 防止链接超出 */
}
.link-label {
display: flex;
align-items: center;
gap: 8rpx;
margin-bottom: 12rpx;
box-sizing: border-box;
}
.link-icon {
font-size: 28rpx;
flex-shrink: 0; /* 防止图标被压缩 */
color: #ff2442;
}
.link-text {
font-size: 26rpx;
color: #666;
font-weight: 500;
flex-shrink: 0; /* 防止文字被压缩 */
}
.link-value {
font-size: 24rpx;
color: #ff2442;
word-break: break-all; /* 强制换行 */
line-height: 1.6;
overflow-wrap: break-word; /* 允许换行 */
max-width: 100%; /* 确保不超出 */
box-sizing: border-box;
}
/* 底部操作栏 */
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 20rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
z-index: 999; /* 提高层级 */
}
.share-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #ff2442 0%, #ff6b8b 100%);
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
border: none;
box-shadow: 0 6rpx 20rpx rgba(255, 36, 66, 0.3);
transition: all 0.3s;
}
.share-btn::after {
border: none;
}
.share-btn:active {
transform: scale(0.98);
opacity: 0.9;
}
.btn-icon {
width: 36rpx;
height: 36rpx;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.icon-share {
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE4IDhDMTkuNjU2OSA4IDIxIDYuNjU2ODUgMjEgNUMyMSAzLjM0MzE1IDE5LjY1NjkgMiAxOCAyQzE2LjM0MzEgMiAxNSAzLjM0MzE1IDE1IDVDMTUgNS4xMjk3MSAxNS4wMDk1IDUuMjU3MjMgMTUuMDI3NyA1LjM4MTk3TDguODg1OTMgOC43ODc0NUM4LjQzMzQ1IDguMjg5NTkgNy43OTQzNSA4IDcuMDggOCA1LjQyMzE1IDggNCAxMC42NTY5IDQgMTJDNCAxMi44NjkgNC4zNzcwNiAxMy42NDU5IDQuOTY5NTggMTQuMTk5OEw0Ljk3MTIzIDE0LjE5ODdDNC45ODA3NCAxNC4yMDQ1IDQuOTkwNDkgMTQuMjEgNSAxNC4yMTU0QzUuMzQ5MDUgMTQuNDk5MyA1Ljc1MzggMTQuNzE4NSA2LjE4OTggMTQuODUyNkM2LjQ0ODcxIDE0Ljk0NTYgNi43MjQzOCAxNSA3LjA4IDE1QzcuNzk0MzUgMTUgOC40MzM0NSAxNC43MTA0IDguODg1OTMgMTQuMjEyNUwxNS4wMjc3IDE3LjYxOEMxNS4wMDk1IDE3Ljc0MjggMTUgMTcuODcwMyAxNSAxOUMxNSAyMC42NTY5IDE2LjM0MzEgMjIgMTggMjJDMTkuNjU2OSAyMiAyMSAyMC42NTY5IDIxIDE5QzIxIDE3LjM0MzEgMTkuNjU2OSAxNiAxOCAxNkMxNy4yNDkyIDE2IDE2LjU3NTYgMTYuMjk5IDE2LjA5MjggMTYuNzg2OUw5Ljk3MTIzIDE0LjE5ODdDOS45ODA3NCAxNC4yMDQ1IDkuOTkwNDkgMTQuMjEgMTAgMTQuMjE1NEMxMC4zNDkgMTQuNDk5MyAxMC43NTM4IDE0LjcxODUgMTEuMTg5OCAxNC44NTI2QzExLjQ0ODcgMTQuOTQ1NiAxMS43MjQ0IDE1IDEyLjA4IDE1QzEyLjc5NDQgMTUgMTMuNDMzNCAxNC43MTA0IDEzLjg4NTkgMTQuMjEyNUwxOS4wMjc3IDE3LjYxOEMxOS4wMDk1IDE3Ljc0MjggMTkgMTcuODcwMyAxOSAxOUMxOSAyMC42NTY5IDIwLjM0MzEgMjIgMjIgMjJDMjMuNjU2OSAyMiAyNSAyMC42NTY5IDI1IDE5QzI1IDE3LjM0MzEgMjMuNjU2OSAxNiAyMiAxNkMyMS4yNDkyIDE2IDIwLjU3NTYgMTYuMjk5IDIwLjA5MjggMTYuNzg2OUwxNC45NzEyIDE0LjE5ODdDMTQuOTgwNyAxNC4yMDQ1IDE0Ljk5MDUgMTQuMjEgMTUgMTQuMjE1NFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=');
}
.btn-text {
font-size: 32rpx;
color: white;
font-weight: 600;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "环境切换",
"navigationBarBackgroundColor": "#667eea",
"navigationBarTextStyle": "white"
}

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "意见反馈",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,58 @@
// pages/profile/feedback/feedback.ts
Page({
data: {
typeList: ['功能建议', 'Bug反馈', '体验问题', '其他'],
typeIndex: 0,
content: '',
contact: ''
},
onLoad() {
},
onTypeChange(e: any) {
this.setData({
typeIndex: e.detail.value
});
},
onContentInput(e: any) {
this.setData({
content: e.detail.value
});
},
onContactInput(e: any) {
this.setData({
contact: e.detail.value
});
},
handleSubmit() {
if (!this.data.content.trim()) {
wx.showToast({
title: '请输入问题描述',
icon: 'none'
});
return;
}
wx.showLoading({
title: '提交中...',
mask: true
});
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: '提交成功',
icon: 'success'
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}, 1000);
}
});

View File

@@ -0,0 +1,40 @@
<!--pages/profile/feedback/feedback.wxml-->
<view class="page-container">
<view class="form-section">
<view class="form-item">
<view class="item-label">反馈类型</view>
<picker bindchange="onTypeChange" value="{{typeIndex}}" range="{{typeList}}">
<view class="picker-value">
{{typeList[typeIndex]}}
<text class="picker-arrow"></text>
</view>
</picker>
</view>
<view class="form-item textarea-item">
<view class="item-label">问题描述</view>
<textarea
class="feedback-textarea"
placeholder="请详细描述您遇到的问题或建议"
maxlength="500"
value="{{content}}"
bindInput="onContentInput"
/>
<view class="char-count">{{content.length}}/500</view>
</view>
<view class="form-item">
<view class="item-label">联系方式(选填)</view>
<input
class="feedback-input"
placeholder="请输入您的手机号或邮箱"
value="{{contact}}"
bindInput="onContactInput"
/>
</view>
</view>
<view class="submit-section">
<button class="submit-btn" bindtap="handleSubmit">提交反馈</button>
</view>
</view>

View File

@@ -0,0 +1,100 @@
/* pages/profile/feedback/feedback.wxss */
page {
background: #f8f8f8;
}
.page-container {
min-height: 100vh;
padding-bottom: 120rpx;
}
.form-section {
width: 100%;
background: white;
margin-top: 20rpx;
}
.form-item {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.form-item:last-child {
border-bottom: none;
}
.item-label {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 20rpx;
}
.picker-value {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 28rpx;
color: #666;
padding: 20rpx 0;
}
.picker-arrow {
font-size: 36rpx;
color: #ccc;
}
.feedback-textarea {
width: 100%;
min-height: 300rpx;
font-size: 28rpx;
color: #333;
line-height: 1.6;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
box-sizing: border-box;
}
.char-count {
text-align: right;
font-size: 24rpx;
color: #999;
margin-top: 12rpx;
}
.feedback-input {
width: 100%;
font-size: 28rpx;
color: #333;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
box-sizing: border-box;
}
.submit-section {
width: 100%;
padding: 40rpx 30rpx;
}
.submit-btn {
width: 100%;
background: #07c160;
color: white;
border: none;
border-radius: 16rpx;
padding: 32rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 8rpx 24rpx rgba(255, 36, 66, 0.3);
}
.submit-btn::after {
border: none;
}
.submit-btn:active {
opacity: 0.9;
transform: translateY(2rpx);
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "绑定账号",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,285 @@
// pages/profile/platform-bind/platform-bind.ts
Page({
data: {
platformType: '',
platformName: '',
countryCode: '+86',
phone: '',
code: '',
codeSending: false,
countdown: 0,
binded: false,
bindTime: ''
},
onLoad(options: any) {
const platform = options.platform || 'xiaohongshu';
// 如果是小红书,跳转到专门的小红书登录页
if (platform === 'xiaohongshu') {
wx.redirectTo({
url: '/pages/profile/xhs-login/xhs-login'
});
return;
}
const platformNames: Record<string, string> = {
xiaohongshu: '小红书',
weibo: '微博',
douyin: '抖音'
};
this.setData({
platformType: platform,
platformName: platformNames[platform]
});
this.loadBindingStatus(platform);
},
// 加载绑定状态
loadBindingStatus(platform: string) {
const bindings = wx.getStorageSync('socialBindings') || {};
const bindingInfo = bindings[platform];
if (bindingInfo) {
const bindTime = new Date(bindingInfo.bindTime);
const formatTime = `${bindTime.getFullYear()}-${String(bindTime.getMonth() + 1).padStart(2, '0')}-${String(bindTime.getDate()).padStart(2, '0')} ${String(bindTime.getHours()).padStart(2, '0')}:${String(bindTime.getMinutes()).padStart(2, '0')}`;
this.setData({
binded: true,
phone: bindingInfo.phone,
bindTime: formatTime
});
}
},
// 选择国家区号
selectCountryCode() {
const codes = ['+86', '+852', '+853', '+886', '+1', '+44', '+81', '+82'];
const names = ['中国大陆', '中国香港', '中国澳门', '中国台湾', '美国', '英国', '日本', '韩国'];
wx.showActionSheet({
itemList: names.map((name, index) => `${name} ${codes[index]}`),
success: (res) => {
this.setData({
countryCode: codes[res.tapIndex]
});
}
});
},
// 手机号输入
onPhoneInput(e: any) {
this.setData({
phone: e.detail.value
});
},
// 验证码输入
onCodeInput(e: any) {
this.setData({
code: e.detail.value
});
},
// 发送验证码
sendCode() {
const phone = this.data.phone;
// 验证手机号
if (!phone || phone.length !== 11) {
wx.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
// 手机号格式验证
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(phone)) {
wx.showToast({
title: '手机号格式不正确',
icon: 'none'
});
return;
}
// 设置发送状态
this.setData({
codeSending: true
});
// 模拟发送验证码实际应该调用后端API
wx.showLoading({
title: '发送中...',
mask: true
});
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: '验证码已发送',
icon: 'success'
});
// 开始倒计时
this.startCountdown();
// 在控制台输出模拟验证码(实际开发中删除)
console.log(`验证码: 123456`);
}, 1000);
},
// 倒计时
startCountdown() {
let countdown = 60;
this.setData({
countdown: countdown,
codeSending: true
});
const timer = setInterval(() => {
countdown--;
if (countdown <= 0) {
clearInterval(timer);
this.setData({
countdown: 0,
codeSending: false
});
} else {
this.setData({
countdown: countdown
});
}
}, 1000);
},
// 绑定账号
bindAccount() {
const phone = this.data.phone;
const code = this.data.code;
const platform = this.data.platformType;
// 验证手机号
if (!phone || phone.length !== 11) {
wx.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
// 验证验证码
if (!code || code.length !== 6) {
wx.showToast({
title: '请输入6位验证码',
icon: 'none'
});
return;
}
// 模拟验证码验证实际应该调用后端API
wx.showLoading({
title: '绑定中...',
mask: true
});
setTimeout(() => {
wx.hideLoading();
// 保存绑定信息
const bindings = wx.getStorageSync('socialBindings') || {};
const bindTime = new Date().getTime();
bindings[platform] = {
phone: phone,
bindTime: bindTime
};
wx.setStorageSync('socialBindings', bindings);
// 格式化时间
const time = new Date(bindTime);
const formatTime = `${time.getFullYear()}-${String(time.getMonth() + 1).padStart(2, '0')}-${String(time.getDate()).padStart(2, '0')} ${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`;
// 更新状态
this.setData({
binded: true,
code: '',
bindTime: formatTime
});
wx.showToast({
title: '绑定成功',
icon: 'success',
duration: 1500
});
}, 1500);
},
// 跳转到用户协议
goToUserAgreement() {
wx.navigateTo({
url: '/pages/agreement/user-agreement/user-agreement'
});
},
// 跳转到隐私政策
goToPrivacy() {
wx.navigateTo({
url: '/pages/agreement/privacy-policy/privacy-policy'
});
},
// 跳转到个人信息保护政策
goToPersonalInfo() {
// TODO: 创建个人信息保护政策页面
wx.showToast({
title: '功能开发中',
icon: 'none'
});
},
// 解除绑定
unbindAccount() {
const platform = this.data.platformType;
const platformName = this.data.platformName;
wx.showModal({
title: '解除绑定',
content: `确定要解除${platformName}账号绑定吗?`,
confirmText: '解除',
confirmColor: '#07c160',
success: (res) => {
if (res.confirm) {
// 删除绑定信息
const bindings = wx.getStorageSync('socialBindings') || {};
delete bindings[platform];
wx.setStorageSync('socialBindings', bindings);
// 更新状态
this.setData({
binded: false,
phone: '',
code: '',
bindTime: ''
});
wx.showToast({
title: '已解除绑定',
icon: 'success',
duration: 1500,
success: () => {
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
});
}
}
});
}
});

View File

@@ -0,0 +1,84 @@
<!--pages/profile/platform-bind/platform-bind.wxml-->
<view class="page-container">
<view class="platform-header">
<view class="platform-icon {{platformType}}"></view>
<view class="platform-text">
<text class="platform-name">{{platformName}}</text>
<text class="platform-tip">绑定后可同步发布内容</text>
</view>
</view>
<!-- 未绑定状态 -->
<view class="bind-form" wx:if="{{!binded}}">
<!-- 手机号 -->
<view class="form-item">
<view class="phone-input-wrapper">
<view class="country-code" bindtap="selectCountryCode">
<text class="code-text">{{countryCode}}</text>
<text class="arrow-down">▼</text>
</view>
<input
class="phone-input"
type="number"
maxlength="11"
placeholder="请输入手机号"
value="{{phone}}"
bindinput="onPhoneInput"
/>
</view>
</view>
<!-- 验证码 -->
<view class="form-item">
<view class="code-input-wrapper">
<input
class="code-input"
type="number"
maxlength="6"
placeholder="请输入验证码"
value="{{code}}"
bindinput="onCodeInput"
/>
<view
class="send-code-text {{codeSending ? 'disabled' : ''}}"
bindtap="sendCode"
>
{{countdown > 0 ? '重新发送(' + countdown + 's)' : '获取验证码'}}
</view>
</view>
</view>
<button class="bind-btn" bindtap="bindAccount">
确认
</button>
<!-- 协议提示 -->
<view class="agreement-tip">
<text class="tip-icon">⚠️</text>
<text class="tip-text">我已阅读并同意</text>
<text class="tip-link" bindtap="goToUserAgreement">《用户协议》</text>
<text class="tip-text">、</text>
<text class="tip-link" bindtap="goToPrivacy">《隐私政策》</text>
<text class="tip-text">、</text>
<text class="tip-link" bindtap="goToPersonalInfo">《个人信息保护政策》</text>
</view>
</view>
<!-- 已绑定状态 -->
<view class="bind-info" wx:else>
<view class="info-card">
<view class="info-item">
<text class="info-label">绑定手机号</text>
<text class="info-value">{{phone}}</text>
</view>
<view class="info-item">
<text class="info-label">绑定时间</text>
<text class="info-value">{{bindTime}}</text>
</view>
</view>
<button class="unbind-btn" bindtap="unbindAccount">
解除绑定
</button>
</view>
</view>

View File

@@ -0,0 +1,247 @@
/* pages/profile/platform-bind/platform-bind.wxss */
page {
background: #f8f8f8;
height: 100%;
}
.page-container {
padding: 30rpx;
min-height: 100vh;
box-sizing: border-box;
}
/* 平台头部 */
.platform-header {
background: white;
border-radius: 16rpx;
padding: 40rpx;
margin-bottom: 30rpx;
display: flex;
align-items: center;
gap: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.platform-icon {
width: 120rpx;
height: 120rpx;
border-radius: 20rpx;
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-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.platform-name {
font-size: 36rpx;
font-weight: bold;
color: #1a1a1a;
}
.platform-tip {
font-size: 26rpx;
color: #999;
}
/* 表单 */
.bind-form {
background: white;
border-radius: 16rpx;
padding: 40rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.form-item {
margin-bottom: 32rpx;
}
.form-item:last-of-type {
margin-bottom: 0;
}
/* 手机号输入 */
.phone-input-wrapper {
display: flex;
align-items: center;
height: 88rpx;
background: #f8f8f8;
border-radius: 12rpx;
overflow: hidden;
}
.country-code {
display: flex;
align-items: center;
gap: 8rpx;
padding: 0 20rpx;
height: 100%;
border-right: 1rpx solid #e0e0e0;
flex-shrink: 0;
}
.code-text {
font-size: 28rpx;
color: #1a1a1a;
font-weight: 500;
}
.arrow-down {
font-size: 20rpx;
color: #999;
}
.phone-input {
flex: 1;
height: 100%;
padding: 0 24rpx;
font-size: 28rpx;
color: #1a1a1a;
}
/* 验证码输入 */
.code-input-wrapper {
display: flex;
align-items: center;
height: 88rpx;
background: #f8f8f8;
border-radius: 12rpx;
overflow: hidden;
}
.code-input {
flex: 1;
height: 100%;
padding: 0 24rpx;
font-size: 28rpx;
color: #1a1a1a;
}
.send-code-text {
padding: 0 24rpx;
height: 100%;
display: flex;
align-items: center;
font-size: 26rpx;
color: #07c160;
white-space: nowrap;
flex-shrink: 0;
}
.send-code-text.disabled {
color: #999;
}
.bind-btn {
width: 100%;
height: 88rpx;
background: #07c160;
color: white;
border: none;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 600;
margin-top: 40rpx;
margin-bottom: 24rpx;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.bind-btn::after {
border: none;
}
/* 协议提示 */
.agreement-tip {
display: flex;
align-items: flex-start;
flex-wrap: wrap;
gap: 4rpx;
padding: 0 8rpx;
}
.tip-icon {
font-size: 24rpx;
margin-right: 4rpx;
}
.tip-text {
font-size: 22rpx;
color: #999;
line-height: 1.6;
}
.tip-link {
font-size: 22rpx;
color: #07c160;
line-height: 1.6;
}
/* 绑定信息 */
.bind-info {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.info-card {
background: white;
border-radius: 16rpx;
padding: 40rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-size: 28rpx;
color: #666;
}
.info-value {
font-size: 28rpx;
color: #1a1a1a;
font-weight: 500;
}
.unbind-btn {
width: 100%;
height: 96rpx;
background: white;
color: #07c160;
border: 2rpx solid #07c160;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
}
.unbind-btn::after {
border: none;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "我的",
"navigationBarBackgroundColor": "#07c160",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,187 @@
// pages/profile/profile.ts
import { EmployeeService } from '../../services/employee';
Page({
data: {
username: '管理员',
enterpriseName: '',
avatar: '',
publishedCount: 0,
pendingCount: 0, // 待审核
rejectedCount: 0, // 已驳回
userInfo: null as any,
isBoundXHS: false,
xhsAccount: ''
},
onLoad() {
// 检查登录状态
const token = wx.getStorageSync('token');
if (!token) {
wx.redirectTo({
url: '/pages/login/login'
});
return;
}
// 加载用户信息
this.loadUserInfo();
// 获取数据概览
this.loadStatistics();
},
onShow() {
// 设置 tabBar 选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({
active: 2
});
}
// 每次显示页面时刷新数据
this.loadUserInfo();
this.loadStatistics();
},
// 加载用户信息
async loadUserInfo() {
try {
const response = await EmployeeService.getProfile();
if (response.code === 200 && response.data) {
const userInfo = response.data;
this.setData({
username: userInfo.name,
enterpriseName: userInfo.enterprise_name || '',
avatar: userInfo.avatar || '',
userInfo,
isBoundXHS: userInfo.is_bound_xhs === 1,
xhsAccount: userInfo.xhs_account || ''
});
}
} catch (error) {
console.error('加载用户信息失败:', error);
// 使用本地存储的备用数据
const username = wx.getStorageSync('username');
if (username) {
this.setData({ username });
}
}
},
// 加载数据统计
async loadStatistics() {
try {
// 获取已发布数量从发布记录API获取
const publishResponse = await EmployeeService.getMyPublishRecords(1, 1);
if (publishResponse.code === 200 && publishResponse.data) {
this.setData({
publishedCount: publishResponse.data.total || 0
});
}
// TODO: 后端需要添加待审核和已驳回的API
// 目前使用默认值
this.setData({
pendingCount: 0,
rejectedCount: 0
});
} catch (error) {
console.error('加载数据统计失败:', error);
// 使用默认值
this.setData({
publishedCount: 0,
pendingCount: 0,
rejectedCount: 0
});
}
},
// 返回首页
goToHome() {
wx.redirectTo({
url: '/pages/home/home'
});
},
// 数据统计
goToStats() {
wx.navigateTo({
url: '/pages/profile/stats/stats'
});
},
// 我的文章
goToMyArticles() {
wx.navigateTo({
url: '/pages/profile/my-articles/my-articles'
});
},
// 已发布文章
goToPublished() {
wx.navigateTo({
url: '/pages/profile/published/published'
});
},
// 收藏夹
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({
url: '/pages/profile/feedback/feedback'
});
},
// 关于我们
goToAbout() {
wx.navigateTo({
url: '/pages/profile/about/about'
});
},
// 退出登录
handleLogout() {
wx.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
// 清除本地存储
wx.clearStorageSync();
// 返回登录页
wx.redirectTo({
url: '/pages/login/login'
});
}
}
});
}
});

View File

@@ -0,0 +1,103 @@
<!--pages/profile/profile.wxml-->
<view class="page-wrapper">
<!-- 用户信息区域 -->
<view class="header-section">
<view class="user-card">
<view class="avatar">
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill" />
<text wx:else class="avatar-icon"></text>
</view>
<view class="user-info">
<text class="username">{{username}}</text>
<text wx:if="{{enterpriseName}}" class="enterprise-name">{{enterpriseName}}</text>
<view class="user-badge">
<text class="badge-text">内容管理员</text>
</view>
</view>
<view class="setting-btn">
<text class="setting-icon"></text>
</view>
</view>
</view>
<!-- 数据概览 -->
<view class="data-overview">
<view class="overview-title">数据概览</view>
<view class="stats-grid">
<view class="stat-item" bindtap="goToPublished">
<text class="stat-value">{{publishedCount}}</text>
<text class="stat-label">已发布</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value">{{pendingCount}}</text>
<text class="stat-label">待审核</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value">{{rejectedCount}}</text>
<text class="stat-label">已驳回</text>
</view>
</view>
</view>
<!-- 功能区域 -->
<view class="function-section">
<view class="section-title">常用功能</view>
<view class="function-grid">
<view class="function-item" bindtap="goToStats">
<view class="function-icon icon-chart"></view>
<text class="function-text">数据统计</text>
</view>
<view class="function-item" bindtap="goToMyArticles">
<view class="function-icon icon-article"></view>
<text class="function-text">我的文章</text>
</view>
<view class="function-item" bindtap="goToFavorites">
<view class="function-icon icon-favorite"></view>
<text class="function-text">收藏夹</text>
</view>
<view class="function-item" bindtap="goToNotifications">
<view class="function-icon icon-notification"></view>
<text class="function-text">消息通知</text>
</view>
</view>
</view>
<!-- 设置列表 -->
<view class="setting-section">
<view class="setting-item" bindtap="goToUserInfo">
<view class="item-left">
<text class="item-icon icon-profile"></text>
<text class="item-text">个人资料</text>
</view>
<text class="item-arrow"></text>
</view>
<view class="setting-item" bindtap="goToSocialBinding">
<view class="item-left">
<text class="item-icon icon-link"></text>
<text class="item-text">社交账号绑定</text>
</view>
<text class="item-arrow"></text>
</view>
<view class="setting-item" bindtap="goToFeedback">
<view class="item-left">
<text class="item-icon icon-feedback"></text>
<text class="item-text">意见反馈</text>
</view>
<text class="item-arrow"></text>
</view>
<view class="setting-item" bindtap="goToAbout">
<view class="item-left">
<text class="item-icon icon-about"></text>
<text class="item-text">关于我们</text>
</view>
<text class="item-arrow"></text>
</view>
</view>
<!-- 退出登录 -->
<view class="logout-section">
<button class="logout-btn" bindtap="handleLogout">退出登录</button>
</view>
</view>

View File

@@ -0,0 +1,424 @@
/* pages/profile/profile.wxss */
page {
height: 100%;
width: 100vw;
background: #f8f8f8;
}
.page-wrapper {
min-height: 100%;
width: 100%;
padding-bottom: 200rpx;
box-sizing: border-box;
background: #f8f8f8;
}
/* 用户信息区域 */
.header-section {
width: 100%;
background: #07c160;
padding: 50rpx 30rpx 70rpx;
box-sizing: border-box;
}
.user-card {
display: flex;
align-items: center;
position: relative;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: white;
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;
}
.avatar-text {
font-size: 60rpx;
}
.user-info {
flex: 1;
display: flex;
flex-direction: column;
}
.username {
font-size: 36rpx;
font-weight: bold;
color: white;
margin-bottom: 8rpx;
}
.enterprise-name {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 8rpx;
}
.user-badge {
display: inline-block;
align-self: flex-start;
}
.badge-text {
font-size: 22rpx;
color: #07c160;
background: white;
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.setting-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 30rpx;
}
.setting-icon {
font-size: 36rpx;
}
/* 数据概览 */
.data-overview {
width: 100%;
background: white;
margin: 0;
padding: 30rpx;
box-sizing: border-box;
}
.overview-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
.stats-grid {
display: flex;
align-items: center;
}
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 40rpx;
font-weight: bold;
color: #07c160;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #999;
}
.stat-divider {
width: 1rpx;
height: 60rpx;
background: #f0f0f0;
}
/* 功能区域 */
.function-section {
width: 100%;
background: #f8f8f8;
padding: 20rpx 0;
margin-top: 20rpx;
box-sizing: border-box;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
padding: 0 30rpx;
}
.function-grid {
width: 100%;
background: white;
padding: 30rpx;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24rpx;
box-sizing: border-box;
}
.function-item {
display: flex;
flex-direction: column;
align-items: center;
transition: transform 0.3s;
}
.function-item:active {
transform: scale(0.95);
}
.function-icon {
width: 88rpx;
height: 88rpx;
background: linear-gradient(135deg, #e6f7ed 0%, #d0f0de 100%);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
margin-bottom: 12rpx;
}
.function-text {
font-size: 22rpx;
color: #666;
text-align: center;
}
/* 设置列表*/
.setting-section {
width: 100%;
background: white;
margin-top: 20rpx;
padding-top: 10rpx;
overflow: hidden;
box-sizing: border-box;
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f5f5f5;
transition: background 0.2s;
}
.setting-item:last-child {
border-bottom: none;
}
.setting-item:active {
background: #fafafa;
}
.item-left {
display: flex;
align-items: center;
}
.item-icon {
font-size: 36rpx;
margin-right: 16rpx;
}
.item-text {
font-size: 28rpx;
color: #333;
}
.item-arrow {
font-size: 40rpx;
color: #d0d0d0;
font-weight: 300;
}
/* 退出登录 */
.logout-section {
width: 100%;
background: white;
padding: 0;
margin-top: 20rpx;
margin-bottom: 40rpx;
box-sizing: border-box;
}
.logout-btn {
width: 100%;
background: white;
padding: 32rpx;
text-align: center;
border: none;
font-size: 30rpx;
color: #07c160;
font-weight: 600;
border-radius: 0;
}
.logout-btn::after {
border: none;
}
.logout-btn:active {
opacity: 0.8;
background: #fafafa;
}
/* 底部Tab栏 */
.bottom-tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: white;
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
border-top: 1rpx solid #f0f0f0;
z-index: 1000;
padding-bottom: env(safe-area-inset-bottom);
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 12rpx 0;
transition: all 0.3s ease;
}
.tab-item.active .tab-icon {
transform: scale(1.1);
}
.tab-icon {
font-size: 48rpx;
margin-bottom: 6rpx;
transition: all 0.3s ease;
}
.tab-item.active .tab-icon {
filter: drop-shadow(0 2rpx 8rpx rgba(255, 36, 66, 0.3));
}
.tab-text {
font-size: 22rpx;
color: #999;
font-weight: 500;
transition: all 0.3s ease;
}
.tab-item.active .tab-text {
color: #07c160;
font-weight: 600;
}
/* 图标样式 */
.avatar-icon {
width: 60rpx;
height: 60rpx;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2307c160"><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;
}
.setting-icon {
width: 36rpx;
height: 36rpx;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.icon-chart,
.icon-article,
.icon-favorite,
.icon-notification,
.icon-profile,
.icon-link,
.icon-feedback,
.icon-about {
width: 40rpx;
height: 40rpx;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.icon-chart {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2307c160"><path d="M3.5 18.49l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 16.99z"/></svg>');
}
.icon-article {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2307c160"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>');
}
.icon-favorite {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2307c160"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>');
}
.icon-notification {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2307c160"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/></svg>');
}
.icon-profile {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23666"><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>');
}
.icon-link {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23666"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>');
}
.icon-feedback {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23666"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z"/></svg>');
}
.icon-about {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23666"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>');
}
.icon-home,
.icon-user {
width: 48rpx;
height: 48rpx;
display: inline-block;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.icon-home {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
}
.tab-item.active .icon-home {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2307c160"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
}
.icon-user {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><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>');
}
.tab-item.active .icon-user {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2307c160"><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>');
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "已发布文章",
"navigationBarBackgroundColor": "#07c160",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,187 @@
// pages/profile/published/published.ts
import { EmployeeService, PublishRecord as ApiPublishRecord } from '../../../services/employee';
interface Article {
id: number;
productName: string;
title: string;
content: string;
tags: string[];
images: Array<{
id: number;
image_url: string;
image_thumb_url: string;
sort_order: number;
keywords_name: string;
}>;
createTime: string;
status: string;
}
Page({
data: {
articles: [] as Article[]
},
onLoad() {
this.loadArticles();
},
onShow() {
this.loadArticles();
},
// 初始化模拟数据
initMockData() {
const existingArticles = wx.getStorageSync('myArticles') || [];
// 如果已经有数据,不重复初始化
if (existingArticles.length > 0) return;
const mockArticles = [
{
id: Date.now() + 1,
productName: '兰蔻小黑瓶精华液',
title: '【种草分享】兰蔻小黑瓶精华液使用体验💕',
content: '姐妹们!今天必须来跟大家分享一下我最近入手的宝藏好物——兰蔻小黑瓶精华液!\n\n✨使用感受\n用了一段时间真的太爱了质感超级好完全超出我的预期。包装也非常精致送人自用都很合适。\n\n🌟推荐理由\n1. 品质优秀,性价比超高\n2. 使用体验一级棒\n3. 颜值在线,拿出来超有面子',
tags: ['种草分享', '好物推荐', '必买清单', '真实测评'],
createTime: new Date(Date.now() - 3600000 * 2).toISOString(), // 2小时前
status: 'published'
},
{
id: Date.now() + 2,
productName: 'SK-II神仙水',
title: '真香警告SK-II神仙水实测分享',
content: '集美们看过来今天给大家带来SK-II神仙水的真实使用感受~\n\n🎯第一印象\n收到货的那一刻就被惊艳到了包装精美细节满满完全是高端货的质感。\n\n💫使用体验\n用了几天下来真的是越用越喜欢质量很好用起来特别顺手完全就是我想要的样子',
tags: ['真实测评', '使用心得', '好物安利', '值得入手'],
createTime: new Date(Date.now() - 86400000).toISOString(), // 1天前
status: 'published'
},
{
id: Date.now() + 3,
productName: 'AirPods Pro 2',
title: '【数码种草】AirPods Pro 2使用一周真实感受',
content: '作为一个数码发烧友终于入手了AirPods Pro 2用了一周来给大家分享一下真实体验。\n\n🎵音质表现\n音质真的提升很明显特别是低音部分听流行音乐和播客都非常棒\n\n🔇降噪效果\n降噪功能强大地铁里基本听不到外界噪音专注力提升100%',
tags: ['数码好物', '音质体验', '降噪耳机'],
createTime: new Date(Date.now() - 172800000).toISOString(), // 2天前
status: 'published'
},
{
id: Date.now() + 4,
productName: 'Dior烈艳蓝金口红',
title: '【美妆必入】Dior烈艳蓝金口红999试色分享',
content: '今天来分享我最近的心头好Dior烈艳蓝金口红999号色真的太美了\n\n💄色号分析\n999是经典的正红色白皮黄皮都能驾驭显白效果一级棒\n\n✨质地感受\n润而不油持久度也很好吃饭喝水后还有淡淡的颜色留存。',
tags: ['美妆种草', '口红推荐', 'Dior蓝金'],
createTime: new Date(Date.now() - 259200000).toISOString(), // 3天前
status: 'published'
},
{
id: Date.now() + 5,
productName: '雅诗兰黛小棕瓶',
content: '雅诗兰黛小棕瓶是我用过最好的精华!修复力强,用了一瓶皮肤状态明显提升。\n\n🌟产品亮点\n含有高浓度修复精华抗氧化效果很棒长期使用能看到细纹改善。\n\n💡使用建议\n建议晚上使用配合按摩手法效果更好第二天起来皮肤非常透亮',
title: '【护肤心得】雅诗兰黛小棕瓶修复精华使用报告',
tags: ['护肤精华', '抗衰老', '修复力'],
createTime: new Date(Date.now() - 432000000).toISOString(), // 5天前
status: 'published'
}
];
wx.setStorageSync('myArticles', mockArticles);
},
// 加载文章列表
async loadArticles() {
try {
// 从后端API获取发布记录
const response = await EmployeeService.getMyPublishRecords(1, 50);
console.log('===== 已发布文章列表响应 =====');
console.log('response:', response);
if (response.code === 200 && response.data) {
const apiArticles = response.data.list.map((record: ApiPublishRecord) => {
console.log('处理记录:', record);
console.log('images:', record.images);
console.log('tags:', record.tags);
return {
id: record.id,
productName: record.product_name || '未知商品',
title: record.title,
content: '', // 列表页不需要显示完整内容
tags: record.tags || [],
images: record.images || [],
createTime: record.publish_time,
status: 'published'
};
});
console.log('转换后的文章列表:', apiArticles);
this.setData({
articles: apiArticles
});
console.log('setData后的articles:', this.data.articles);
return;
}
} catch (error) {
console.error('加载发布记录失败:', error);
}
// 如果API失败尝试使用本地数据
const articles = wx.getStorageSync('myArticles') || [];
// 格式化时间
const formattedArticles = articles.map((item: Article) => {
return {
...item,
createTime: this.formatTime(item.createTime)
};
});
this.setData({
articles: formattedArticles
});
},
// 格式化时间
formatTime(isoString: string): string {
const date = new Date(isoString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
},
// 查看文章详情
viewArticle(e: any) {
const index = e.currentTarget.dataset.index;
const article = this.data.articles[index];
// 跳转到文章详情页
wx.navigateTo({
url: `/pages/profile/article-detail/article-detail?id=${article.id}`
});
},
// 分享功能
onShareAppMessage() {
return {
title: '我的发布记录 - 万花筒AI助手',
path: '/pages/home/home',
imageUrl: ''
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: '万花筒AI助手 - 智能生成种草文案',
imageUrl: ''
};
}
});

View File

@@ -0,0 +1,56 @@
<!--pages/profile/published/published.wxml-->
<view class="page-container">
<!-- 空状态 -->
<view class="empty-state" wx:if="{{articles.length === 0}}">
<view class="empty-icon">📝</view>
<text class="empty-text">还没有发布任何文章</text>
<text class="empty-hint">去首页选择商品发布种草文章吧~</text>
</view>
<!-- 文章列表 -->
<scroll-view class="article-list" scroll-y wx:else>
<view
class="article-item"
wx:for="{{articles}}"
wx:key="id"
bindtap="viewArticle"
data-index="{{index}}"
>
<view class="article-content">
<view class="article-header">
<text class="article-title">{{item.title}}</text>
<text class="article-time">{{item.createTime}}</text>
</view>
<!-- 文章图片 -->
<view class="article-images" wx:if="{{item.images && item.images.length > 0}}">
<image
class="article-image"
wx:for="{{item.images}}"
wx:key="id"
wx:for-item="image"
src="{{image.image_thumb_url}}"
mode="aspectFill"
/>
</view>
<view class="article-footer">
<view class="article-product">
<text class="product-label">商品:</text>
<text class="product-name">{{item.productName}}</text>
</view>
<view class="article-tags" wx:if="{{item.tags && item.tags.length > 0}}">
<text
class="tag-item"
wx:for="{{item.tags}}"
wx:key="index"
wx:for-item="tag"
>
#{{tag}}
</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>

View File

@@ -0,0 +1,146 @@
/* pages/profile/published/published.wxss */
page {
background: #f8f8f8;
height: 100%;
}
.page-container {
height: 100vh;
display: flex;
flex-direction: column;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 60rpx;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
opacity: 0.3;
}
.empty-text {
font-size: 32rpx;
color: #999;
margin-bottom: 16rpx;
}
.empty-hint {
font-size: 26rpx;
color: #ccc;
}
/* 文章列表 */
.article-list {
flex: 1;
padding: 20rpx;
box-sizing: border-box;
overflow-y: auto;
}
.article-item {
background: white;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.article-content {
padding: 30rpx;
}
.article-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
}
.article-title {
flex: 1;
font-size: 32rpx;
color: #1a1a1a;
font-weight: bold;
line-height: 1.4;
margin-right: 20rpx;
}
.article-time {
font-size: 22rpx;
color: #999;
white-space: nowrap;
}
/* 文章图片 */
.article-images {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
margin: 20rpx 0;
}
.article-image {
width: 100%;
height: 200rpx;
border-radius: 8rpx;
background: #f5f5f5;
}
.article-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
margin-bottom: 20rpx;
}
.article-footer {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.article-product {
display: flex;
align-items: center;
}
.product-label {
font-size: 24rpx;
color: #999;
}
.product-name {
font-size: 24rpx;
color: #ff2442;
font-weight: 500;
}
.article-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
align-items: center;
}
.tag-item {
padding: 4rpx 16rpx;
background: linear-gradient(135deg, #fff5f7 0%, #ffe8ec 100%);
border-radius: 16rpx;
font-size: 22rpx;
color: #ff2442;
line-height: 1.5;
box-sizing: border-box;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "社交账号绑定",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,174 @@
// pages/profile/social-binding/social-binding.ts
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 {
const response = await EmployeeService.unbindXHS();
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: ''
});
}
} catch (error) {
console.error('解绑失败:', error);
}
}
}
});
}
});

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "个人资料",
"navigationBarBackgroundColor": "#ff2442",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,26 @@
// pages/profile/user-info/user-info.ts
Page({
data: {
username: '管理员'
},
onLoad() {
const username = wx.getStorageSync('username') || '管理员';
this.setData({ username });
},
handleSave() {
wx.showLoading({
title: '保存中...',
mask: true
});
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: '保存成功',
icon: 'success'
});
}, 1000);
}
});

View File

@@ -0,0 +1,49 @@
<!--pages/profile/user-info/user-info.wxml-->
<view class="page-container">
<view class="info-section">
<view class="info-item">
<view class="item-label">头像</view>
<view class="item-value">
<view class="avatar-preview">
<text class="avatar-icon"></text>
</view>
<text class="item-arrow"></text>
</view>
</view>
<view class="info-item">
<view class="item-label">昵称</view>
<view class="item-value">
<text class="value-text">{{username}}</text>
<text class="item-arrow"></text>
</view>
</view>
<view class="info-item">
<view class="item-label">手机号</view>
<view class="item-value">
<text class="value-text">138****8888</text>
<text class="item-arrow"></text>
</view>
</view>
<view class="info-item">
<view class="item-label">邮箱</view>
<view class="item-value">
<text class="value-text">user@example.com</text>
<text class="item-arrow"></text>
</view>
</view>
<view class="info-item">
<view class="item-label">角色</view>
<view class="item-value">
<text class="value-text role">内容管理员</text>
</view>
</view>
</view>
<view class="save-section">
<button class="save-btn" bindtap="handleSave">保存修改</button>
</view>
</view>

View File

@@ -0,0 +1,99 @@
/* pages/profile/user-info/user-info.wxss */
page {
background: #f8f8f8;
}
.page-container {
min-height: 100vh;
padding-bottom: 120rpx;
}
.info-section {
width: 100%;
background: white;
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;
gap: 16rpx;
}
.value-text {
font-size: 28rpx;
color: #666;
}
.value-text.role {
color: #07c160;
font-weight: 600;
}
.item-arrow {
font-size: 36rpx;
color: #ccc;
}
.avatar-preview {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: linear-gradient(135deg, #fff5f7 0%, #ffe8ec 100%);
display: flex;
align-items: center;
justify-content: center;
}
.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="%23ff2442"><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;
}
.save-section {
width: 100%;
padding: 40rpx 30rpx;
}
.save-btn {
width: 100%;
background: #07c160;
color: white;
border: none;
border-radius: 16rpx;
padding: 32rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 8rpx 24rpx rgba(255, 36, 66, 0.3);
}
.save-btn::after {
border: none;
}
.save-btn:active {
opacity: 0.9;
transform: translateY(2rpx);
}

View File

@@ -0,0 +1,5 @@
{
"navigationBarTitleText": "小红书登录",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}

View File

@@ -0,0 +1,243 @@
// pages/profile/xhs-login/xhs-login.ts
import { EmployeeService } from '../../../services/employee';
Page({
data: {
countryCode: '+86',
phone: '',
code: '',
codeSending: false,
countdown: 0,
agreedToTerms: false,
showAgreementModal: false
},
// 选择国家区号
selectCountryCode() {
const codes = ['+86', '+852', '+853', '+886'];
const names = ['中国大陆', '中国香港', '中国澳门', '中国台湾'];
wx.showActionSheet({
itemList: names.map((name, index) => `${name} ${codes[index]}`),
success: (res) => {
this.setData({
countryCode: codes[res.tapIndex]
});
}
});
},
// 手机号输入
onPhoneInput(e: any) {
this.setData({
phone: e.detail.value
});
},
// 验证码输入
onCodeInput(e: any) {
this.setData({
code: e.detail.value
});
},
// 协议勾选变化
onAgreementChange(e: any) {
this.setData({
agreedToTerms: e.detail.value.length > 0
});
},
// 发送验证码
async sendCode() {
const phone = this.data.phone;
// 验证手机号
if (!phone || phone.length !== 11) {
wx.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
// 手机号格式验证
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(phone)) {
wx.showToast({
title: '手机号格式不正确',
icon: 'none'
});
return;
}
// 防止重复发送
if (this.data.codeSending || this.data.countdown > 0) {
return;
}
// 设置发送状态
this.setData({
codeSending: true
});
try {
// 调用后端API发送验证码
const response = await EmployeeService.sendXHSCode(phone);
if (response.code === 200) {
wx.showToast({
title: '验证码已发送',
icon: 'success'
});
this.startCountdown();
} else {
wx.showToast({
title: response.message || '发送失败',
icon: 'none'
});
this.setData({
codeSending: false
});
}
} catch (error) {
console.error('发送验证码失败:', error);
wx.showToast({
title: '发送失败,请重试',
icon: 'none'
});
this.setData({
codeSending: false
});
}
},
// 倒计时
startCountdown() {
let countdown = 60;
this.setData({
countdown: countdown,
codeSending: true
});
const timer = setInterval(() => {
countdown--;
if (countdown <= 0) {
clearInterval(timer);
this.setData({
countdown: 0,
codeSending: false
});
} else {
this.setData({
countdown: countdown
});
}
}, 1000);
},
// 登录
async login() {
const phone = this.data.phone;
const code = this.data.code;
// 检查是否同意协议
if (!this.data.agreedToTerms) {
this.setData({
showAgreementModal: true
});
return;
}
// 验证手机号
if (!phone || phone.length !== 11) {
wx.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
// 验证验证码
if (!code || code.length !== 6) {
wx.showToast({
title: '请输入6位验证码',
icon: 'none'
});
return;
}
try {
// 调用后端API绑定小红书
const response = await EmployeeService.bindXHS(phone, code);
if (response.code === 200) {
// 绑定成功,保存绑定信息
const bindings = wx.getStorageSync('socialBindings') || {};
bindings.xiaohongshu = {
phone: phone,
bindTime: new Date().getTime(),
xhs_account: (response.data && response.data.xhs_account) || ''
};
wx.setStorageSync('socialBindings', bindings);
wx.showToast({
title: '绑定成功',
icon: 'success',
duration: 1500
});
// 延迟500毫秒后返回让用户看到成功提示
setTimeout(() => {
// 返回上一页(社交账号绑定页)
wx.navigateBack({
delta: 1
});
}, 1500);
}
} catch (error) {
console.error('绑定小红书失败:', error);
}
},
// 关闭协议弹窗
closeAgreementModal() {
this.setData({
showAgreementModal: false
});
},
// 同意并登录
agreeAndLogin() {
this.setData({
agreedToTerms: true,
showAgreementModal: false
});
// 延迟执行登录,让用户看到勾选效果
setTimeout(() => {
this.login();
}, 300);
},
// 阻止事件冒泡
stopPropagation() {},
// 跳转到用户协议
goToUserAgreement(e: any) {
e.stopPropagation();
wx.navigateTo({
url: '/pages/agreement/user-agreement/user-agreement'
});
},
// 跳转到隐私政策
goToPrivacy(e: any) {
e.stopPropagation();
wx.navigateTo({
url: '/pages/agreement/privacy-policy/privacy-policy'
});
}
});

View File

@@ -0,0 +1,90 @@
<!--pages/profile/xhs-login/xhs-login.wxml-->
<view class="page-container">
<!-- 背景动画 -->
<view class="background-gradient"></view>
<!-- 登录卡片 -->
<view class="login-card">
<!-- 平台头部 -->
<view class="platform-header">
<view class="platform-icon"></view>
<view class="platform-text">
<text class="platform-name">小红书账号登录</text>
<text class="platform-tip">使用小红书手机号登录</text>
</view>
</view>
<!-- 登录表单 -->
<view class="login-form">
<!-- 手机号 -->
<view class="form-item">
<view class="phone-input-wrapper">
<view class="country-code" bindtap="selectCountryCode">
<text class="code-text">{{countryCode}}</text>
<text class="arrow-down">▼</text>
</view>
<input
class="phone-input"
type="number"
maxlength="11"
placeholder="请输入小红书手机号"
value="{{phone}}"
bindinput="onPhoneInput"
/>
</view>
</view>
<!-- 验证码 -->
<view class="form-item">
<view class="code-input-wrapper">
<input
class="code-input"
type="number"
maxlength="6"
placeholder="请输入验证码"
value="{{code}}"
bindinput="onCodeInput"
/>
<view
class="send-code-text {{codeSending ? 'disabled' : ''}}"
bindtap="sendCode"
>
{{countdown > 0 ? countdown + 's' : '获取验证码'}}
</view>
</view>
</view>
<!-- 协议勾选 -->
<view class="agreement-row">
<checkbox-group bindchange="onAgreementChange">
<label class="agreement-checkbox">
<checkbox value="agree" checked="{{agreedToTerms}}" color="#07c160"/>
<text class="agreement-text">我已阅读并同意</text>
<text class="agreement-link" bindtap="goToUserAgreement">《用户协议》</text>
<text class="agreement-text">和</text>
<text class="agreement-link" bindtap="goToPrivacy">《隐私政策》</text>
</label>
</checkbox-group>
</view>
<!-- 登录按钮 -->
<button class="login-btn" bindtap="login">
登录
</button>
</view>
</view>
</view>
<!-- 提示弹窗 -->
<view class="modal-mask" wx:if="{{showAgreementModal}}" bindtap="closeAgreementModal">
<view class="modal-dialog" catchtap="stopPropagation">
<view class="modal-title">温馨提示</view>
<view class="modal-content">
请先同意《用户协议》和《隐私政策》后再登录
</view>
<view class="modal-footer">
<button class="modal-btn cancel" bindtap="closeAgreementModal">不同意</button>
<button class="modal-btn confirm" bindtap="agreeAndLogin">同意</button>
</view>
</view>
</view>

View File

@@ -0,0 +1,312 @@
/* pages/profile/xhs-login/xhs-login.wxss */
page {
background: #07c160;
height: 100%;
width: 100%;
}
.page-container {
min-height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
box-sizing: border-box;
position: relative;
}
/* 背景渐变动画 */
.background-gradient {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #07c160;
background-size: 200% 200%;
animation: gradientMove 8s ease infinite;
z-index: 0;
}
@keyframes gradientMove {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 登录卡片 - 毛玻璃效果 */
.login-card {
width: 100%;
max-width: 680rpx;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
-webkit-backdrop-filter: blur(20rpx);
border-radius: 32rpx;
padding: 48rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
position: relative;
z-index: 1;
}
/* 平台头部 */
.platform-header {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 48rpx;
}
.platform-icon {
width: 120rpx;
height: 120rpx;
border-radius: 24rpx;
background: #07c160;
flex-shrink: 0;
box-shadow: 0 4rpx 16rpx rgba(255, 36, 66, 0.3);
}
.platform-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.platform-name {
font-size: 36rpx;
font-weight: bold;
color: #1a1a1a;
}
.platform-tip {
font-size: 26rpx;
color: #999;
}
/* 表单 */
.login-form {
width: 100%;
}
.form-item {
margin-bottom: 32rpx;
}
/* 手机号输入 */
.phone-input-wrapper {
display: flex;
align-items: center;
height: 96rpx;
background: #f8f8f8;
border-radius: 16rpx;
overflow: hidden;
border: 2rpx solid transparent;
transition: all 0.3s ease;
}
.phone-input-wrapper:focus-within {
border-color: #07c160;
background: white;
}
.country-code {
display: flex;
align-items: center;
gap: 8rpx;
padding: 0 24rpx;
height: 100%;
border-right: 1rpx solid #e0e0e0;
flex-shrink: 0;
}
.code-text {
font-size: 28rpx;
color: #1a1a1a;
font-weight: 500;
}
.arrow-down {
font-size: 20rpx;
color: #999;
}
.phone-input {
flex: 1;
height: 100%;
padding: 0 24rpx;
font-size: 28rpx;
color: #1a1a1a;
}
/* 验证码输入 */
.code-input-wrapper {
display: flex;
align-items: center;
height: 96rpx;
background: #f8f8f8;
border-radius: 16rpx;
overflow: hidden;
border: 2rpx solid transparent;
transition: all 0.3s ease;
}
.code-input-wrapper:focus-within {
border-color: #07c160;
background: white;
}
.code-input {
flex: 1;
height: 100%;
padding: 0 24rpx;
font-size: 28rpx;
color: #1a1a1a;
}
.send-code-text {
padding: 0 24rpx;
height: 100%;
display: flex;
align-items: center;
font-size: 26rpx;
color: #07c160;
white-space: nowrap;
flex-shrink: 0;
font-weight: 500;
}
.send-code-text.disabled {
color: #999;
}
/* 协议勾选 */
.agreement-row {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32rpx;
}
.agreement-checkbox {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
gap: 4rpx;
}
.agreement-text {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
.agreement-link {
font-size: 24rpx;
color: #07c160;
line-height: 1.6;
}
/* 登录按钮 */
.login-btn {
width: 100%;
height: 96rpx;
background: #07c160;
color: white;
border: none;
border-radius: 48rpx;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
box-shadow: 0 4rpx 16rpx rgba(255, 36, 66, 0.3);
transition: all 0.3s ease;
}
.login-btn:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(255, 36, 66, 0.3);
}
.login-btn::after {
border: none;
}
/* 提示弹窗 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal-dialog {
width: 560rpx;
background: white;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
}
.modal-title {
padding: 40rpx 32rpx 24rpx;
font-size: 32rpx;
font-weight: bold;
color: #1a1a1a;
text-align: center;
}
.modal-content {
padding: 0 32rpx 40rpx;
font-size: 28rpx;
color: #666;
text-align: center;
line-height: 1.6;
}
.modal-footer {
display: flex;
border-top: 1rpx solid #e0e0e0;
}
.modal-btn {
flex: 1;
height: 96rpx;
background: white;
border: none;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.modal-btn::after {
border: none;
}
.modal-btn.cancel {
color: #666;
border-right: 1rpx solid #e0e0e0;
}
.modal-btn.confirm {
color: #07c160;
font-weight: 600;
}

View File

@@ -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>

Some files were not shown because too many files have changed in this diff Show More