commit
This commit is contained in:
@@ -1,13 +1,10 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/home/home",
|
||||
"pages/article-generate/article-generate",
|
||||
"pages/login/login",
|
||||
"pages/login/phone-login",
|
||||
"pages/articles/articles",
|
||||
"pages/article-detail/article-detail",
|
||||
"pages/profile/profile",
|
||||
"pages/profile/user-info/user-info",
|
||||
"pages/profile/social-binding/social-binding",
|
||||
"pages/profile/platform-bind/platform-bind",
|
||||
"pages/profile/xhs-login/xhs-login",
|
||||
"pages/profile/published/published",
|
||||
@@ -15,9 +12,7 @@
|
||||
"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"
|
||||
"pages/agreement/privacy-policy/privacy-policy"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTextStyle": "white",
|
||||
@@ -25,32 +20,6 @@
|
||||
"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"
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { API } from './config/api';
|
||||
|
||||
interface IAppInstance {
|
||||
globalData: Record<string, any>;
|
||||
showEnvironmentTip: () => void;
|
||||
logEnvironmentInfo: () => void;
|
||||
}
|
||||
|
||||
App<IAppOption & IAppInstance>({
|
||||
@@ -14,8 +14,8 @@ App<IAppOption & IAppInstance>({
|
||||
logs.unshift(Date.now())
|
||||
wx.setStorageSync('logs', logs)
|
||||
|
||||
// 显示当前环境提示
|
||||
this.showEnvironmentTip();
|
||||
// 输出环境信息到控制台(不显示弹窗)
|
||||
this.logEnvironmentInfo();
|
||||
|
||||
// 登录
|
||||
wx.login({
|
||||
@@ -26,27 +26,23 @@ App<IAppOption & IAppInstance>({
|
||||
})
|
||||
},
|
||||
|
||||
// 显示环境提示
|
||||
showEnvironmentTip() {
|
||||
// 输出环境信息到控制台
|
||||
logEnvironmentInfo() {
|
||||
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 = '未知环境';
|
||||
@@ -55,17 +51,6 @@ App<IAppOption & IAppInstance>({
|
||||
// 输出到控制台
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ interface EnvConfig {
|
||||
const API_CONFIG: Record<EnvType, EnvConfig> = {
|
||||
// 开发环境 - 本地开发
|
||||
dev: {
|
||||
baseURL: 'http://192.168.17.127:8080', // 本地Go服务
|
||||
pythonURL: 'http://192.168.17.127:8000', // 本地Python服务
|
||||
baseURL: 'http://localhost:8080', // 本地Go服务
|
||||
pythonURL: 'http://localhost:8000', // 本地Python服务
|
||||
timeout: 90000
|
||||
},
|
||||
|
||||
@@ -77,6 +77,13 @@ function detectEnvironment(): EnvType {
|
||||
const currentEnv: EnvType = detectEnvironment();
|
||||
const currentConfig = (): EnvConfig => API_CONFIG[currentEnv];
|
||||
|
||||
// 导出环境检测相关函数和变量
|
||||
export const getCurrentEnv = (): EnvType => currentEnv;
|
||||
export const isDevelopment = (): boolean => currentEnv === 'dev';
|
||||
export const isDevOrTrial = (): boolean => currentEnv === 'dev' || currentEnv === 'test'; // 开发版或体验版
|
||||
export { detectEnvironment };
|
||||
export type { EnvType };
|
||||
|
||||
// 输出环境信息
|
||||
console.log(`[API Config] 当前环境: ${currentEnv}`);
|
||||
console.log(`[API Config] 主服务: ${currentConfig().baseURL}`);
|
||||
@@ -99,14 +106,17 @@ export const API = {
|
||||
|
||||
// 登录接口
|
||||
auth: {
|
||||
wechatLogin: '/api/login/wechat', // 微信登录
|
||||
phoneLogin: '/api/login/phone' // 手机号登录
|
||||
wechatLogin: '/api/login/wechat', // 微信登录
|
||||
phoneLogin: '/api/login/phone', // 手机号登录
|
||||
phonePasswordLogin: '/api/login/phone-password', // 手机号密码登录
|
||||
xhsPhoneCodeLogin: '/api/login/xhs-phone-code' // 小红书手机号验证码登录
|
||||
},
|
||||
|
||||
// 员工端接口
|
||||
employee: {
|
||||
profile: '/api/employee/profile', // 获取个人信息
|
||||
bindXHS: '/api/employee/bind-xhs', // 绑定小红书
|
||||
bindXHSStatus: '/api/employee/bind-xhs-status', // 获取绑定状态
|
||||
unbindXHS: '/api/employee/unbind-xhs', // 解绑小红书
|
||||
availableCopies: '/api/employee/available-copies', // 获取可领取文案
|
||||
claimCopy: '/api/employee/claim-copy', // 领取文案
|
||||
@@ -122,7 +132,8 @@ export const API = {
|
||||
|
||||
// 小红书相关接口
|
||||
xhs: {
|
||||
sendCode: '/api/xhs/send-code' // 发送小红书验证码
|
||||
sendCode: '/api/xhs/send-code', // 发送小红书验证码
|
||||
sendVerificationCode: '/api/xhs/send-verification-code' // 发送验证码(新接口)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
5
miniprogram/miniprogram/images/default-avatar.svg
Normal file
5
miniprogram/miniprogram/images/default-avatar.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="50" cy="50" r="50" fill="#E5E5E5"/>
|
||||
<circle cx="50" cy="35" r="15" fill="#999999"/>
|
||||
<path d="M25 75C25 65 35 58 50 58C65 58 75 65 75 75" stroke="#999999" stroke-width="8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 321 B |
BIN
miniprogram/miniprogram/images/logo.png
Normal file
BIN
miniprogram/miniprogram/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
3
miniprogram/miniprogram/images/phone-icon.svg
Normal file
3
miniprogram/miniprogram/images/phone-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.25 3C11.5931 3 10.25 4.34315 10.25 6V42C10.25 43.6569 11.5931 45 13.25 45H34.75C36.4069 45 37.75 43.6569 37.75 42V6C37.75 4.34315 36.4069 3 34.75 3H13.25ZM13.25 6H34.75V42H13.25V6ZM24 38.5C22.6193 38.5 21.5 39.6193 21.5 41C21.5 42.3807 22.6193 43.5 24 43.5C25.3807 43.5 26.5 42.3807 26.5 41C26.5 39.6193 25.3807 38.5 24 38.5Z" fill="#333333"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 462 B |
3
miniprogram/miniprogram/images/wechat-icon.svg
Normal file
3
miniprogram/miniprogram/images/wechat-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.5 12.5C11.15 12.5 6 17.1 6 22.8C6 26.1 7.65 29 10.2 31L9 35.5L13.5 33.2C14.8 33.7 16.1 34 17.5 34C23.85 34 29 29.4 29 23.7C29 18.1 23.85 12.5 17.5 12.5ZM14.5 26C13.12 26 12 24.88 12 23.5C12 22.12 13.12 21 14.5 21C15.88 21 17 22.12 17 23.5C17 24.88 15.88 26 14.5 26ZM20.5 26C19.12 26 18 24.88 18 23.5C18 22.12 19.12 21 20.5 21C21.88 21 23 22.12 23 23.5C23 24.88 21.88 26 20.5 26ZM31.5 22C31.2 22 30.9 22 30.6 22.1C31.5 19.9 31.8 17.5 31.3 15.2C37.2 16.6 41.5 21.2 41.5 26.7C41.5 29.3 40.2 31.7 38.1 33.4L39 37.5L35.1 35.6C34 36.05 32.25 36.4 31.5 36.4C26.15 36.4 21.7 32.5 21.7 27.7C21.7 24.4 24 21.6 27.5 20.1C28.7 20.9 30.1 21.4 31.5 21.4V22ZM27.5 30C26.12 30 25 28.88 25 27.5C25 26.12 26.12 25 27.5 25C28.88 25 30 26.12 30 27.5C30 28.88 28.88 30 27.5 30ZM35.5 30C34.12 30 33 28.88 33 27.5C33 26.12 34.12 25 35.5 25C36.88 25 38 26.12 38 27.5C38 28.88 36.88 30 35.5 30Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1004 B |
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"backgroundTextStyle": "dark",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// pages/articles/articles.ts
|
||||
import { formatDate, getStatusInfo, getChannelInfo, getCoverColor, getCoverIcon } from '../../utils/util'
|
||||
import { formatDate, getStatusInfo, getChannelInfo, getCoverColor, getCoverIcon, getImageUrl } from '../../utils/util'
|
||||
import { EmployeeService } from '../../services/employee'
|
||||
|
||||
Page({
|
||||
@@ -13,7 +13,13 @@ Page({
|
||||
loading: false,
|
||||
claiming: false, // 领取中
|
||||
publishing: false, // 发布中
|
||||
rejecting: false // 拒绝中
|
||||
rejecting: false, // 拒绝中
|
||||
hasLoaded: false, // 是否已加载过数据
|
||||
// 图片拖拽状态
|
||||
draggingId: null as number | null, // 当前拖拽的图片ID
|
||||
dragStartX: 0, // 开始拖拽的X坐标
|
||||
dragStartIndex: -1, // 开始拖拽的索引
|
||||
isDragging: false // 是否正在拖拽
|
||||
},
|
||||
|
||||
onLoad(options: any) {
|
||||
@@ -38,7 +44,7 @@ Page({
|
||||
}
|
||||
|
||||
// 检查小红书绑定状态
|
||||
this.checkXHSBinding().then(isBound => {
|
||||
this.checkXHSBinding().then((isBound: boolean) => {
|
||||
if (!isBound) {
|
||||
wx.showModal({
|
||||
title: '未绑定小红书',
|
||||
@@ -83,14 +89,19 @@ Page({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 每次显示页面时刷新数据
|
||||
if (this.data.productId) {
|
||||
// 仅在未加载过数据时加载(例如从其他页面返回且数据已存在)
|
||||
if (this.data.productId && !this.data.hasLoaded) {
|
||||
this.loadCopies();
|
||||
}
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadCopies(true);
|
||||
},
|
||||
|
||||
// 加载文案列表
|
||||
async loadCopies() {
|
||||
async loadCopies(isPullRefresh: boolean = false) {
|
||||
const productId = this.data.productId;
|
||||
|
||||
if (!productId) {
|
||||
@@ -98,6 +109,9 @@ Page({
|
||||
title: '请先选择产品',
|
||||
icon: 'none'
|
||||
});
|
||||
if (isPullRefresh) {
|
||||
wx.stopPullDownRefresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,22 +135,49 @@ Page({
|
||||
console.log('标签数量:', (copies[0].tags && copies[0].tags.length) || 0);
|
||||
}
|
||||
|
||||
// 处理图片URL:使用统一工具函数处理
|
||||
const processedCopies = copies.map((copy: any) => {
|
||||
if (copy.images && Array.isArray(copy.images)) {
|
||||
copy.images = copy.images.map((img: any) => {
|
||||
return {
|
||||
...img,
|
||||
image_url: getImageUrl(img.image_url),
|
||||
image_thumb_url: getImageUrl(img.image_thumb_url || img.image_url)
|
||||
};
|
||||
});
|
||||
}
|
||||
return copy;
|
||||
});
|
||||
|
||||
// 确保 currentCopy 有值
|
||||
const firstCopy = processedCopies.length > 0 ? processedCopies[0] : null;
|
||||
|
||||
console.log('即将设置 currentCopy:', firstCopy);
|
||||
|
||||
this.setData({
|
||||
allCopies: copies,
|
||||
allCopies: processedCopies,
|
||||
productName: product.name || '',
|
||||
productImage: product.image || '',
|
||||
currentIndex: 0,
|
||||
currentCopy: copies.length > 0 ? copies[0] : null,
|
||||
loading: false
|
||||
currentCopy: firstCopy,
|
||||
loading: false,
|
||||
hasLoaded: true // 标记已加载
|
||||
});
|
||||
|
||||
console.log('setData后的 currentCopy:', this.data.currentCopy);
|
||||
console.log('allCopies 长度:', this.data.allCopies.length);
|
||||
|
||||
if (copies.length === 0) {
|
||||
wx.showToast({
|
||||
title: '暂无可领取文案',
|
||||
icon: 'none'
|
||||
});
|
||||
} else if (isPullRefresh) {
|
||||
wx.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -146,6 +187,10 @@ Page({
|
||||
title: '加载文案失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
if (isPullRefresh) {
|
||||
wx.stopPullDownRefresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -163,6 +208,15 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果只有一篇文案,提示用户
|
||||
if (allCopies.length === 1) {
|
||||
wx.showToast({
|
||||
title: '已经是唯一的文案了',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载动画
|
||||
wx.showLoading({
|
||||
title: '加载中...',
|
||||
@@ -173,18 +227,24 @@ Page({
|
||||
setTimeout(() => {
|
||||
// 切换到下一个文案
|
||||
const nextIndex = (currentIndex + 1) % allCopies.length;
|
||||
const nextCopy = allCopies[nextIndex];
|
||||
|
||||
this.setData({
|
||||
currentIndex: nextIndex,
|
||||
currentCopy: allCopies[nextIndex]
|
||||
});
|
||||
// 确保 nextCopy 存在且有效
|
||||
if (nextCopy) {
|
||||
this.setData({
|
||||
currentIndex: nextIndex,
|
||||
currentCopy: nextCopy
|
||||
});
|
||||
} else {
|
||||
console.error('换一换失败: nextCopy 为空', { nextIndex, allCopies });
|
||||
}
|
||||
|
||||
// 隐藏加载动画
|
||||
wx.hideLoading();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
// 一键发布(先领取,再发布)
|
||||
// 一键发布(先检查绑定状态,再领取,再发布)
|
||||
async publishArticle() {
|
||||
if (!this.data.currentCopy) {
|
||||
wx.showToast({
|
||||
@@ -194,6 +254,54 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证内容是否为空
|
||||
const { currentCopy } = this.data;
|
||||
const title = (currentCopy.title || '').trim();
|
||||
const content = (currentCopy.content || '').trim();
|
||||
const images = currentCopy.images || [];
|
||||
|
||||
if (!title) {
|
||||
wx.showToast({
|
||||
title: '请输入标题',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
wx.showToast({
|
||||
title: '请输入文案内容',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (images.length === 0) {
|
||||
wx.showToast({
|
||||
title: '请至少添加一张图片',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 发布前检查小红书绑定状态
|
||||
const isBound = await this.checkXHSBinding();
|
||||
if (!isBound) {
|
||||
wx.showModal({
|
||||
title: '未绑定小红书',
|
||||
content: '发布内容前需要先绑定小红书账号',
|
||||
confirmText: '去绑定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/profile/platform-bind/platform-bind'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
wx.showModal({
|
||||
title: '确认发布',
|
||||
content: '确定要领取并发布这篇文案吗?',
|
||||
@@ -212,9 +320,23 @@ Page({
|
||||
this.setData({ claiming: true });
|
||||
|
||||
try {
|
||||
// 1. 先领取文案
|
||||
const { currentCopy } = this.data;
|
||||
|
||||
// 1. 先保存修改的内容
|
||||
try {
|
||||
await EmployeeService.updateArticleContent(currentCopy.id, {
|
||||
title: currentCopy.title,
|
||||
content: currentCopy.content
|
||||
});
|
||||
console.log('文案内容已保存');
|
||||
} catch (error) {
|
||||
console.error('保存文案失败:', error);
|
||||
// 继续发布,不阻断流程
|
||||
}
|
||||
|
||||
// 2. 领取文案
|
||||
const claimResponse = await EmployeeService.claimCopy(
|
||||
this.data.currentCopy.id,
|
||||
currentCopy.id,
|
||||
this.data.productId
|
||||
);
|
||||
|
||||
@@ -227,16 +349,39 @@ Page({
|
||||
|
||||
this.setData({ claiming: false, publishing: true });
|
||||
|
||||
// 2. 再发布
|
||||
// 3. 发布(使用修改后的内容)
|
||||
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,
|
||||
copy_id: currentCopy.id,
|
||||
title: currentCopy.title, // 使用修改后的标题
|
||||
content: currentCopy.content, // 使用修改后的内容
|
||||
publish_link: '',
|
||||
xhs_note_id: ''
|
||||
});
|
||||
|
||||
if (publishResponse.code === 200) {
|
||||
// 发布成功后,同步更新发布记录的完整内容(标题、内容、图片、标签)
|
||||
try {
|
||||
const recordId = publishResponse.data && publishResponse.data.record_id;
|
||||
if (recordId) {
|
||||
const images = (currentCopy.images || []).map((img: any, index: number) => ({
|
||||
image_url: img.image_url,
|
||||
image_thumb_url: img.image_thumb_url || img.image_url,
|
||||
sort_order: img.sort_order || index + 1,
|
||||
keywords_name: img.keywords_name || '产品图片'
|
||||
}));
|
||||
|
||||
await EmployeeService.updatePublishRecord(recordId, {
|
||||
title: currentCopy.title,
|
||||
content: currentCopy.content,
|
||||
images,
|
||||
tags: currentCopy.tags || []
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('同步发布记录失败:', err);
|
||||
// 不阻断发布成功,只记录错误
|
||||
}
|
||||
|
||||
wx.showToast({
|
||||
title: '发布成功',
|
||||
icon: 'success',
|
||||
@@ -372,5 +517,272 @@ Page({
|
||||
title: `${this.data.productName} - 精彩种草文案`,
|
||||
imageUrl: this.data.productImage || ''
|
||||
};
|
||||
},
|
||||
|
||||
// 修改标题
|
||||
onTitleInput(e: any) {
|
||||
const { currentCopy, currentIndex, allCopies } = this.data;
|
||||
// 直接更新当前文案的标题
|
||||
currentCopy.title = e.detail.value;
|
||||
allCopies[currentIndex] = currentCopy;
|
||||
this.setData({
|
||||
currentCopy,
|
||||
allCopies
|
||||
});
|
||||
},
|
||||
|
||||
// 修改内容
|
||||
onContentInput(e: any) {
|
||||
const { currentCopy, currentIndex, allCopies } = this.data;
|
||||
// 直接更新当前文案的内容
|
||||
currentCopy.content = e.detail.value;
|
||||
allCopies[currentIndex] = currentCopy;
|
||||
this.setData({
|
||||
currentCopy,
|
||||
allCopies
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 图片管理功能 ==========
|
||||
|
||||
// 选择并上传图片
|
||||
async chooseAndUploadImage() {
|
||||
console.log('点击添加图片按钮');
|
||||
const { currentCopy, currentIndex, allCopies } = this.data;
|
||||
|
||||
if (!currentCopy) {
|
||||
wx.showToast({
|
||||
title: '请先选择文案',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('开始选择图片...');
|
||||
// 1. 选择图片
|
||||
const res = await wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
sizeType: ['compressed']
|
||||
}) as any;
|
||||
|
||||
console.log('选择图片结果:', res);
|
||||
|
||||
if (!res.tempFiles || res.tempFiles.length === 0) {
|
||||
console.error('未选择图片');
|
||||
return;
|
||||
}
|
||||
|
||||
const tempFilePath = res.tempFiles[0].tempFilePath;
|
||||
console.log('临时文件路径:', tempFilePath);
|
||||
|
||||
wx.showLoading({ title: '上传中...', mask: true });
|
||||
|
||||
// 2. 上传到服务器(仅上传文件,不修改文案数据)
|
||||
console.log('开始上传图片...');
|
||||
const uploadRes = await EmployeeService.uploadImage(tempFilePath);
|
||||
console.log('上传响应:', uploadRes);
|
||||
|
||||
if (uploadRes.code === 200 && uploadRes.data) {
|
||||
// 3. 仅在本地更新当前文案的图片列表
|
||||
const images = currentCopy.images ? [...currentCopy.images] : [];
|
||||
const newImage = {
|
||||
id: Date.now(), // 本地唯一ID
|
||||
image_url: getImageUrl(uploadRes.data.image_url),
|
||||
image_thumb_url: getImageUrl(uploadRes.data.image_thumb_url || uploadRes.data.image_url),
|
||||
sort_order: images.length + 1,
|
||||
keywords_name: '产品图片'
|
||||
} as any;
|
||||
images.push(newImage);
|
||||
currentCopy.images = images;
|
||||
allCopies[currentIndex] = currentCopy;
|
||||
this.setData({
|
||||
currentCopy,
|
||||
allCopies
|
||||
});
|
||||
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '添加成功', icon: 'success' });
|
||||
} else {
|
||||
throw new Error(uploadRes.message || '上传失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('添加图片失败:', error);
|
||||
wx.hideLoading();
|
||||
|
||||
// 处理用户取消选择的情况
|
||||
if (error.errMsg && error.errMsg.includes('cancel')) {
|
||||
console.log('用户取消选择图片');
|
||||
return;
|
||||
}
|
||||
|
||||
wx.showToast({
|
||||
title: error.message || error.errMsg || '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(e: any) {
|
||||
const { url } = e.currentTarget.dataset;
|
||||
const { currentCopy } = this.data;
|
||||
|
||||
if (!currentCopy || !currentCopy.images) return;
|
||||
|
||||
const urls = currentCopy.images.map((img: any) => img.image_url);
|
||||
|
||||
wx.previewImage({
|
||||
current: url,
|
||||
urls: urls
|
||||
});
|
||||
},
|
||||
|
||||
// 删除图片(仅本地修改,未发布前不更新后端)
|
||||
async deleteImage(e: any) {
|
||||
const { id } = e.currentTarget.dataset;
|
||||
const { currentCopy, currentIndex, allCopies } = this.data;
|
||||
|
||||
if (!currentCopy || !currentCopy.images) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是最后一张
|
||||
if (currentCopy.images.length <= 1) {
|
||||
wx.showToast({
|
||||
title: '至少需要保留一张图片',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await wx.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这张图片吗?'
|
||||
});
|
||||
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const imageId = Number(id);
|
||||
let images = currentCopy.images.filter((img: any) => Number(img.id) !== imageId);
|
||||
|
||||
if (images.length === 0) {
|
||||
wx.showToast({
|
||||
title: '至少需要保留一张图片',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新计算排序
|
||||
images = images.map((img: any, index: number) => ({
|
||||
...img,
|
||||
sort_order: index + 1
|
||||
}));
|
||||
|
||||
currentCopy.images = images;
|
||||
allCopies[currentIndex] = currentCopy;
|
||||
this.setData({
|
||||
currentCopy,
|
||||
allCopies
|
||||
});
|
||||
} catch (error: any) {
|
||||
wx.showToast({ title: error.message || '删除失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 图片拖拽功能 ==========
|
||||
|
||||
// 长按图片开始拖拽
|
||||
onImageLongPress(e: any) {
|
||||
const { id, index } = e.currentTarget.dataset;
|
||||
console.log('长按图片:', id, index);
|
||||
|
||||
// 振动反馈
|
||||
wx.vibrateShort({ type: 'medium' });
|
||||
|
||||
this.setData({
|
||||
draggingId: Number(id),
|
||||
dragStartIndex: index,
|
||||
isDragging: true
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '已进入拖拽模式',
|
||||
icon: 'none',
|
||||
duration: 1000
|
||||
});
|
||||
},
|
||||
|
||||
// 触摸开始
|
||||
onImageTouchStart(e: any) {
|
||||
if (!this.data.isDragging) return;
|
||||
this.setData({
|
||||
dragStartX: e.touches[0].pageX
|
||||
});
|
||||
},
|
||||
|
||||
// 触摸移动
|
||||
onImageTouchMove(e: any) {
|
||||
if (!this.data.isDragging || !this.data.draggingId) return;
|
||||
|
||||
const { dragStartX, dragStartIndex, currentCopy, currentIndex, allCopies } = this.data;
|
||||
const currentX = e.touches[0].pageX;
|
||||
const deltaX = currentX - dragStartX;
|
||||
|
||||
// 每移动176rpx(160rpx图片 + 16rpx间隙)切换一个位置
|
||||
const itemWidth = 176 * (wx.getSystemInfoSync().windowWidth / 750); // rpx转px
|
||||
const moveSteps = Math.round(deltaX / itemWidth);
|
||||
|
||||
if (moveSteps !== 0) {
|
||||
const newIndex = dragStartIndex + moveSteps;
|
||||
|
||||
if (newIndex >= 0 && newIndex < currentCopy.images.length) {
|
||||
// 交换图片位置
|
||||
const images = [...currentCopy.images];
|
||||
const draggedItem = images[dragStartIndex];
|
||||
images.splice(dragStartIndex, 1);
|
||||
images.splice(newIndex, 0, draggedItem);
|
||||
|
||||
// 更新sort_order
|
||||
images.forEach((img, idx) => {
|
||||
img.sort_order = idx + 1;
|
||||
});
|
||||
|
||||
currentCopy.images = images;
|
||||
allCopies[currentIndex] = currentCopy;
|
||||
|
||||
this.setData({
|
||||
currentCopy,
|
||||
allCopies,
|
||||
dragStartIndex: newIndex,
|
||||
dragStartX: currentX
|
||||
});
|
||||
|
||||
// 振动反馈
|
||||
wx.vibrateShort({ type: 'light' });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 触摸结束
|
||||
onImageTouchEnd(e: any) {
|
||||
if (!this.data.isDragging) return;
|
||||
|
||||
this.setData({
|
||||
draggingId: null,
|
||||
dragStartX: 0,
|
||||
dragStartIndex: -1,
|
||||
isDragging: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '图片顺序已调整',
|
||||
icon: 'success',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,10 +3,40 @@
|
||||
<!-- 文案内容 -->
|
||||
<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 class="article-images">
|
||||
<view class="image-gallery">
|
||||
<!-- 已有图片 -->
|
||||
<view
|
||||
class="image-item {{draggingId === item.id ? 'dragging' : ''}}"
|
||||
wx:for="{{currentCopy.images}}"
|
||||
wx:key="id"
|
||||
data-id="{{item.id}}"
|
||||
data-index="{{index}}"
|
||||
bindlongpress="onImageLongPress"
|
||||
bindtouchstart="onImageTouchStart"
|
||||
bindtouchmove="onImageTouchMove"
|
||||
bindtouchend="onImageTouchEnd"
|
||||
style="{{draggingId === item.id ? 'opacity: 0.5; transform: scale(1.05);' : ''}}">
|
||||
<image
|
||||
class="gallery-image"
|
||||
src="{{item.image_url}}"
|
||||
mode="aspectFill"
|
||||
bindtap="previewImage"
|
||||
data-url="{{item.image_url}}"
|
||||
/>
|
||||
<view class="delete-btn" catchtap="deleteImage" data-id="{{item.id}}">
|
||||
<!-- 绿色圆形删除按钮 -->
|
||||
<view class="delete-icon">
|
||||
<view class="delete-icon-line line-1"></view>
|
||||
<view class="delete-icon-line line-2"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 添加按钮 -->
|
||||
<view class="image-item add-btn" bindtap="chooseAndUploadImage">
|
||||
<text class="add-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -18,22 +48,27 @@
|
||||
</view>
|
||||
|
||||
<view class="article-header">
|
||||
<text class="article-title">{{currentCopy.title}}</text>
|
||||
<!-- 标题始终可编辑 -->
|
||||
<input
|
||||
class="article-title-input"
|
||||
placeholder="请输入标题(最多20字)"
|
||||
value="{{currentCopy.title}}"
|
||||
bindinput="onTitleInput"
|
||||
maxlength="20"
|
||||
confirm-type="done"
|
||||
/>
|
||||
</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>
|
||||
<!-- 内容始终可编辑 -->
|
||||
<textarea
|
||||
class="content-textarea"
|
||||
placeholder="请输入内容(最多1000字)"
|
||||
value="{{currentCopy.content}}"
|
||||
bindinput="onContentInput"
|
||||
maxlength="1000"
|
||||
show-confirm-bar="{{false}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -55,22 +90,14 @@
|
||||
<button
|
||||
class="action-btn refresh"
|
||||
bindtap="changeArticle"
|
||||
disabled="{{claiming || publishing || rejecting}}"
|
||||
disabled="{{claiming || publishing}}"
|
||||
>
|
||||
<text class="btn-text">换一换</text>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn reject"
|
||||
bindtap="rejectArticle"
|
||||
disabled="{{claiming || publishing || rejecting}}"
|
||||
loading="{{rejecting}}"
|
||||
>
|
||||
<text class="btn-text">{{rejecting ? '拒绝中...' : '拒绝'}}</text>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn primary"
|
||||
bindtap="publishArticle"
|
||||
disabled="{{claiming || publishing || rejecting}}"
|
||||
disabled="{{claiming || publishing}}"
|
||||
loading="{{claiming || publishing}}"
|
||||
>
|
||||
<text class="btn-text">{{claiming ? '领取中...' : (publishing ? '发布中...' : '一键发布')}}</text>
|
||||
|
||||
@@ -9,7 +9,7 @@ page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(136rpx + env(safe-area-inset-bottom));
|
||||
padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 文案容器 */
|
||||
@@ -22,36 +22,118 @@ page {
|
||||
|
||||
.article-wrapper {
|
||||
padding: 30rpx;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 文章图片 - 3列网格布局 */
|
||||
/* 文章图片画廊 - 水平滚动 */
|
||||
.article-images {
|
||||
margin-bottom: 24rpx;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.article-images .section-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
overflow-x: auto;
|
||||
padding: 16rpx 0 24rpx;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.image-gallery::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
width: 100%;
|
||||
padding-bottom: 100%; /* 1:1 比例 */
|
||||
position: relative;
|
||||
border-radius: 8rpx;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: #f5f5f5;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.article-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.image-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 拖拽状态 */
|
||||
.image-item.dragging {
|
||||
opacity: 0.6;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.2);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 删除按钮 - 占据右上角圆角区域,透明灰色背景 */
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 0 16rpx 0 16rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
position: relative;
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
}
|
||||
|
||||
.delete-icon-line {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 16rpx;
|
||||
height: 2rpx;
|
||||
background: white;
|
||||
border-radius: 1rpx;
|
||||
}
|
||||
|
||||
.delete-icon-line.line-1 {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.delete-icon-line.line-2 {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
/* 添加按钮 */
|
||||
.image-item.add-btn {
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx dashed #d9d9d9;
|
||||
}
|
||||
|
||||
.add-btn .add-icon {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.article-tags {
|
||||
display: flex;
|
||||
@@ -74,24 +156,61 @@ page {
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
.char-count {
|
||||
position: absolute;
|
||||
right: 16rpx;
|
||||
bottom: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.char-count-warning {
|
||||
color: #ff4d4f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 标题输入框 - 始终可编辑 */
|
||||
.article-title-input {
|
||||
width: 100%;
|
||||
font-size: 36rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
height: 50rpx;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.article-content {
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
/* 内容输入框 - 始终可编辑 */
|
||||
.content-textarea {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.8;
|
||||
white-space: pre-line;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
/* 动态计算高度:100vh - 图片区域 - 标签 - 标题 - 底部按钮栏 - 其他间距 */
|
||||
min-height: calc(100vh - 450rpx);
|
||||
max-height: calc(100vh - 300rpx);
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
@@ -198,9 +317,25 @@ page {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 保存按钮 */
|
||||
.action-btn.save-btn {
|
||||
flex: 0 0 160rpx;
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.save-btn:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.action-btn.save-btn[disabled] {
|
||||
background: #d9d9d9;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 换一换按钮 */
|
||||
.action-btn.refresh {
|
||||
flex: 0 0 220rpx;
|
||||
flex: 1;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
@@ -209,17 +344,6 @@ page {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
/* 拒绝按钮 */
|
||||
.action-btn.reject {
|
||||
flex: 0 0 180rpx;
|
||||
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.reject:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 一键发布按钮 */
|
||||
.action-btn.primary {
|
||||
flex: 1;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "商品选择",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"backgroundTextStyle": "light"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// pages/home/home.ts
|
||||
import { EmployeeService, Product as ApiProduct } from '../../services/employee';
|
||||
import { getImageUrl } from '../../utils/util';
|
||||
|
||||
interface Product {
|
||||
id: number;
|
||||
@@ -27,187 +28,158 @@ Page({
|
||||
hasMore: true,
|
||||
loading: false,
|
||||
page: 1,
|
||||
pageSize: 6
|
||||
pageSize: 6,
|
||||
userAvatar: '/images/default-avatar.svg', // 默认头像
|
||||
avatarX: 9999, // 头像 X 坐标(设置到屏幕外,等待初始化)
|
||||
avatarY: 9999, // 头像 Y 坐标
|
||||
windowWidth: 375, // 窗口宽度
|
||||
windowHeight: 667, // 窗口高度
|
||||
avatarAnimation: false // 是否启用头像动画
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 不在onLoad检查登录,允许用户浏览首页
|
||||
this.loadProducts();
|
||||
this.loadUserAvatar();
|
||||
this.initAvatarPosition();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时重新加载用户头像(处理退出登录后返回的场景)
|
||||
this.loadUserAvatar();
|
||||
},
|
||||
|
||||
// 初始化头像位置(右下角)
|
||||
initAvatarPosition() {
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
const windowWidth = systemInfo.windowWidth;
|
||||
const windowHeight = systemInfo.windowHeight;
|
||||
|
||||
// 尝试从本地存储读取上次位置
|
||||
const savedPosition = wx.getStorageSync('avatarPosition');
|
||||
|
||||
let initX = windowWidth - 100; // 100rpx 头像宽度
|
||||
let initY = windowHeight - 200; // 留出底部按钮空间
|
||||
|
||||
if (savedPosition && savedPosition.x !== undefined && savedPosition.y !== undefined) {
|
||||
// 使用保存的位置
|
||||
initX = savedPosition.x;
|
||||
initY = savedPosition.y;
|
||||
}
|
||||
|
||||
// 同步设置窗口尺寸和位置,确保不触发动画
|
||||
this.setData({
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
avatarX: initX,
|
||||
avatarY: initY,
|
||||
avatarAnimation: false // 明确禁用动画
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
// 重置分页数据,重新加载第一页
|
||||
this.setData({
|
||||
products: [],
|
||||
page: 1,
|
||||
hasMore: true
|
||||
});
|
||||
this.loadProducts(true);
|
||||
},
|
||||
|
||||
// 加载用户头像
|
||||
async loadUserAvatar() {
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) {
|
||||
// 未登录,重置为默认头像
|
||||
this.setData({
|
||||
userAvatar: '/images/default-avatar.svg'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.getProfile();
|
||||
if (response.code === 200 && response.data) {
|
||||
const avatar = getImageUrl(response.data.avatar) || '/images/default-avatar.svg';
|
||||
this.setData({
|
||||
userAvatar: avatar
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
// 获取失败,重置为默认头像
|
||||
this.setData({
|
||||
userAvatar: '/images/default-avatar.svg'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 加载商品列表
|
||||
async loadProducts() {
|
||||
if (this.data.loading || !this.data.hasMore) return;
|
||||
async loadProducts(isPullRefresh: boolean = false) {
|
||||
if (this.data.loading || !this.data.hasMore) {
|
||||
if (isPullRefresh) {
|
||||
wx.stopPullDownRefresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
// 从后端API获取产品列表(公开接口,不需要登录)
|
||||
const response = await EmployeeService.getProducts();
|
||||
// 从后端API获取产品列表(支持分页)
|
||||
const { page, pageSize, products } = this.data;
|
||||
const response = await EmployeeService.getProducts(page, pageSize);
|
||||
|
||||
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
|
||||
}));
|
||||
const apiProducts = response.data.list.map((product: ApiProduct, index: number) => {
|
||||
// 商品名称最多8个字,多了直接截断
|
||||
const truncatedName = product.name.length > 8 ? product.name.substring(0, 8) : product.name;
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
name: truncatedName,
|
||||
price: 0, // 后端暂无价格字段
|
||||
sales: product.available_copies || 0,
|
||||
image: getImageUrl(product.image) || `https://picsum.photos/id/${237 + products.length + 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,
|
||||
products: [...products, ...apiProducts],
|
||||
loading: false,
|
||||
hasMore: false
|
||||
hasMore: !!response.data.has_more,
|
||||
page: page + 1
|
||||
});
|
||||
|
||||
if (isPullRefresh) {
|
||||
wx.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载产品失败:', error);
|
||||
// API失败,使用模拟数据
|
||||
} finally {
|
||||
if (isPullRefresh) {
|
||||
wx.stopPullDownRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果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);
|
||||
// 接口失败或无有效数据时,停止加载,保持现有列表
|
||||
this.setData({
|
||||
loading: false,
|
||||
hasMore: false
|
||||
});
|
||||
},
|
||||
|
||||
// 切换商品选中状态
|
||||
@@ -293,6 +265,76 @@ Page({
|
||||
this.loadProducts();
|
||||
},
|
||||
|
||||
// 跳转到个人中心
|
||||
goToProfile() {
|
||||
// 检查是否登录
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) {
|
||||
wx.showModal({
|
||||
title: '未登录',
|
||||
content: '请先登录后再访问个人中心',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.redirectTo({
|
||||
url: '/pages/login/login'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 已登录,跳转到个人中心页面
|
||||
wx.navigateTo({
|
||||
url: '/pages/profile/profile'
|
||||
});
|
||||
},
|
||||
|
||||
// 头像拖动结束,自动吸附到边缘
|
||||
onAvatarDragEnd(e: any) {
|
||||
const { x, y } = e.detail;
|
||||
const { windowWidth, windowHeight } = this.data;
|
||||
const avatarSize = 100; // 头像尺寸 100rpx
|
||||
const edgeMargin = 10; // 边缘边距
|
||||
|
||||
// 计算头像中心点位置
|
||||
const centerX = x + avatarSize / 2;
|
||||
const centerY = y + avatarSize / 2;
|
||||
|
||||
// 判断离哪个边更近(只吸附到左右两侧)
|
||||
let targetX = x;
|
||||
|
||||
if (centerX < windowWidth / 2) {
|
||||
// 离左边更近,吸附到左侧
|
||||
targetX = edgeMargin;
|
||||
} else {
|
||||
// 离右边更近,吸附到右侧
|
||||
targetX = windowWidth - avatarSize - edgeMargin;
|
||||
}
|
||||
|
||||
// 限制 Y 坐标在合理范围内
|
||||
let targetY = y;
|
||||
const topLimit = 50; // 顶部留出空间
|
||||
const bottomLimit = windowHeight - 150; // 底部留出按钮空间
|
||||
|
||||
if (targetY < topLimit) {
|
||||
targetY = topLimit;
|
||||
} else if (targetY > bottomLimit) {
|
||||
targetY = bottomLimit;
|
||||
}
|
||||
|
||||
// 启用动画并设置新位置
|
||||
this.setData({
|
||||
avatarAnimation: true,
|
||||
avatarX: targetX,
|
||||
avatarY: targetY
|
||||
});
|
||||
|
||||
// 保存头像位置到本地存储
|
||||
wx.setStorageSync('avatarPosition', { x: targetX, y: targetY });
|
||||
},
|
||||
|
||||
// 分享功能
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">选择商品</text>
|
||||
<text class="page-subtitle">运营提供</text>
|
||||
</view>
|
||||
|
||||
<!-- 商品网格列表 -->
|
||||
@@ -22,23 +21,15 @@
|
||||
data-id="{{item.id}}"
|
||||
data-name="{{item.name}}"
|
||||
>
|
||||
<view class="checkbox">
|
||||
<view class="checkbox-icon {{selectedProduct === item.id ? 'checked' : ''}}"></view>
|
||||
</view>
|
||||
<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>
|
||||
<image class="product-image" src="{{item.image}}" mode="aspectFit" />
|
||||
</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>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
@@ -46,9 +37,25 @@
|
||||
<button
|
||||
class="generate-btn"
|
||||
bindtap="goToGenerate"
|
||||
disabled="{{!selectedProduct}}"
|
||||
>
|
||||
去生成内容
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 可拖动的悬浮头像 -->
|
||||
<movable-area class="movable-area">
|
||||
<movable-view
|
||||
class="avatar-btn"
|
||||
direction="all"
|
||||
x="{{avatarX}}"
|
||||
y="{{avatarY}}"
|
||||
animation="{{avatarAnimation}}"
|
||||
damping="50"
|
||||
friction="5"
|
||||
bindtap="goToProfile"
|
||||
bindtouchend="onAvatarDragEnd"
|
||||
>
|
||||
<image class="avatar-image" src="{{userAvatar}}" mode="aspectFill" />
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
</view>
|
||||
|
||||
@@ -9,13 +9,16 @@ page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
padding: 40rpx 30rpx 30rpx;
|
||||
background: white;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -23,95 +26,118 @@ page {
|
||||
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;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* 商品网格布局 */
|
||||
.product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
padding: 20rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between; /* 两端对齐 */
|
||||
padding: 0 12rpx; /* 减小左右padding */
|
||||
padding-bottom: 200rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 商品卡片 */
|
||||
.product-card {
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
border: 2rpx solid #f0f0f0;
|
||||
transition: all 0.3s;
|
||||
border: 2rpx solid #E5E5E5;
|
||||
padding: 0; /* 重置padding,使用绝对定位控制布局 */
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
box-sizing: border-box;
|
||||
width: 356rpx; /* 178px × 2 */
|
||||
height: 436rpx; /* 218px × 2 */
|
||||
margin-bottom: 12rpx; /* 减小底部间距 */
|
||||
}
|
||||
|
||||
.product-card.selected {
|
||||
border-color: #07c160;
|
||||
box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.2);
|
||||
border-color: #07C160;
|
||||
border-width: 4rpx;
|
||||
}
|
||||
|
||||
/* Checkbox 样式 */
|
||||
.checkbox {
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
right: 12rpx;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border: 2rpx solid #E5E5E5;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.checkbox-icon.checked {
|
||||
background: #07C160;
|
||||
border-color: #07C160;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.checkbox-icon.checked::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 14rpx;
|
||||
top: 10rpx;
|
||||
width: 10rpx;
|
||||
height: 18rpx;
|
||||
border: solid white;
|
||||
border-width: 0 3rpx 3rpx 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.product-image-wrapper {
|
||||
width: 100%;
|
||||
height: 330rpx;
|
||||
background: #f5f5f5;
|
||||
position: relative;
|
||||
width: 212rpx; /* 106px × 2 */
|
||||
height: 212rpx; /* 106px × 2 */
|
||||
position: absolute;
|
||||
top: 50%; /* 垂直居中 */
|
||||
left: 50%; /* 水平居中 */
|
||||
transform: translate(-50%, -50%); /* 精确居中 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
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;
|
||||
object-fit: cover; /* 强制填充,保持1:1比例 */
|
||||
aspect-ratio: 1 / 1; /* 强制1:1比例 */
|
||||
}
|
||||
|
||||
.product-name {
|
||||
display: block;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
height: 78rpx;
|
||||
font-size: 32rpx; /* 16px × 2 */
|
||||
color: #333;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: clip; /* 不显示省略号,直接截断 */
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
position: absolute;
|
||||
bottom: 32rpx; /* 距离底部 16px × 2 */
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 16rpx; /* 左右留边距 */
|
||||
box-sizing: border-box;
|
||||
max-width: 100%; /* 确保不超出容器 */
|
||||
}
|
||||
|
||||
/* 加载提示 */
|
||||
@@ -133,17 +159,19 @@ page {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 8rpx 30rpx;
|
||||
padding: 8rpx 0;
|
||||
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;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
width: 100%;
|
||||
width: 90% !important;
|
||||
min-width: 90% !important;
|
||||
height: 80rpx;
|
||||
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
|
||||
color: white;
|
||||
@@ -153,6 +181,7 @@ page {
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2rpx 8rpx rgba(7, 193, 96, 0.25);
|
||||
transition: all 0.3s;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.generate-btn::after {
|
||||
@@ -168,3 +197,41 @@ page {
|
||||
color: #999;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 可拖动区域 */
|
||||
.movable-area {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none; /* 不阻塞其他点击事件 */
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 悬浮头像按钮 */
|
||||
.avatar-btn {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
pointer-events: auto; /* 开启头像本身的点击 */
|
||||
opacity: 1; /* 默认显示 */
|
||||
}
|
||||
|
||||
.avatar-btn:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "万花筒AI助手",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
|
||||
@@ -12,12 +12,52 @@ Page({
|
||||
// 检查是否已登录(检查token)
|
||||
const token = wx.getStorageSync('token');
|
||||
if (token) {
|
||||
wx.switchTab({
|
||||
wx.redirectTo({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
// 使用 reLaunch 跳转到首页,确保页面栈清空
|
||||
wx.reLaunch({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
},
|
||||
|
||||
// 显示更多菜单
|
||||
showMore() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['关于我们', '联系客服'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
// 关于我们
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
} else if (res.tapIndex === 1) {
|
||||
// 联系客服
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 显示帮助
|
||||
showHelp() {
|
||||
wx.showModal({
|
||||
title: '帮助',
|
||||
content: '如果遇到问题,请联系客服获取帮助。',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
});
|
||||
},
|
||||
|
||||
// 同意协议
|
||||
onAgreeChange(e: any) {
|
||||
this.setData({
|
||||
@@ -39,6 +79,28 @@ Page({
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到手机号登录页
|
||||
goToPhoneLogin() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/login/phone-login'
|
||||
});
|
||||
},
|
||||
|
||||
// 登录成功后跳转到首页
|
||||
loginSuccessToHome() {
|
||||
wx.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
// 未同意协议时点击登录
|
||||
handleAgreeFirst() {
|
||||
if (this.data.loginLoading) return
|
||||
@@ -108,6 +170,8 @@ Page({
|
||||
}, false);
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
console.log('[微信登录] 后端返回成功:', response.data);
|
||||
|
||||
// 保存token
|
||||
wx.setStorageSync('token', response.data.token);
|
||||
|
||||
@@ -116,18 +180,106 @@ Page({
|
||||
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'
|
||||
|
||||
// 登录成功后立即获取并缓存绑定状态
|
||||
try {
|
||||
console.log('[微信登录] 开始获取用户信息...');
|
||||
// 使用EmployeeService获取用户信息
|
||||
wx.request({
|
||||
url: `${API.baseURL}/api/employee/profile`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': `Bearer ${response.data.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (profileResponse) => {
|
||||
const profileData = profileResponse.data as any;
|
||||
console.log('[微信登录] 获取用户信息响应:', profileData);
|
||||
|
||||
if (profileData.code === 200 && profileData.data) {
|
||||
const userInfo = profileData.data;
|
||||
const isBound = userInfo.is_bound_xhs === 1;
|
||||
const hasCookie = userInfo.has_xhs_cookie === true;
|
||||
|
||||
console.log('[微信登录] 绑定状态:', { isBound, hasCookie });
|
||||
|
||||
// 更新本地绑定状态缓存
|
||||
const bindings = wx.getStorageSync('socialBindings') || {};
|
||||
if (isBound) {
|
||||
bindings.xiaohongshu = {
|
||||
phone: userInfo.xhs_phone,
|
||||
xhs_account: userInfo.xhs_account,
|
||||
bindTime: userInfo.bound_at || new Date().getTime(),
|
||||
cookieExpired: !hasCookie
|
||||
};
|
||||
} else {
|
||||
delete bindings.xiaohongshu;
|
||||
}
|
||||
wx.setStorageSync('socialBindings', bindings);
|
||||
|
||||
console.log('[登录] 已缓存绑定状态:', bindings);
|
||||
|
||||
// 检查是否需要跳转到绑定页面
|
||||
if (!isBound) {
|
||||
console.log('[微信登录] 未绑定,跳转绑定页');
|
||||
// 新用户,未绑定小红书,跳转到绑定页面
|
||||
this.setData({ loginLoading: false }); // 重置加载状态
|
||||
|
||||
wx.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
wx.showModal({
|
||||
title: '欢迎使用',
|
||||
content: '首次登录需要绑定小红书账号,即将跳转到绑定页面',
|
||||
showCancel: false,
|
||||
confirmText: '去绑定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/profile/platform-bind/platform-bind'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
console.log('[微信登录] 已绑定,跳转首页');
|
||||
// 老用户,已绑定,跳转到首页
|
||||
this.setData({ loginLoading: false }); // 重置加载状态
|
||||
|
||||
wx.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
} else {
|
||||
console.error('[微信登录] 获取绑定状态失败,默认跳转首页');
|
||||
// 获取绑定状态失败,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
},
|
||||
fail: (profileError) => {
|
||||
console.error('[登录] 获取绑定状态失败:', profileError);
|
||||
// 获取失败,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
} catch (profileError) {
|
||||
console.error('[登录] 获取绑定状态异常:', profileError);
|
||||
// 异常情况,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.message || '登录失败');
|
||||
}
|
||||
|
||||
@@ -1,58 +1,65 @@
|
||||
<!--pages/login/login.wxml-->
|
||||
<view class="login-container">
|
||||
<!-- 顶部Logo区域 -->
|
||||
<view class="logo-section">
|
||||
<view class="logo-box">
|
||||
<view class="logo-icon"></view>
|
||||
<view class="container">
|
||||
<!-- 登录内容 -->
|
||||
<view class="login-content">
|
||||
<!-- Logo区域 -->
|
||||
<view class="logo-container">
|
||||
<image class="logo-image" src="/images/logo.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
<text class="app-name">AI文章审核平台</text>
|
||||
<text class="app-slogan">智能内容生成与审核管理</text>
|
||||
</view>
|
||||
|
||||
<!-- 标题区域 -->
|
||||
<text class="app-title">万花筒</text>
|
||||
<text class="app-subtitle">快速生成种草内容</text>
|
||||
|
||||
<!-- 登录区域 -->
|
||||
<view class="login-section">
|
||||
<view class="welcome-text">
|
||||
<text class="welcome-title">欢迎使用</text>
|
||||
<text class="welcome-subtitle">请使用微信授权登录</text>
|
||||
<view class="login-buttons">
|
||||
<!-- 微信登录按钮(未同意协议) -->
|
||||
<button
|
||||
wx:if="{{!agreed}}"
|
||||
class="wechat-btn {{loginLoading ? 'loading' : ''}}"
|
||||
bindtap="handleAgreeFirst"
|
||||
loading="{{loginLoading}}"
|
||||
hover-class="btn-hover"
|
||||
>
|
||||
<image class="wechat-icon" src="/images/wechat-icon.svg" mode="aspectFit"></image>
|
||||
<text>微信一键登录</text>
|
||||
</button>
|
||||
|
||||
<!-- 微信登录按钮(已同意协议) -->
|
||||
<button
|
||||
wx:else
|
||||
class="wechat-btn {{loginLoading ? 'loading' : ''}}"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="handleWechatLogin"
|
||||
loading="{{loginLoading}}"
|
||||
hover-class="btn-hover"
|
||||
>
|
||||
<image class="wechat-icon" src="/images/wechat-icon.svg" mode="aspectFit"></image>
|
||||
<text>微信一键登录</text>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="phone-btn"
|
||||
bindtap="goToPhoneLogin"
|
||||
hover-class="btn-hover"
|
||||
>
|
||||
<image class="phone-icon" src="/images/phone-icon.svg" mode="aspectFit"></image>
|
||||
<text>手机号登录</text>
|
||||
</button>
|
||||
</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" />
|
||||
<checkbox value="agree" checked="{{agreed}}" color="#07c160" />
|
||||
<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>
|
||||
<text class="normal-text">已阅读并同意《万花筒AI用户协议》和《隐私协议》,允许平台统一管理本人账号信息</text>
|
||||
</view>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部指示器 -->
|
||||
<view class="bottom-indicator"></view>
|
||||
</view>
|
||||
|
||||
@@ -1,214 +1,145 @@
|
||||
/* pages/login/login.wxss */
|
||||
page {
|
||||
height: 100vh;
|
||||
background: #07c160;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
min-height: 100vh;
|
||||
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;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 顶部Logo区域 */
|
||||
.logo-section {
|
||||
.login-content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 200rpx 0 0;
|
||||
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;
|
||||
/* Logo容器 */
|
||||
.logo-container {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
background: #fff;
|
||||
border-radius: 40rpx;
|
||||
border: 1rpx solid #E5E5E5;
|
||||
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;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10rpx);
|
||||
}
|
||||
.logo-image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: white;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
.app-title {
|
||||
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;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.app-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.login-buttons {
|
||||
width: 100%;
|
||||
padding: 0 5%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.wechat-btn {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
padding: 28rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: #07C160;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
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;
|
||||
gap: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.login-btn::after {
|
||||
.wechat-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 {
|
||||
.wechat-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;
|
||||
width: 48rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
.phone-btn {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
padding: 28rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
border: 2rpx solid #E5E5E5;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 协议同意区域 */
|
||||
.phone-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.phone-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.btn-hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 协议区域 */
|
||||
.agreement-section {
|
||||
margin-top: 32rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-left: 40rpx;
|
||||
}
|
||||
|
||||
.agreement-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
@@ -219,18 +150,53 @@ checkbox {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 将 checkbox 改为圆形 */
|
||||
checkbox .wx-checkbox-input {
|
||||
border-radius: 50% !important;
|
||||
width: 36rpx !important;
|
||||
height: 36rpx !important;
|
||||
}
|
||||
|
||||
checkbox .wx-checkbox-input-checked {
|
||||
background-color: #07c160 !important;
|
||||
border-color: #07c160 !important;
|
||||
}
|
||||
|
||||
checkbox .wx-checkbox-input-checked::before {
|
||||
border-radius: 50%;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
line-height: 36rpx;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
background: transparent;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.normal-text {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
color: #07c160;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bottom-indicator {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 268rpx;
|
||||
height: 10rpx;
|
||||
background: #000;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
5
miniprogram/miniprogram/pages/login/phone-login.json
Normal file
5
miniprogram/miniprogram/pages/login/phone-login.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
556
miniprogram/miniprogram/pages/login/phone-login.ts
Normal file
556
miniprogram/miniprogram/pages/login/phone-login.ts
Normal file
@@ -0,0 +1,556 @@
|
||||
// pages/login/phone-login.ts
|
||||
import { API } from '../../config/api';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
phone: '',
|
||||
code: '',
|
||||
password: '',
|
||||
codePlaceholder: '请输入验证码',
|
||||
codeButtonText: '获取验证码',
|
||||
canGetCode: false,
|
||||
canLogin: false,
|
||||
countdown: 0,
|
||||
countdownTimer: null as any,
|
||||
showSuccess: false,
|
||||
showLoading: false,
|
||||
agreed: false,
|
||||
countryCodes: ['+86', '+852', '+853', '+886', '+1', '+44', '+81', '+82'],
|
||||
countryCodeIndex: 0,
|
||||
loginType: 'code' // 'code' 或 'password'
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
console.log('手机号登录页面加载');
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.data.countdownTimer) {
|
||||
clearInterval(this.data.countdownTimer);
|
||||
}
|
||||
},
|
||||
|
||||
// 区号选择
|
||||
onCountryCodeChange(e: any) {
|
||||
this.setData({
|
||||
countryCodeIndex: e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
// 同意协议
|
||||
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'
|
||||
});
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
wx.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
wx.navigateTo({
|
||||
url: '/pages/login/login'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 显示更多菜单
|
||||
showMore() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['关于我们', '联系客服'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
wx.showToast({ title: '功能开发中', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 显示帮助
|
||||
showHelp() {
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
// 手机号输入
|
||||
onPhoneInput(e: any) {
|
||||
const phone = e.detail.value;
|
||||
this.setData({
|
||||
phone: phone,
|
||||
canGetCode: phone.length === 11
|
||||
});
|
||||
this.checkCanLogin();
|
||||
},
|
||||
|
||||
// 验证码输入
|
||||
onCodeInput(e: any) {
|
||||
const code = e.detail.value;
|
||||
this.setData({
|
||||
code: code
|
||||
});
|
||||
this.checkCanLogin();
|
||||
},
|
||||
|
||||
// 密码输入
|
||||
onPasswordInput(e: any) {
|
||||
const password = e.detail.value;
|
||||
this.setData({
|
||||
password: password
|
||||
});
|
||||
this.checkCanLogin();
|
||||
},
|
||||
|
||||
// 切换登录方式
|
||||
switchLoginType() {
|
||||
const newType = this.data.loginType === 'code' ? 'password' : 'code';
|
||||
this.setData({
|
||||
loginType: newType,
|
||||
code: '',
|
||||
password: ''
|
||||
});
|
||||
},
|
||||
|
||||
// 检查是否可以登录
|
||||
checkCanLogin() {
|
||||
const { phone, code, password, loginType } = this.data;
|
||||
if (loginType === 'code') {
|
||||
this.setData({
|
||||
canLogin: phone.length === 11 && code.length >= 4
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
canLogin: phone.length === 11 && password.length >= 6
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 获取验证码
|
||||
getVerifyCode() {
|
||||
if (!this.data.agreed) {
|
||||
wx.showToast({
|
||||
title: '请先同意用户协议',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.data.canGetCode || this.data.countdown > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { phone } = this.data;
|
||||
if (phone.length !== 11) {
|
||||
wx.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载提示
|
||||
wx.showLoading({
|
||||
title: '发送中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
// 调用后端API发送验证码
|
||||
wx.request({
|
||||
url: `${API.baseURL}/api/xhs/send-verification-code`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
phone: phone
|
||||
},
|
||||
success: (res: any) => {
|
||||
wx.hideLoading();
|
||||
|
||||
if (res.statusCode === 200 && res.data.code === 200) {
|
||||
// 发送成功
|
||||
wx.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 开发环境打印验证码
|
||||
if (res.data.data && res.data.data.code) {
|
||||
console.log('验证码:', res.data.data.code);
|
||||
}
|
||||
|
||||
// 开始倒计时
|
||||
this.startCountdown();
|
||||
} else {
|
||||
// 发送失败
|
||||
wx.showToast({
|
||||
title: res.data.message || '发送失败,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading();
|
||||
console.error('发送验证码请求失败:', err);
|
||||
wx.showToast({
|
||||
title: '网络错误,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 开始倒计时
|
||||
startCountdown() {
|
||||
let countdown = 180;
|
||||
this.setData({
|
||||
countdown: countdown,
|
||||
codeButtonText: `${countdown}秒后重新获取`,
|
||||
canGetCode: false
|
||||
});
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown--;
|
||||
if (countdown <= 0) {
|
||||
clearInterval(timer);
|
||||
this.setData({
|
||||
countdown: 0,
|
||||
codeButtonText: '获取验证码',
|
||||
canGetCode: this.data.phone.length === 11,
|
||||
countdownTimer: null
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
countdown: countdown,
|
||||
codeButtonText: `${countdown}秒后重新获取`
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.setData({
|
||||
countdownTimer: timer
|
||||
});
|
||||
},
|
||||
|
||||
// 处理登录
|
||||
handleLogin() {
|
||||
if (!this.data.agreed) {
|
||||
wx.showToast({
|
||||
title: '请先同意用户协议',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.data.canLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { phone, code, password, loginType } = this.data;
|
||||
|
||||
// 显示加载提示
|
||||
wx.showLoading({
|
||||
title: '登录中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
// 调用后端API进行登录验证
|
||||
if (loginType === 'code') {
|
||||
// 验证码登录
|
||||
wx.request({
|
||||
url: `${API.baseURL}/api/login/xhs-phone-code`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
phone: phone,
|
||||
code: code
|
||||
},
|
||||
success: async (res: any) => {
|
||||
wx.hideLoading();
|
||||
|
||||
if (res.statusCode === 200 && res.data.code === 200) {
|
||||
// 登录成功
|
||||
const { token, employee } = res.data.data;
|
||||
|
||||
// 保存token和用户信息
|
||||
wx.setStorageSync('token', token);
|
||||
wx.setStorageSync('userInfo', employee);
|
||||
wx.setStorageSync('employeeInfo', employee);
|
||||
wx.setStorageSync('username', employee.name);
|
||||
|
||||
// 登录成功后立即获取并缓存绑定状态
|
||||
try {
|
||||
wx.request({
|
||||
url: `${API.baseURL}/api/employee/profile`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (profileResponse: any) => {
|
||||
const profileData = profileResponse.data;
|
||||
if (profileData.code === 200 && profileData.data) {
|
||||
const userInfo = profileData.data;
|
||||
const isBound = userInfo.is_bound_xhs === 1;
|
||||
const hasCookie = userInfo.has_xhs_cookie === true;
|
||||
|
||||
// 更新本地绑定状态缓存
|
||||
const bindings = wx.getStorageSync('socialBindings') || {};
|
||||
if (isBound) {
|
||||
bindings.xiaohongshu = {
|
||||
phone: userInfo.xhs_phone,
|
||||
xhs_account: userInfo.xhs_account,
|
||||
bindTime: userInfo.bound_at || new Date().getTime(),
|
||||
cookieExpired: !hasCookie
|
||||
};
|
||||
} else {
|
||||
delete bindings.xiaohongshu;
|
||||
}
|
||||
wx.setStorageSync('socialBindings', bindings);
|
||||
|
||||
console.log('[验证码登录] 已缓存绑定状态:', bindings);
|
||||
|
||||
// 检查是否需要跳转到绑定页面
|
||||
if (!isBound) {
|
||||
// 新用户,未绑定小红书,跳转到绑定页面
|
||||
this.setData({
|
||||
showSuccess: true,
|
||||
loginLoading: false
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({ showSuccess: false });
|
||||
|
||||
wx.showModal({
|
||||
title: '欢迎使用',
|
||||
content: '首次登录需要绑定小红书账号,即将跳转到绑定页面',
|
||||
showCancel: false,
|
||||
confirmText: '去绑定',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/profile/platform-bind/platform-bind'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
// 老用户,已绑定,跳转到首页
|
||||
this.setData({
|
||||
showSuccess: true,
|
||||
loginLoading: false
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({ showSuccess: false });
|
||||
|
||||
wx.reLaunch({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
} else {
|
||||
// 获取绑定状态失败,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
},
|
||||
fail: (profileError) => {
|
||||
console.error('[验证码登录] 获取绑定状态失败:', profileError);
|
||||
// 获取失败,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
});
|
||||
} catch (profileError) {
|
||||
console.error('[验证码登录] 获取绑定状态异常:', profileError);
|
||||
// 异常情况,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
} else {
|
||||
// 登录失败
|
||||
wx.showToast({
|
||||
title: res.data.message || '登录失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading();
|
||||
this.setData({ loginLoading: false });
|
||||
console.error('验证码登录请求失败:', err);
|
||||
wx.showToast({
|
||||
title: '网络错误,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 密码登录
|
||||
wx.request({
|
||||
url: `${API.baseURL}/api/login/phone-password`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
phone: phone,
|
||||
password: password
|
||||
},
|
||||
success: async (res: any) => {
|
||||
wx.hideLoading();
|
||||
|
||||
if (res.statusCode === 200 && res.data.code === 200) {
|
||||
// 登录成功
|
||||
const { token, employee } = res.data.data;
|
||||
|
||||
// 保存token和用户信息
|
||||
wx.setStorageSync('token', token);
|
||||
wx.setStorageSync('userInfo', employee);
|
||||
wx.setStorageSync('employeeInfo', employee);
|
||||
wx.setStorageSync('username', employee.name);
|
||||
|
||||
// 登录成功后立即获取并缓存绑定状态
|
||||
try {
|
||||
wx.request({
|
||||
url: `${API.baseURL}/api/employee/profile`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (profileResponse: any) => {
|
||||
const profileData = profileResponse.data;
|
||||
if (profileData.code === 200 && profileData.data) {
|
||||
const userInfo = profileData.data;
|
||||
const isBound = userInfo.is_bound_xhs === 1;
|
||||
const hasCookie = userInfo.has_xhs_cookie === true;
|
||||
|
||||
// 更新本地绑定状态缓存
|
||||
const bindings = wx.getStorageSync('socialBindings') || {};
|
||||
if (isBound) {
|
||||
bindings.xiaohongshu = {
|
||||
phone: userInfo.xhs_phone,
|
||||
xhs_account: userInfo.xhs_account,
|
||||
bindTime: userInfo.bound_at || new Date().getTime(),
|
||||
cookieExpired: !hasCookie
|
||||
};
|
||||
} else {
|
||||
delete bindings.xiaohongshu;
|
||||
}
|
||||
wx.setStorageSync('socialBindings', bindings);
|
||||
|
||||
console.log('[密码登录] 已缓存绑定状态:', bindings);
|
||||
|
||||
// 检查是否需要跳转到绑定页面
|
||||
if (!isBound) {
|
||||
// 新用户,未绑定小红书,跳转到绑定页面
|
||||
this.setData({
|
||||
showSuccess: true,
|
||||
loginLoading: false
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({ showSuccess: false });
|
||||
|
||||
wx.showModal({
|
||||
title: '欢迎使用',
|
||||
content: '首次登录需要绑定小红书账号,即将跳转到绑定页面',
|
||||
showCancel: false,
|
||||
confirmText: '去绑定',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/profile/platform-bind/platform-bind'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
// 老用户,已绑定,跳转到首页
|
||||
this.setData({
|
||||
showSuccess: true,
|
||||
loginLoading: false
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({ showSuccess: false });
|
||||
|
||||
wx.reLaunch({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
} else {
|
||||
// 获取绑定状态失败,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
},
|
||||
fail: (profileError) => {
|
||||
console.error('[密码登录] 获取绑定状态失败:', profileError);
|
||||
// 获取失败,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
});
|
||||
} catch (profileError) {
|
||||
console.error('[密码登录] 获取绑定状态异常:', profileError);
|
||||
// 异常情况,默认跳转首页
|
||||
this.loginSuccessToHome();
|
||||
}
|
||||
} else {
|
||||
// 登录失败
|
||||
wx.showToast({
|
||||
title: res.data.message || '登录失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading();
|
||||
this.setData({ loginLoading: false });
|
||||
console.error('登录请求失败:', err);
|
||||
wx.showToast({
|
||||
title: '网络错误,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 登录成功后跳转首页(备用方法)
|
||||
loginSuccessToHome() {
|
||||
this.setData({
|
||||
showSuccess: true,
|
||||
loginLoading: false
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({ showSuccess: false });
|
||||
|
||||
wx.reLaunch({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
101
miniprogram/miniprogram/pages/login/phone-login.wxml
Normal file
101
miniprogram/miniprogram/pages/login/phone-login.wxml
Normal file
@@ -0,0 +1,101 @@
|
||||
<!--pages/login/phone-login.wxml-->
|
||||
<view class="container">
|
||||
<!-- 登录表单 -->
|
||||
<view class="login-form">
|
||||
<text class="page-title">登录万花筒</text>
|
||||
|
||||
<view class="input-row">
|
||||
<text class="label">手机号</text>
|
||||
<picker mode="selector" range="{{countryCodes}}" value="{{countryCodeIndex}}" bindchange="onCountryCodeChange">
|
||||
<view class="prefix">{{countryCodes[countryCodeIndex]}}</view>
|
||||
</picker>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="输入手机号"
|
||||
maxlength="11"
|
||||
bindinput="onPhoneInput"
|
||||
value="{{phone}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 验证码登录 -->
|
||||
<view class="input-row" wx:if="{{loginType === 'code'}}">
|
||||
<text class="label">验证码</text>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
bindinput="onCodeInput"
|
||||
value="{{code}}"
|
||||
/>
|
||||
<button
|
||||
class="get-code {{countdown > 0 ? 'disabled' : ''}}"
|
||||
bindtap="getVerifyCode"
|
||||
>
|
||||
{{codeButtonText}}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 密码登录 -->
|
||||
<view class="input-row" wx:if="{{loginType === 'password'}}">
|
||||
<text class="label">密码</text>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
bindinput="onPasswordInput"
|
||||
value="{{password}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 切换登录方式 -->
|
||||
<view class="switch-login-type" bindtap="switchLoginType">
|
||||
<text wx:if="{{loginType === 'code'}}">密码登录</text>
|
||||
<text wx:else>验证码登录</text>
|
||||
</view>
|
||||
|
||||
<!-- 协议提示 -->
|
||||
<view class="agreement-section">
|
||||
<checkbox-group bindchange="onAgreeChange">
|
||||
<label class="agreement-label">
|
||||
<checkbox value="agree" checked="{{agreed}}" color="#07c160" />
|
||||
<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>
|
||||
|
||||
<!-- 登录按钮(独立在login-form外) -->
|
||||
<view class="button-wrapper">
|
||||
<button
|
||||
class="green-btn"
|
||||
bindtap="handleLogin"
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 底部指示器 -->
|
||||
<view class="bottom-indicator"></view>
|
||||
</view>
|
||||
|
||||
<!-- Toast提示 -->
|
||||
<view class="toast-overlay" wx:if="{{showSuccess}}">
|
||||
<view class="toast">
|
||||
<view class="toast-icon">
|
||||
<view class="success-checkmark"></view>
|
||||
</view>
|
||||
<text class="toast-text">登录成功</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="toast-overlay" wx:if="{{showLoading}}">
|
||||
<view class="toast">
|
||||
<view class="toast-loading"></view>
|
||||
<text class="toast-text">获取验证中</text>
|
||||
</view>
|
||||
</view>
|
||||
244
miniprogram/miniprogram/pages/login/phone-login.wxss
Normal file
244
miniprogram/miniprogram/pages/login/phone-login.wxss
Normal file
@@ -0,0 +1,244 @@
|
||||
/* pages/login/phone-login.wxss */
|
||||
page {
|
||||
height: 100vh;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 40rpx 48rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 44rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
display: block;
|
||||
margin-bottom: 80rpx;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 2rpx solid #E5E5E5;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-row .label {
|
||||
width: 112rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.input-row .prefix {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-right: 16rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.input-row input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
font-size: 32rpx;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.input-row input::placeholder {
|
||||
color: #C0C0C0;
|
||||
}
|
||||
|
||||
.input-row .get-code {
|
||||
font-size: 28rpx !important;
|
||||
font-weight: normal !important;
|
||||
color: #666 !important;
|
||||
padding: 12rpx 24rpx !important;
|
||||
border: 2rpx solid #E5E5E5 !important;
|
||||
border-radius: 8rpx !important;
|
||||
background: #fff !important;
|
||||
white-space: nowrap;
|
||||
line-height: 1 !important;
|
||||
min-width: auto !important;
|
||||
width: auto !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.input-row .get-code::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.input-row .get-code.disabled {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 切换登录方式 */
|
||||
.switch-login-type {
|
||||
text-align: right;
|
||||
padding: 24rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #07C160;
|
||||
}
|
||||
|
||||
/* 协议区域 */
|
||||
.agreement-section {
|
||||
margin-top: 60rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.agreement-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: 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: #999;
|
||||
}
|
||||
|
||||
.normal-text {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
color: #07c160;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 登录按钮容器 */
|
||||
.button-wrapper {
|
||||
width: 90%;
|
||||
margin: 24rpx auto 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.button-wrapper .green-btn {
|
||||
background: #07C160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx 0;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
transition: opacity 0.2s;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-wrapper .green-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button-wrapper .green-btn:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.bottom-indicator {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 268rpx;
|
||||
height: 10rpx;
|
||||
background: #000;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
/* Toast 提示 */
|
||||
.toast-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
background: rgba(76, 76, 76, 0.9);
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx 64rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
min-width: 272rpx;
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.success-checkmark {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border: 6rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.success-checkmark::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
top: 16rpx;
|
||||
width: 16rpx;
|
||||
height: 32rpx;
|
||||
border: solid #fff;
|
||||
border-width: 0 6rpx 6rpx 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.toast-text {
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.toast-loading {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border: 6rpx solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--pages/profile/about/about.wxml-->
|
||||
<view class="page-container">
|
||||
<view class="logo-section">
|
||||
<view class="app-logo"></view>
|
||||
<image class="app-logo" src="/images/logo.png" mode="aspectFit"></image>
|
||||
<text class="app-name">AI文章审核平台</text>
|
||||
<text class="app-version">v1.0.0</text>
|
||||
</view>
|
||||
|
||||
@@ -26,38 +26,11 @@ page {
|
||||
.app-logo {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(10rpx);
|
||||
border-radius: 24rpx;
|
||||
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;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// pages/profile/article-detail/article-detail.ts
|
||||
import { EmployeeService } from '../../../services/employee';
|
||||
import { getImageUrl } from '../../../utils/util';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
articleId: 0,
|
||||
productName: '',
|
||||
article: {
|
||||
title: '',
|
||||
content: '',
|
||||
topic: '',
|
||||
publishLink: '', // 小红书链接
|
||||
images: [] as Array<{
|
||||
id: number;
|
||||
@@ -33,36 +32,23 @@ Page({
|
||||
|
||||
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 || [] // 获取标签列表
|
||||
publishLink: response.data.publish_link || '',
|
||||
images: (response.data.images || []).map((img: any) => ({
|
||||
...img,
|
||||
image_url: getImageUrl(img.image_url),
|
||||
image_thumb_url: getImageUrl(img.image_thumb_url || img.image_url)
|
||||
})),
|
||||
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 || '加载失败');
|
||||
}
|
||||
@@ -81,8 +67,8 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 分享链接(自动复制)
|
||||
shareArticle() {
|
||||
// 复制小红书链接
|
||||
copyLink() {
|
||||
const publishLink = this.data.article.publishLink;
|
||||
|
||||
if (!publishLink) {
|
||||
@@ -93,7 +79,6 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
wx.setClipboardData({
|
||||
data: publishLink,
|
||||
success: () => {
|
||||
@@ -110,5 +95,16 @@ Page({
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(e: any) {
|
||||
const { url } = e.currentTarget.dataset;
|
||||
const urls = this.data.article.images.map((img: any) => img.image_url);
|
||||
|
||||
wx.previewImage({
|
||||
current: url,
|
||||
urls: urls
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,65 +1,48 @@
|
||||
<!--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 class="image-gallery">
|
||||
<view
|
||||
class="image-item"
|
||||
wx:for="{{article.images}}"
|
||||
wx:key="id">
|
||||
<image
|
||||
class="gallery-image"
|
||||
src="{{item.image_url}}"
|
||||
mode="aspectFill"
|
||||
bindtap="previewImage"
|
||||
data-url="{{item.image_url}}"
|
||||
/>
|
||||
</view>
|
||||
</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 class="article-header">
|
||||
<!-- 标题(只读) -->
|
||||
<text class="article-title" user-select>{{article.title}}</text>
|
||||
</view>
|
||||
|
||||
<!-- Topic标签(如果tags为空,则显示topic) -->
|
||||
<view class="article-tags" wx:elif="{{article.topic}}">
|
||||
<text class="tag-item" user-select>#{{article.topic}}</text>
|
||||
|
||||
<view class="article-content">
|
||||
<!-- 内容(只读) -->
|
||||
<text class="content-text" user-select>{{article.content}}</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 class="link-header">
|
||||
<text class="link-label">小红书链接</text>
|
||||
<button class="copy-btn" bindtap="copyLink">
|
||||
<text class="copy-text">复制链接</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="link-value">
|
||||
<text user-select>{{article.publishLink}}</text>
|
||||
@@ -67,12 +50,4 @@
|
||||
</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>
|
||||
|
||||
@@ -2,43 +2,164 @@
|
||||
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;
|
||||
/* 文案容器 */
|
||||
.article-container {
|
||||
flex: 1;
|
||||
background: white;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.article-wrapper {
|
||||
padding: 30rpx;
|
||||
margin: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(255, 36, 66, 0.2);
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - 40rpx); /* 确保不超出屏幕 */
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
/* 文章图片画廊 - 水平滚动 */
|
||||
.article-images {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
overflow-x: auto;
|
||||
padding: 16rpx 0 24rpx;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.image-gallery::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: #f5f5f5;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.image-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
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: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 12rpx;
|
||||
color: #07c160;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: white;
|
||||
.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;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.article-content {
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 内容文本(只读) */
|
||||
.content-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.8;
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 小红书链接区域 */
|
||||
.xhs-link-section {
|
||||
margin-top: 40rpx;
|
||||
padding: 24rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 16rpx;
|
||||
border: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.link-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.link-label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
padding: 8rpx 20rpx;
|
||||
height: auto;
|
||||
line-height: 1;
|
||||
background: linear-gradient(135deg, #07c160 0%, #0ae97a 100%);
|
||||
border-radius: 20rpx;
|
||||
border: none;
|
||||
font-size: 24rpx;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.copy-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.copy-text {
|
||||
color: white;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.link-value {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
word-break: break-all;
|
||||
line-height: 1.6;
|
||||
padding: 12rpx 16rpx;
|
||||
background: white;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
@@ -53,202 +174,3 @@ page {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "意见反馈",
|
||||
"navigationBarBackgroundColor": "#ff2442",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
// pages/profile/feedback/feedback.ts
|
||||
import { request } from '../../../utils/request';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
typeList: ['功能建议', 'Bug反馈', '体验问题', '其他'],
|
||||
typeIndex: 0,
|
||||
content: '',
|
||||
contact: ''
|
||||
contact: '',
|
||||
nickname: ''
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
|
||||
// 获取用户信息
|
||||
const employeeInfo = wx.getStorageSync('employeeInfo');
|
||||
if (employeeInfo && employeeInfo.name) {
|
||||
this.setData({
|
||||
nickname: employeeInfo.name
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onTypeChange(e: any) {
|
||||
@@ -18,6 +27,7 @@ Page({
|
||||
},
|
||||
|
||||
onContentInput(e: any) {
|
||||
console.log('输入内容:', e.detail.value);
|
||||
this.setData({
|
||||
content: e.detail.value
|
||||
});
|
||||
@@ -29,7 +39,11 @@ Page({
|
||||
});
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
async handleSubmit() {
|
||||
console.log('提交时的内容:', this.data.content);
|
||||
console.log('内容长度:', this.data.content.length);
|
||||
console.log('去空格后:', this.data.content.trim());
|
||||
|
||||
if (!this.data.content.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入问题描述',
|
||||
@@ -43,16 +57,41 @@ Page({
|
||||
mask: true
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success'
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/api/employee/feedback',
|
||||
method: 'POST',
|
||||
data: {
|
||||
feedback_type: this.data.typeList[this.data.typeIndex],
|
||||
description: this.data.content.trim(),
|
||||
contact_info: this.data.contact.trim(),
|
||||
nickname: this.data.nickname
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
wx.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
wx.hideLoading();
|
||||
|
||||
if (res.code === 200) {
|
||||
wx.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
wx.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.message || '提交失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: error.message || '网络错误',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
class="feedback-textarea"
|
||||
placeholder="请详细描述您遇到的问题或建议"
|
||||
maxlength="500"
|
||||
value="{{content}}"
|
||||
bindInput="onContentInput"
|
||||
model:value="{{content}}"
|
||||
/>
|
||||
<view class="char-count">{{content.length}}/500</view>
|
||||
</view>
|
||||
@@ -28,8 +27,7 @@
|
||||
<input
|
||||
class="feedback-input"
|
||||
placeholder="请输入您的手机号或邮箱"
|
||||
value="{{contact}}"
|
||||
bindInput="onContactInput"
|
||||
model:value="{{contact}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -65,9 +65,11 @@ page {
|
||||
|
||||
.feedback-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
padding: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
box-sizing: border-box;
|
||||
@@ -83,11 +85,11 @@ page {
|
||||
background: #07c160;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
border-radius: 50rpx;
|
||||
padding: 32rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 36, 66, 0.3);
|
||||
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
.submit-btn::after {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "绑定账号",
|
||||
"navigationBarBackgroundColor": "#ff2442",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarTitleText": "绑定小红书账号",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
|
||||
@@ -1,71 +1,41 @@
|
||||
// pages/profile/platform-bind/platform-bind.ts
|
||||
import { EmployeeService } from '../../../services/employee';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
platformType: '',
|
||||
platformName: '',
|
||||
countryCode: '+86',
|
||||
phone: '',
|
||||
code: '',
|
||||
codeSending: false,
|
||||
codeButtonText: '获取验证码',
|
||||
countdown: 0,
|
||||
binded: false,
|
||||
bindTime: ''
|
||||
countdownTimer: null as any,
|
||||
showSuccess: false,
|
||||
showLoading: false,
|
||||
showFail: false,
|
||||
loadingText: '加载中...',
|
||||
countryCodes: ['+86', '+852', '+853', '+886', '+1', '+44', '+81', '+82'],
|
||||
countryCodeIndex: 0,
|
||||
pollTimer: null as any, // 轮询定时器
|
||||
pollCount: 0 // 轮询次数
|
||||
},
|
||||
|
||||
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: '抖音'
|
||||
};
|
||||
onLoad() {
|
||||
console.log('小红书绑定页面加载');
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.data.countdownTimer) {
|
||||
clearInterval(this.data.countdownTimer);
|
||||
}
|
||||
// 清理轮询定时器
|
||||
if (this.data.pollTimer) {
|
||||
clearInterval(this.data.pollTimer);
|
||||
}
|
||||
},
|
||||
|
||||
// 区号选择
|
||||
onCountryCodeChange(e: any) {
|
||||
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]
|
||||
});
|
||||
}
|
||||
countryCodeIndex: e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
@@ -83,203 +53,278 @@ Page({
|
||||
});
|
||||
},
|
||||
|
||||
// 发送验证码
|
||||
sendCode() {
|
||||
const phone = this.data.phone;
|
||||
// 获取验证码
|
||||
async getVerifyCode() {
|
||||
if (this.data.countdown > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证手机号
|
||||
if (!phone || phone.length !== 11) {
|
||||
const { phone, countryCodes, countryCodeIndex } = this.data;
|
||||
if (phone.length !== 11) {
|
||||
wx.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 手机号格式验证
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(phone)) {
|
||||
wx.showToast({
|
||||
title: '手机号格式不正确',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置发送状态
|
||||
// 显示加载
|
||||
this.setData({
|
||||
codeSending: true
|
||||
showLoading: true
|
||||
});
|
||||
|
||||
// 模拟发送验证码(实际应该调用后端API)
|
||||
wx.showLoading({
|
||||
title: '发送中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
wx.hideLoading();
|
||||
try {
|
||||
// 调用后端API发送验证码
|
||||
const countryCode = countryCodes[countryCodeIndex];
|
||||
console.log('发送验证码到:', phone, '区号:', countryCode);
|
||||
|
||||
await EmployeeService.sendXHSCode(phone);
|
||||
|
||||
this.setData({
|
||||
showLoading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success'
|
||||
title: '验证码已发送,请在小红书APP中查看',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 开始倒计时
|
||||
this.startCountdown();
|
||||
|
||||
// 在控制台输出模拟验证码(实际开发中删除)
|
||||
console.log(`验证码: 123456`);
|
||||
}, 1000);
|
||||
} catch (error: any) {
|
||||
this.setData({
|
||||
showLoading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: error.message || '发送失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 倒计时
|
||||
// 开始倒计时
|
||||
startCountdown() {
|
||||
let countdown = 60;
|
||||
|
||||
let countdown = 180;
|
||||
this.setData({
|
||||
countdown: countdown,
|
||||
codeSending: true
|
||||
codeButtonText: `${countdown}秒后重新获取`
|
||||
});
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown--;
|
||||
|
||||
if (countdown <= 0) {
|
||||
clearInterval(timer);
|
||||
this.setData({
|
||||
countdown: 0,
|
||||
codeSending: false
|
||||
codeButtonText: '获取验证码',
|
||||
countdownTimer: null
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
countdown: countdown
|
||||
countdown: countdown,
|
||||
codeButtonText: `${countdown}秒后重新获取`
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.setData({
|
||||
countdownTimer: timer
|
||||
});
|
||||
},
|
||||
|
||||
// 绑定账号
|
||||
bindAccount() {
|
||||
const phone = this.data.phone;
|
||||
const code = this.data.code;
|
||||
const platform = this.data.platformType;
|
||||
async bindAccount() {
|
||||
const { phone, code } = this.data;
|
||||
|
||||
// 验证手机号
|
||||
if (!phone || phone.length !== 11) {
|
||||
wx.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
if (!code || code.length !== 6) {
|
||||
if (!code || code.length < 4) {
|
||||
wx.showToast({
|
||||
title: '请输入6位验证码',
|
||||
icon: 'none'
|
||||
title: '请输入验证码',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟验证码验证(实际应该调用后端API)
|
||||
wx.showLoading({
|
||||
title: '绑定中...',
|
||||
mask: true
|
||||
// 显示加载
|
||||
this.setData({
|
||||
showLoading: true,
|
||||
loadingText: '正在验证...',
|
||||
pollCount: 0
|
||||
});
|
||||
|
||||
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')}`;
|
||||
|
||||
// 更新状态
|
||||
try {
|
||||
// 调用后端API进行绑定(异步处理)
|
||||
console.log('绑定小红书账号:', { phone, code });
|
||||
|
||||
const result = await EmployeeService.bindXHS(phone, code);
|
||||
|
||||
// 后端立即返回,开始轮询绑定状态
|
||||
this.setData({
|
||||
binded: true,
|
||||
code: '',
|
||||
bindTime: formatTime
|
||||
loadingText: '正在登录小红书...'
|
||||
});
|
||||
|
||||
// 开始轮询绑定状态
|
||||
this.startPollingBindStatus();
|
||||
|
||||
} catch (error: any) {
|
||||
this.setData({
|
||||
showLoading: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '绑定成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
// 绑定失败
|
||||
this.setData({
|
||||
showFail: true
|
||||
});
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
// 跳转到用户协议
|
||||
goToUserAgreement() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/agreement/user-agreement/user-agreement'
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
showFail: false
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
|
||||
// 开始轮询绑定状态
|
||||
startPollingBindStatus() {
|
||||
// 清理之前的定时器
|
||||
if (this.data.pollTimer) {
|
||||
clearInterval(this.data.pollTimer);
|
||||
}
|
||||
|
||||
// 立即查询一次
|
||||
this.checkBindStatus();
|
||||
|
||||
// 每1秒查询一次,最多轮询60次(60秒)
|
||||
const timer = setInterval(() => {
|
||||
this.checkBindStatus();
|
||||
}, 1000);
|
||||
|
||||
this.setData({
|
||||
pollTimer: timer
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到隐私政策
|
||||
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);
|
||||
|
||||
// 更新状态
|
||||
|
||||
// 检查绑定状态
|
||||
async checkBindStatus() {
|
||||
try {
|
||||
const pollCount = this.data.pollCount + 1;
|
||||
this.setData({ pollCount });
|
||||
|
||||
// 超过60次轮询(60秒),停止轮询
|
||||
if (pollCount > 60) {
|
||||
this.stopPolling();
|
||||
this.setData({
|
||||
showLoading: false,
|
||||
showFail: true
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
binded: false,
|
||||
phone: '',
|
||||
code: '',
|
||||
bindTime: ''
|
||||
showFail: false
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`查询绑定状态: 第${pollCount}次`);
|
||||
|
||||
const response = await EmployeeService.getBindXHSStatus();
|
||||
const status = response.data;
|
||||
|
||||
console.log('绑定状态:', status);
|
||||
|
||||
if (!status) {
|
||||
console.error('状态数据为空');
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.status === 'success') {
|
||||
// 绑定成功
|
||||
this.stopPolling();
|
||||
|
||||
this.setData({
|
||||
showLoading: false,
|
||||
showSuccess: true
|
||||
});
|
||||
|
||||
// 保存绑定信息到本地
|
||||
const bindings = wx.getStorageSync('socialBindings') || {};
|
||||
bindings.xiaohongshu = {
|
||||
phone: this.data.phone,
|
||||
xhs_account: status.xhs_account || this.data.phone,
|
||||
bindTime: new Date().getTime(),
|
||||
cookieExpired: false
|
||||
};
|
||||
wx.setStorageSync('socialBindings', bindings);
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
showSuccess: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '已解除绑定',
|
||||
icon: 'success',
|
||||
duration: 1500,
|
||||
success: () => {
|
||||
setTimeout(() => {
|
||||
wx.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
// 绑定成功后直接跳转到首页
|
||||
wx.reLaunch({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
} else if (status.status === 'failed') {
|
||||
// 绑定失败
|
||||
this.stopPolling();
|
||||
|
||||
this.setData({
|
||||
showLoading: false,
|
||||
showFail: true
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: status.error || '绑定失败',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
showFail: false
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
} else if (status.status === 'processing') {
|
||||
// 仍在处理中,继续轮询
|
||||
this.setData({
|
||||
loadingText: status.message || '正在登录小红书...'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('查询绑定状态失败:', error);
|
||||
// 查询失败,继续轮询
|
||||
}
|
||||
},
|
||||
|
||||
// 停止轮询
|
||||
stopPolling() {
|
||||
if (this.data.pollTimer) {
|
||||
clearInterval(this.data.pollTimer);
|
||||
this.setData({
|
||||
pollTimer: null
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,84 +1,79 @@
|
||||
<!--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 class="container">
|
||||
<!-- 绑定内容 -->
|
||||
<view class="bind-content">
|
||||
<!-- 小红书Logo -->
|
||||
<view class="xhs-logo">
|
||||
<view class="logo-box">
|
||||
<text class="logo-text">小红书</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="unbind-btn" bindtap="unbindAccount">
|
||||
解除绑定
|
||||
</button>
|
||||
<text class="page-title">请绑定小红书账号</text>
|
||||
<text class="page-subtitle">手机号未注册小红书会导致绑定失败</text>
|
||||
|
||||
<view class="bind-form">
|
||||
<view class="input-row">
|
||||
<text class="label">手机号</text>
|
||||
<picker mode="selector" range="{{countryCodes}}" value="{{countryCodeIndex}}" bindchange="onCountryCodeChange">
|
||||
<view class="prefix">{{countryCodes[countryCodeIndex]}}</view>
|
||||
</picker>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="输入手机号"
|
||||
maxlength="11"
|
||||
bindinput="onPhoneInput"
|
||||
value="{{phone}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-row">
|
||||
<text class="label">验证码</text>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
bindinput="onCodeInput"
|
||||
value="{{code}}"
|
||||
/>
|
||||
<button
|
||||
class="get-code {{countdown > 0 ? 'disabled' : ''}}"
|
||||
bindtap="getVerifyCode"
|
||||
>
|
||||
{{codeButtonText}}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<button class="red-btn" bindtap="bindAccount">验证并绑定</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部指示器 -->
|
||||
<view class="bottom-indicator"></view>
|
||||
</view>
|
||||
|
||||
<!-- Toast提示 -->
|
||||
<view class="toast-overlay" wx:if="{{showSuccess}}">
|
||||
<view class="toast">
|
||||
<view class="toast-icon">
|
||||
<view class="success-checkmark"></view>
|
||||
</view>
|
||||
<text class="toast-text">绑定成功</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="toast-overlay" wx:if="{{showLoading}}">
|
||||
<view class="toast">
|
||||
<view class="toast-loading"></view>
|
||||
<text class="toast-text">{{loadingText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="toast-overlay" wx:if="{{showFail}}">
|
||||
<view class="toast">
|
||||
<view class="toast-icon info">
|
||||
<view class="info-icon">i</view>
|
||||
</view>
|
||||
<text class="toast-text">绑定失败,手机\n号未注册账号</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,247 +1,240 @@
|
||||
/* pages/profile/platform-bind/platform-bind.wxss */
|
||||
page {
|
||||
background: #f8f8f8;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
padding: 30rpx;
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 平台头部 */
|
||||
.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;
|
||||
.bind-content {
|
||||
padding: 80rpx 48rpx;
|
||||
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;
|
||||
.xhs-logo {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.logo-box {
|
||||
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;
|
||||
height: 100%;
|
||||
background: #FF2442;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.bind-btn::after {
|
||||
.logo-text {
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 44rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 80rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bind-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 2rpx solid #E5E5E5;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-row .label {
|
||||
width: 112rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.input-row .prefix {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-right: 16rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.input-row input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
font-size: 32rpx;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.input-row input::placeholder {
|
||||
color: #C0C0C0;
|
||||
}
|
||||
|
||||
.input-row .get-code {
|
||||
font-size: 28rpx !important;
|
||||
font-weight: normal !important;
|
||||
color: #666 !important;
|
||||
padding: 12rpx 24rpx !important;
|
||||
border: 2rpx solid #E5E5E5 !important;
|
||||
border-radius: 8rpx !important;
|
||||
background: #fff !important;
|
||||
white-space: nowrap;
|
||||
line-height: 1 !important;
|
||||
min-width: auto !important;
|
||||
width: auto !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.input-row .get-code::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 协议提示 */
|
||||
.agreement-tip {
|
||||
.input-row .get-code.disabled {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.red-btn {
|
||||
width: 100%;
|
||||
padding: 28rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: #FF2442;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 80rpx;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.red-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.red-btn:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.bottom-indicator {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 268rpx;
|
||||
height: 10rpx;
|
||||
background: #000;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
/* Toast 提示 */
|
||||
.toast-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 4rpx;
|
||||
padding: 0 8rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.toast {
|
||||
background: rgba(76, 76, 76, 0.9);
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx 64rpx;
|
||||
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;
|
||||
gap: 24rpx;
|
||||
min-width: 272rpx;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
.toast-icon {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
.success-checkmark {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border: 6rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 500;
|
||||
.success-checkmark::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
top: 16rpx;
|
||||
width: 16rpx;
|
||||
height: 32rpx;
|
||||
border: solid #fff;
|
||||
border-width: 0 6rpx 6rpx 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.unbind-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: white;
|
||||
color: #07c160;
|
||||
border: 2rpx solid #07c160;
|
||||
border-radius: 12rpx;
|
||||
.info-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border: 4rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.unbind-btn::after {
|
||||
border: none;
|
||||
.toast-text {
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.toast-loading {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border: 6rpx solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"onReachBottomDistance": 50,
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// pages/profile/profile.ts
|
||||
import { EmployeeService } from '../../services/employee';
|
||||
import { API, isDevOrTrial } from '../../config/api';
|
||||
import { getImageUrl } from '../../utils/util';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -7,11 +9,16 @@ Page({
|
||||
enterpriseName: '',
|
||||
avatar: '',
|
||||
publishedCount: 0,
|
||||
pendingCount: 0, // 待审核
|
||||
rejectedCount: 0, // 已驳回
|
||||
userInfo: null as any,
|
||||
isBoundXHS: false,
|
||||
xhsAccount: ''
|
||||
xhsAccount: '',
|
||||
publishRecords: [] as any[], // 发布记录列表
|
||||
currentPage: 1, // 当前页码
|
||||
pageSize: 10, // 每页数量
|
||||
hasMore: true, // 是否还有更多数据
|
||||
loading: false, // 是否正在加载
|
||||
showLogoutBtn: false, // 是否显示退出登录按钮(开发版和体验版)
|
||||
showUnbindDialog: false // 是否显示解绑确认弹窗
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -24,23 +31,21 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置是否显示退出登录按钮(开发版和体验版显示)
|
||||
this.setData({
|
||||
showLogoutBtn: isDevOrTrial()
|
||||
});
|
||||
|
||||
// 加载用户信息
|
||||
this.loadUserInfo();
|
||||
// 获取数据概览
|
||||
this.loadStatistics();
|
||||
// 加载发布记录
|
||||
this.loadPublishRecords();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 设置 tabBar 选中状态
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
this.getTabBar().setData({
|
||||
active: 2
|
||||
});
|
||||
}
|
||||
|
||||
// 每次显示页面时刷新数据
|
||||
this.loadUserInfo();
|
||||
this.loadStatistics();
|
||||
this.loadPublishRecords(true); // 重置加载
|
||||
},
|
||||
|
||||
// 加载用户信息
|
||||
@@ -53,7 +58,7 @@ Page({
|
||||
this.setData({
|
||||
username: userInfo.name,
|
||||
enterpriseName: userInfo.enterprise_name || '',
|
||||
avatar: userInfo.avatar || '',
|
||||
avatar: getImageUrl(userInfo.avatar),
|
||||
userInfo,
|
||||
isBoundXHS: userInfo.is_bound_xhs === 1,
|
||||
xhsAccount: userInfo.xhs_account || ''
|
||||
@@ -69,34 +74,253 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 加载数据统计
|
||||
async loadStatistics() {
|
||||
// 加载发布记录
|
||||
async loadPublishRecords(reset: boolean = false) {
|
||||
// 如果正在加载或没有更多数据,直接返回
|
||||
if (this.data.loading || (!reset && !this.data.hasMore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
// 获取已发布数量(从发布记录API获取)
|
||||
const publishResponse = await EmployeeService.getMyPublishRecords(1, 1);
|
||||
if (publishResponse.code === 200 && publishResponse.data) {
|
||||
const page = reset ? 1 : this.data.currentPage;
|
||||
const response = await EmployeeService.getMyPublishRecords(page, this.data.pageSize);
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const records = response.data.list || [];
|
||||
// 提取封面图片(使用第一张图片)
|
||||
const processedRecords = records.map((record: any) => ({
|
||||
...record,
|
||||
cover_image: record.images && record.images.length > 0
|
||||
? getImageUrl(record.images[0].image_url)
|
||||
: '/images/default-cover.png'
|
||||
}));
|
||||
|
||||
// 如果是重置,替换数据;否则追加数据
|
||||
const newRecords = reset ? processedRecords : [...this.data.publishRecords, ...processedRecords];
|
||||
|
||||
this.setData({
|
||||
publishedCount: publishResponse.data.total || 0
|
||||
publishedCount: response.data.total || 0,
|
||||
publishRecords: newRecords,
|
||||
currentPage: page + 1,
|
||||
hasMore: newRecords.length < (response.data.total || 0),
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: 后端需要添加待审核和已驳回的API
|
||||
// 目前使用默认值
|
||||
this.setData({
|
||||
pendingCount: 0,
|
||||
rejectedCount: 0
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载数据统计失败:', error);
|
||||
// 使用默认值
|
||||
console.error('加载发布记录失败:', error);
|
||||
this.setData({
|
||||
publishedCount: 0,
|
||||
pendingCount: 0,
|
||||
rejectedCount: 0
|
||||
publishRecords: [],
|
||||
loading: false,
|
||||
hasMore: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 滚动到底部加载更多
|
||||
onReachBottom() {
|
||||
this.loadPublishRecords(false);
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadUserInfo();
|
||||
this.loadPublishRecords(true);
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
// 选择头像
|
||||
chooseAvatar() {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const tempFilePath = res.tempFiles[0].tempFilePath;
|
||||
await this.uploadAvatar(tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择图片失败:', err);
|
||||
if (err.errMsg !== 'chooseMedia:fail cancel') {
|
||||
wx.showToast({
|
||||
title: '选择图片失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 上传头像
|
||||
async uploadAvatar(filePath: string) {
|
||||
wx.showLoading({ title: '上传中...', mask: true });
|
||||
|
||||
try {
|
||||
// 获取 token
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) {
|
||||
throw new Error('未登录');
|
||||
}
|
||||
|
||||
// 上传到 OSS
|
||||
const uploadResult = await this.uploadToOSS(filePath);
|
||||
|
||||
if (!uploadResult.url) {
|
||||
throw new Error('上传失败');
|
||||
}
|
||||
|
||||
// 更新用户资料
|
||||
const response = await EmployeeService.updateProfile({
|
||||
avatar: uploadResult.url
|
||||
});
|
||||
|
||||
wx.hideLoading();
|
||||
|
||||
if (response.code === 200) {
|
||||
wx.showToast({
|
||||
title: '更换成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 更新本地头像
|
||||
this.setData({
|
||||
avatar: getImageUrl(uploadResult.url)
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.message || '更新失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('上传头像失败:', error);
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: error.message || '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 上传文件到 OSS
|
||||
async uploadToOSS(filePath: string): Promise<{ url: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = wx.getStorageSync('token');
|
||||
|
||||
wx.uploadFile({
|
||||
url: `${API.baseURL}/api/employee/upload-avatar`,
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.code === 200 && data.data && data.data.url) {
|
||||
resolve({ url: data.data.url });
|
||||
} else {
|
||||
reject(new Error(data.message || '上传失败'));
|
||||
}
|
||||
} catch (err) {
|
||||
reject(new Error('解析响应失败'));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 处理小红书账号点击
|
||||
handleAccountClick() {
|
||||
if (this.data.isBoundXHS) {
|
||||
// 已绑定,显示自定义解绑确认弹窗
|
||||
this.setData({
|
||||
showUnbindDialog: true
|
||||
});
|
||||
} else {
|
||||
// 未绑定,跳转绑定页
|
||||
wx.navigateTo({
|
||||
url: '/pages/profile/platform-bind/platform-bind'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 隐藏弹窗
|
||||
hideDialog() {
|
||||
this.setData({
|
||||
showUnbindDialog: false
|
||||
});
|
||||
},
|
||||
|
||||
// 阻止事件冒泡
|
||||
stopPropagation() {
|
||||
// 空函数,用于阻止事件冒泡
|
||||
},
|
||||
|
||||
// 确认解绑
|
||||
async confirmUnbind() {
|
||||
// 先隐藏弹窗
|
||||
this.setData({
|
||||
showUnbindDialog: false
|
||||
});
|
||||
|
||||
// 执行解绑操作
|
||||
await this.handleUnbind();
|
||||
},
|
||||
|
||||
// 执行解绑操作
|
||||
async handleUnbind() {
|
||||
wx.showLoading({ title: '解绑中...', mask: true });
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.unbindXHS();
|
||||
|
||||
if (response.code === 200) {
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '解绑成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 更新绑定状态
|
||||
this.setData({
|
||||
isBoundXHS: false,
|
||||
xhsAccount: ''
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.message || '解绑失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('解绑失败:', error);
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: error.message || '解绑失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 跳转到发布记录详情
|
||||
viewRecordDetail(e: any) {
|
||||
const { id } = e.currentTarget.dataset;
|
||||
wx.navigateTo({
|
||||
url: `/pages/profile/article-detail/article-detail?id=${id}`
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到发布记录详情(旧方法,保留兼容)
|
||||
goToRecordDetail(e: any) {
|
||||
const { id } = e.currentTarget.dataset;
|
||||
wx.navigateTo({
|
||||
url: `/pages/profile/article-detail/article-detail?id=${id}`
|
||||
});
|
||||
},
|
||||
|
||||
// 返回首页
|
||||
goToHome() {
|
||||
wx.redirectTo({
|
||||
@@ -172,12 +396,31 @@ Page({
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
// 调用后端退出登录接口(删除Redis中token)
|
||||
const token = wx.getStorageSync('token');
|
||||
if (token) {
|
||||
await wx.request({
|
||||
url: `${API.baseURL}/api/logout`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退出登录接口调用失败:', error);
|
||||
// 即使接口失败也继续清理本地状态
|
||||
}
|
||||
|
||||
// 清除本地存储
|
||||
wx.clearStorageSync();
|
||||
// 返回登录页
|
||||
wx.redirectTo({
|
||||
|
||||
// 使用 reLaunch 跳转到登录页,清空页面栈,防止返回
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,103 +1,70 @@
|
||||
<!--pages/profile/profile.wxml-->
|
||||
<view class="page-wrapper">
|
||||
<!-- 用户信息区域 -->
|
||||
<view class="header-section">
|
||||
<view class="user-card">
|
||||
<view class="avatar">
|
||||
<view class="profile-content">
|
||||
<!-- 用户信息 -->
|
||||
<view class="user-info">
|
||||
<view class="avatar" bindtap="chooseAvatar">
|
||||
<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 wx:else class="avatar-placeholder">
|
||||
<view class="avatar-icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-btn">
|
||||
<text class="setting-icon"></text>
|
||||
<view class="user-details">
|
||||
<text class="user-name">{{username}}</text>
|
||||
<text wx:if="{{enterpriseName}}" class="enterprise-name">{{enterpriseName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 小红书账号 -->
|
||||
<view class="account-row" bindtap="handleAccountClick">
|
||||
<text class="row-label">小红书账号</text>
|
||||
<view class="row-right">
|
||||
<text class="row-value">{{isBoundXHS ? xhsAccount : '未绑定'}}</text>
|
||||
<view class="arrow-icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发布记录 -->
|
||||
<view class="section-title">发布记录</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-records" wx:if="{{publishedCount === 0}}">
|
||||
<text>暂无记录</text>
|
||||
</view>
|
||||
|
||||
<!-- 记录网格 -->
|
||||
<view class="records-grid" wx:if="{{publishedCount > 0}}">
|
||||
<view class="record-card" wx:for="{{publishRecords}}" wx:key="id" bindtap="viewRecordDetail" data-id="{{item.id}}">
|
||||
<view class="record-image">
|
||||
<image src="{{item.cover_image}}" mode="aspectFill" />
|
||||
</view>
|
||||
<text class="record-title">{{item.title}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载提示 -->
|
||||
<view class="loading-more" wx:if="{{loading}}">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
<view class="no-more" wx:if="{{!hasMore && publishedCount > 0}}">
|
||||
<text class="no-more-text">没有更多了</text>
|
||||
</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">
|
||||
<!-- 底部退出登录按钮(仅开发环境显示) -->
|
||||
<view class="logout-wrapper" wx:if="{{showLogoutBtn}}">
|
||||
<button class="logout-btn" bindtap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
|
||||
<!-- 解绑确认半屏弹窗 -->
|
||||
<view class="dialog-mask" wx:if="{{showUnbindDialog}}" bindtap="hideDialog" catchtouchmove="stopPropagation">
|
||||
<view class="dialog-container" catchtap="stopPropagation">
|
||||
<view class="dialog-title">确定解绑</view>
|
||||
<view class="dialog-content">解绑账号后将无法继续使用它登录小红书账号</view>
|
||||
<view class="dialog-buttons">
|
||||
<button class="dialog-btn confirm-btn" bindtap="confirmUnbind">确定解绑</button>
|
||||
<button class="dialog-btn cancel-btn" bindtap="hideDialog">取消</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -2,271 +2,219 @@
|
||||
page {
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
background: #f8f8f8;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
padding-bottom: 200rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f8f8f8;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* 用户信息区域 */
|
||||
.header-section {
|
||||
width: 100%;
|
||||
background: #07c160;
|
||||
padding: 50rpx 30rpx 70rpx;
|
||||
box-sizing: border-box;
|
||||
.profile-content {
|
||||
padding: 32rpx;
|
||||
padding-top: 48rpx;
|
||||
padding-bottom: 180rpx; /* 为底部按钮留出空间 */
|
||||
}
|
||||
|
||||
.user-card {
|
||||
/* 用户信息 */
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
background: white;
|
||||
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||||
margin-right: 24rpx;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.avatar:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 60rpx;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.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;
|
||||
.avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.setting-icon {
|
||||
font-size: 36rpx;
|
||||
.avatar-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><circle cx="12" cy="8" r="4" stroke="%23999" stroke-width="2" fill="none"/><path d="M4 20C4 16.6863 7.58172 14 12 14C16.4183 14 20 16.6863 20 20" stroke="%23999" stroke-width="2" stroke-linecap="round" fill="none"/></svg>');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/* 数据概览 */
|
||||
.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;
|
||||
.user-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.enterprise-name {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 小红书账号 */
|
||||
.account-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #07c160;
|
||||
margin-bottom: 8rpx;
|
||||
.row-label {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
.row-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.row-value {
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
width: 16rpx;
|
||||
height: 28rpx;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1L7 7L1 13" stroke="%23C0C0C0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/* 发布记录 */
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-records {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 600rpx;
|
||||
color: #999;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.records-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.record-image {
|
||||
width: 100%;
|
||||
padding-top: 100%; /* 1:1 比例 */
|
||||
position: relative;
|
||||
background: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.record-image image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-top: 16rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
/* 加载提示 */
|
||||
.loading-more,
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
.loading-text,
|
||||
.no-more-text {
|
||||
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%;
|
||||
/* 退出登录按钮 */
|
||||
.logout-wrapper {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 16rpx 32rpx;
|
||||
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
|
||||
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;
|
||||
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: white;
|
||||
padding: 32rpx;
|
||||
text-align: center;
|
||||
border: none;
|
||||
font-size: 30rpx;
|
||||
color: #07c160;
|
||||
font-weight: 600;
|
||||
border-radius: 0;
|
||||
color: #ff3b30;
|
||||
border: 2rpx solid #ff3b30;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.logout-btn::after {
|
||||
@@ -274,151 +222,103 @@ page {
|
||||
}
|
||||
|
||||
.logout-btn:active {
|
||||
opacity: 0.8;
|
||||
background: #fafafa;
|
||||
background: #fff5f5;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 底部Tab栏 */
|
||||
.bottom-tabbar {
|
||||
/* 半屏弹窗样式 */
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background: white;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
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);
|
||||
align-items: flex-end;
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
.dialog-container {
|
||||
width: 100%;
|
||||
background: white;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding: 48rpx 32rpx;
|
||||
padding-bottom: calc(48rpx + env(safe-area-inset-bottom));
|
||||
animation: slideUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.dialog-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.dialog-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12rpx 0;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-icon {
|
||||
transform: scale(1.1);
|
||||
.dialog-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 6rpx;
|
||||
transition: all 0.3s ease;
|
||||
.cancel-btn {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-icon {
|
||||
filter: drop-shadow(0 2rpx 8rpx rgba(255, 36, 66, 0.3));
|
||||
.cancel-btn:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
.confirm-btn {
|
||||
color: #ff3b30;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #07c160;
|
||||
font-weight: 600;
|
||||
.confirm-btn:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 图标样式 */
|
||||
.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;
|
||||
/* 动画效果 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.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>');
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "已发布文章",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarTextStyle": "white",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"backgroundTextStyle": "light"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// pages/profile/published/published.ts
|
||||
import { EmployeeService, PublishRecord as ApiPublishRecord } from '../../../services/employee';
|
||||
import { getImageUrl } from '../../../utils/util';
|
||||
|
||||
interface Article {
|
||||
id: number;
|
||||
@@ -20,7 +21,8 @@ interface Article {
|
||||
|
||||
Page({
|
||||
data: {
|
||||
articles: [] as Article[]
|
||||
articles: [] as Article[],
|
||||
hasLoaded: false // 是否已加载过数据
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -28,7 +30,15 @@ Page({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadArticles();
|
||||
// 仅在未加载过数据时加载
|
||||
if (!this.data.hasLoaded) {
|
||||
this.loadArticles();
|
||||
}
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadArticles(true);
|
||||
},
|
||||
|
||||
// 初始化模拟数据
|
||||
@@ -90,7 +100,7 @@ Page({
|
||||
},
|
||||
|
||||
// 加载文章列表
|
||||
async loadArticles() {
|
||||
async loadArticles(isPullRefresh: boolean = false) {
|
||||
try {
|
||||
// 从后端API获取发布记录
|
||||
const response = await EmployeeService.getMyPublishRecords(1, 50);
|
||||
@@ -110,7 +120,11 @@ Page({
|
||||
title: record.title,
|
||||
content: '', // 列表页不需要显示完整内容
|
||||
tags: record.tags || [],
|
||||
images: record.images || [],
|
||||
images: (record.images || []).map((img: any) => ({
|
||||
...img,
|
||||
image_url: getImageUrl(img.image_url),
|
||||
image_thumb_url: getImageUrl(img.image_thumb_url || img.image_url)
|
||||
})),
|
||||
createTime: record.publish_time,
|
||||
status: 'published'
|
||||
};
|
||||
@@ -119,14 +133,27 @@ Page({
|
||||
console.log('转换后的文章列表:', apiArticles);
|
||||
|
||||
this.setData({
|
||||
articles: apiArticles
|
||||
articles: apiArticles,
|
||||
hasLoaded: true // 标记已加载
|
||||
});
|
||||
|
||||
console.log('setData后的articles:', this.data.articles);
|
||||
|
||||
if (isPullRefresh) {
|
||||
wx.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载发布记录失败:', error);
|
||||
} finally {
|
||||
if (isPullRefresh) {
|
||||
wx.stopPullDownRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果API失败,尝试使用本地数据
|
||||
@@ -141,8 +168,13 @@ Page({
|
||||
});
|
||||
|
||||
this.setData({
|
||||
articles: formattedArticles
|
||||
articles: formattedArticles,
|
||||
hasLoaded: true
|
||||
});
|
||||
|
||||
if (isPullRefresh) {
|
||||
wx.stopPullDownRefresh();
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
|
||||
@@ -9,7 +9,8 @@ Page({
|
||||
codeSending: false,
|
||||
countdown: 0,
|
||||
agreedToTerms: false,
|
||||
showAgreementModal: false
|
||||
showAgreementModal: false,
|
||||
loginLoading: false // 绑定加载状态,防止重复提交
|
||||
},
|
||||
|
||||
// 选择国家区号
|
||||
@@ -82,8 +83,12 @@ Page({
|
||||
});
|
||||
|
||||
try {
|
||||
// 调用后端API发送验证码
|
||||
const response = await EmployeeService.sendXHSCode(phone);
|
||||
console.log('开始发送验证码,手机号:', phone);
|
||||
|
||||
// 调用后端API发送验证码(showLoading=true,显示加载提示)
|
||||
const response = await EmployeeService.sendXHSCode(phone, true);
|
||||
|
||||
console.log('发送验证码响应:', response);
|
||||
|
||||
if (response.code === 200) {
|
||||
wx.showToast({
|
||||
@@ -94,7 +99,8 @@ Page({
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: response.message || '发送失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
this.setData({
|
||||
codeSending: false
|
||||
@@ -102,10 +108,18 @@ Page({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送验证码失败:', error);
|
||||
|
||||
// 检查是否是超时错误
|
||||
const errorMsg = error && typeof error === 'object' && 'message' in error
|
||||
? (error as any).message
|
||||
: '发送失败,请重试';
|
||||
|
||||
wx.showToast({
|
||||
title: '发送失败,请重试',
|
||||
icon: 'none'
|
||||
title: errorMsg,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
this.setData({
|
||||
codeSending: false
|
||||
});
|
||||
@@ -114,7 +128,7 @@ Page({
|
||||
|
||||
// 倒计时
|
||||
startCountdown() {
|
||||
let countdown = 60;
|
||||
let countdown = 180;
|
||||
|
||||
this.setData({
|
||||
countdown: countdown,
|
||||
@@ -169,7 +183,18 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复提交(关键优化)
|
||||
if (this.data.loginLoading) {
|
||||
console.log('绑定请求进行中,忽略重复点击');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置 loading 状态
|
||||
this.setData({
|
||||
loginLoading: true
|
||||
});
|
||||
|
||||
// 调用后端API绑定小红书
|
||||
const response = await EmployeeService.bindXHS(phone, code);
|
||||
|
||||
@@ -191,14 +216,27 @@ Page({
|
||||
|
||||
// 延迟500毫秒后返回,让用户看到成功提示
|
||||
setTimeout(() => {
|
||||
// 重置 loading 状态
|
||||
this.setData({
|
||||
loginLoading: false
|
||||
});
|
||||
// 返回上一页(社交账号绑定页)
|
||||
wx.navigateBack({
|
||||
delta: 1
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
// 绑定失败,重置 loading
|
||||
this.setData({
|
||||
loginLoading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('绑定小红书失败:', error);
|
||||
// 重置 loading 状态
|
||||
this.setData({
|
||||
loginLoading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -239,5 +277,15 @@ Page({
|
||||
wx.navigateTo({
|
||||
url: '/pages/agreement/privacy-policy/privacy-policy'
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到个人信息保护政策
|
||||
goToPersonalInfo(e: any) {
|
||||
e.stopPropagation();
|
||||
// TODO: 创建个人信息保护政策页面
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,76 +1,66 @@
|
||||
<!--pages/profile/xhs-login/xhs-login.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 背景动画 -->
|
||||
<view class="background-gradient"></view>
|
||||
<view class="platform-header">
|
||||
<view class="platform-icon xiaohongshu"></view>
|
||||
<view class="platform-text">
|
||||
<text class="platform-name">小红书</text>
|
||||
<text class="platform-tip">绑定后可同步发布内容</text>
|
||||
</view>
|
||||
</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 class="bind-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="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 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="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="login">
|
||||
确认
|
||||
</button>
|
||||
|
||||
<!-- 协议勾选 -->
|
||||
<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 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>
|
||||
|
||||
@@ -1,76 +1,36 @@
|
||||
/* pages/profile/xhs-login/xhs-login.wxss */
|
||||
page {
|
||||
background: #07c160;
|
||||
background: #f8f8f8;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
padding: 30rpx;
|
||||
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 {
|
||||
background: white;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx;
|
||||
margin-bottom: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 48rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.platform-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #07c160;
|
||||
border-radius: 20rpx;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4rpx 16rpx rgba(255, 36, 66, 0.3);
|
||||
}
|
||||
|
||||
.platform-icon.xiaohongshu {
|
||||
background: #07c160;
|
||||
}
|
||||
|
||||
.platform-text {
|
||||
@@ -92,36 +52,36 @@ page {
|
||||
}
|
||||
|
||||
/* 表单 */
|
||||
.login-form {
|
||||
width: 100%;
|
||||
.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: 96rpx;
|
||||
height: 88rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
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;
|
||||
padding: 0 20rpx;
|
||||
height: 100%;
|
||||
border-right: 1rpx solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
@@ -150,17 +110,10 @@ page {
|
||||
.code-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 96rpx;
|
||||
height: 88rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.code-input-wrapper:focus-within {
|
||||
border-color: #07c160;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
@@ -180,68 +133,59 @@ page {
|
||||
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 {
|
||||
.bind-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
height: 88rpx;
|
||||
background: #07c160;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 48rpx;
|
||||
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;
|
||||
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 {
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 提示弹窗 */
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 员工端API服务
|
||||
import { get, post } from '../utils/request';
|
||||
import { get, post, put, del } from '../utils/request';
|
||||
import { API } from '../config/api';
|
||||
|
||||
// 员工信息接口
|
||||
@@ -11,6 +11,8 @@ export interface EmployeeInfo {
|
||||
enterprise_id: number;
|
||||
enterprise_name: string;
|
||||
avatar?: string;
|
||||
nickname?: string;
|
||||
email?: string;
|
||||
is_bound_xhs: number;
|
||||
xhs_account: string;
|
||||
xhs_phone: string;
|
||||
@@ -71,10 +73,10 @@ export class EmployeeService {
|
||||
/**
|
||||
* 发送小红书验证码
|
||||
*/
|
||||
static async sendXHSCode(xhsPhone: string) {
|
||||
static async sendXHSCode(xhsPhone: string, showLoading = true) {
|
||||
return post(API.xhs.sendCode, {
|
||||
xhs_phone: xhsPhone
|
||||
});
|
||||
}, showLoading);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,11 +89,23 @@ export class EmployeeService {
|
||||
/**
|
||||
* 绑定小红书账号
|
||||
*/
|
||||
static async bindXHS(xhsPhone: string, code: string) {
|
||||
static async bindXHS(xhsPhone: string, code: string, showLoading = false) {
|
||||
return post<{ xhs_account: string }>(API.employee.bindXHS, {
|
||||
xhs_phone: xhsPhone,
|
||||
code
|
||||
});
|
||||
}, showLoading);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取小红书绑定状态
|
||||
*/
|
||||
static async getBindXHSStatus() {
|
||||
return get<{
|
||||
status: string;
|
||||
message?: string;
|
||||
xhs_account?: string;
|
||||
error?: string;
|
||||
}>(API.employee.bindXHSStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,12 +116,26 @@ export class EmployeeService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品列表(公开接口,不需要登录)
|
||||
* 更新个人资料(昵称、邮箱、头像)
|
||||
*/
|
||||
static async getProducts() {
|
||||
return get<{ list: Product[] }>(API.public.products);
|
||||
static async updateProfile(data: {
|
||||
nickname?: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
}) {
|
||||
return put(API.employee.profile, data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取产品列表(支持分页)
|
||||
*/
|
||||
static async getProducts(page: number = 1, pageSize: number = 10) {
|
||||
return get<{ list: Product[]; has_more: boolean }>(API.public.products, {
|
||||
page,
|
||||
page_size: pageSize
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可领取文案列表
|
||||
*/
|
||||
@@ -219,4 +247,102 @@ export class EmployeeService {
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文案内容(标题、正文)
|
||||
*/
|
||||
static async updateArticleContent(articleId: number, data: {
|
||||
title: string;
|
||||
content: string;
|
||||
}) {
|
||||
return put(`/api/employee/article/${articleId}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑发布记录
|
||||
*/
|
||||
static async updatePublishRecord(recordId: number, data: {
|
||||
title?: string;
|
||||
content?: string;
|
||||
images?: Array<{
|
||||
image_url: string;
|
||||
image_thumb_url?: string;
|
||||
sort_order: number;
|
||||
keywords_name?: string;
|
||||
}>;
|
||||
tags?: string[];
|
||||
}) {
|
||||
return put(`/api/employee/publish-record/${recordId}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新发布种草内容
|
||||
*/
|
||||
static async republishRecord(recordId: number) {
|
||||
return post(`/api/employee/publish-record/${recordId}/republish`, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文案图片
|
||||
*/
|
||||
static async addArticleImage(articleId: number, data: {
|
||||
image_url: string;
|
||||
image_thumb_url?: string;
|
||||
keywords_name?: string;
|
||||
}) {
|
||||
return post(`/api/employee/article/${articleId}/image`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文案图片
|
||||
*/
|
||||
static async deleteArticleImage(imageId: number) {
|
||||
return del(`/api/employee/article/image/${imageId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文案图片排序
|
||||
*/
|
||||
static async updateArticleImagesOrder(articleId: number, imageOrders: Array<{
|
||||
id: number;
|
||||
sort_order: number;
|
||||
}>) {
|
||||
return put(`/api/employee/article/${articleId}/images/order`, {
|
||||
image_orders: imageOrders
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
*/
|
||||
static async uploadImage(filePath: string) {
|
||||
return new Promise<{
|
||||
code: number;
|
||||
message: string;
|
||||
data: {
|
||||
image_url: string;
|
||||
image_thumb_url: string;
|
||||
};
|
||||
}>((resolve, reject) => {
|
||||
const token = wx.getStorageSync('token');
|
||||
|
||||
wx.uploadFile({
|
||||
url: `${API.baseURL}/api/employee/upload/image`,
|
||||
filePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
resolve(data);
|
||||
} catch (e) {
|
||||
reject(new Error('解析响应失败'));
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,3 +93,27 @@ export const getCoverIcon = (topicTypeId: number) => {
|
||||
]
|
||||
return icons[topicTypeId % icons.length]
|
||||
}
|
||||
|
||||
// OSS 图片URL前缀
|
||||
const OSS_PREFIX = 'https://bxmkb-beijing.oss-cn-beijing.aliyuncs.com/Images/'
|
||||
|
||||
// 处理图片URL - 自动补全OSS前缀
|
||||
export const getImageUrl = (imagePath: string | null | undefined): string => {
|
||||
// 如果为空,返回默认图片
|
||||
if (!imagePath) {
|
||||
return '/images/default-cover.png'
|
||||
}
|
||||
|
||||
// 如果已经是完整的URL(http或https开头),直接返回
|
||||
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
|
||||
return imagePath
|
||||
}
|
||||
|
||||
// 如果是本地路径(以/开头),直接返回
|
||||
if (imagePath.startsWith('/')) {
|
||||
return imagePath
|
||||
}
|
||||
|
||||
// 其他情况,补全OSS前缀
|
||||
return OSS_PREFIX + imagePath
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user