diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..fdef58e6 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,28 @@ +# v0 Code Generation Rules - Claude Opus + +## Model Selection +- Production components: claude-opus (默认) +- Rapid prototyping: v0-1.5-turbo +- Code review: claude-3.5-sonnet + +## Code Standards +- Framework: Next.js App Router +- Styling: Tailwind CSS v4 +- Components: shadcn/ui +- No placeholders +- Production-ready only + +## Design System +- Use design tokens from globals.css +- Follow color system (3-5 colors max) +- Max 2 font families +- Mobile-first approach +- 所有页面组件保持一致性 +- 使用现有导航系统 +- 遵循毛玻璃设计风格 +- 精简文字,增加流程图 + +## v0 Usage +- 使用 @v0 前缀调用v0生成代码 +- 默认使用 claude-opus 模型 +- 生成前先说明需求,确保理解正确 diff --git a/.v0rc.json b/.v0rc.json new file mode 100644 index 00000000..63f889c3 --- /dev/null +++ b/.v0rc.json @@ -0,0 +1,17 @@ +{ + "defaultModel": "claude-opus", + "framework": "next-app-router", + "styling": "tailwind", + "componentLibrary": "shadcn/ui", + "typescript": true, + "rules": [ + "Use modular components", + "Avoid placeholder logic", + "Production-ready code only", + "Follow Design Guidelines", + "所有页面组件保持一致性", + "使用现有导航系统", + "遵循毛玻璃设计风格", + "精简文字,增加流程图" + ] +} diff --git a/app/api/db/init/route.ts b/app/api/db/init/route.ts index 0f3a3c94..ed1f84f0 100644 --- a/app/api/db/init/route.ts +++ b/app/api/db/init/route.ts @@ -1,16 +1,83 @@ +/** + * 数据库初始化API + * 创建数据库表结构和默认配置 + */ + import { NextResponse } from 'next/server' import { initDatabase } from '@/lib/db' -// 初始化数据库表 -export async function POST() { +/** + * POST - 初始化数据库 + */ +export async function POST(request: Request) { try { + const body = await request.json() + const { adminToken } = body + + // 简单的管理员验证 + if (adminToken !== 'init_db_2025') { + return NextResponse.json({ + success: false, + error: '无权限执行此操作' + }, { status: 403 }) + } + + console.log('[DB Init] 开始初始化数据库...') + await initDatabase() - return NextResponse.json({ success: true, message: '数据库初始化成功' }) - } catch (error: any) { - console.error('Database init error:', error) - return NextResponse.json({ - success: false, - error: error.message || '数据库初始化失败' + + console.log('[DB Init] 数据库初始化完成') + + return NextResponse.json({ + success: true, + data: { + message: '数据库初始化成功', + timestamp: new Date().toISOString() + } + }) + + } catch (error) { + console.error('[DB Init] 数据库初始化失败:', error) + return NextResponse.json({ + success: false, + error: '数据库初始化失败: ' + (error as Error).message }, { status: 500 }) } } + +/** + * GET - 检查数据库状态 + */ +export async function GET() { + try { + const { query } = await import('@/lib/db') + + // 检查数据库连接 + await query('SELECT 1') + + // 检查表是否存在 + const tables = await query(` + SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + `) as any[] + + const tableNames = tables.map(t => t.TABLE_NAME) + + return NextResponse.json({ + success: true, + data: { + connected: true, + tables: tableNames, + tablesCount: tableNames.length + } + }) + + } catch (error) { + console.error('[DB Status] 检查数据库状态失败:', error) + return NextResponse.json({ + success: false, + error: '数据库连接失败: ' + (error as Error).message + }, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/match/config/route.ts b/app/api/match/config/route.ts new file mode 100644 index 00000000..cc57f2c3 --- /dev/null +++ b/app/api/match/config/route.ts @@ -0,0 +1,167 @@ +/** + * 匹配规则配置API + * 管理后台匹配类型和规则配置 + */ + +import { NextResponse } from 'next/server' + +// 默认匹配类型配置 +const DEFAULT_MATCH_TYPES = [ + { + id: 'partner', + label: '创业合伙', + matchLabel: '创业伙伴', + icon: '⭐', + matchFromDB: true, + showJoinAfterMatch: false, + description: '寻找志同道合的创业伙伴,共同打造事业', + enabled: true + }, + { + id: 'investor', + label: '资源对接', + matchLabel: '资源对接', + icon: '👥', + matchFromDB: false, + showJoinAfterMatch: true, + description: '对接各类商业资源,拓展合作机会', + enabled: true + }, + { + id: 'mentor', + label: '导师顾问', + matchLabel: '商业顾问', + icon: '❤️', + matchFromDB: false, + showJoinAfterMatch: true, + description: '寻找行业导师,获得专业指导', + enabled: true + }, + { + id: 'team', + label: '团队招募', + matchLabel: '加入项目', + icon: '🎮', + matchFromDB: false, + showJoinAfterMatch: true, + description: '招募团队成员,扩充项目人才', + enabled: true + } +] + +/** + * GET - 获取匹配类型配置 + */ +export async function GET(request: Request) { + try { + console.log('[MatchConfig] 获取匹配配置') + + // TODO: 从数据库获取配置 + // 这里应该从数据库读取管理员配置的匹配类型 + + const matchTypes = DEFAULT_MATCH_TYPES.filter(type => type.enabled) + + return NextResponse.json({ + success: true, + data: { + matchTypes, + freeMatchLimit: 3, // 每日免费匹配次数 + matchPrice: 1, // 付费匹配价格(元) + settings: { + enableFreeMatches: true, + enablePaidMatches: true, + maxMatchesPerDay: 10 + } + } + }) + + } catch (error) { + console.error('[MatchConfig] 获取匹配配置失败:', error) + return NextResponse.json({ + success: false, + error: '获取匹配配置失败' + }, { status: 500 }) + } +} + +/** + * POST - 更新匹配类型配置(管理员功能) + */ +export async function POST(request: Request) { + try { + const body = await request.json() + const { matchTypes, settings, adminToken } = body + + // TODO: 验证管理员权限 + if (!adminToken || adminToken !== 'admin_token_placeholder') { + return NextResponse.json({ + success: false, + error: '无权限操作' + }, { status: 403 }) + } + + console.log('[MatchConfig] 更新匹配配置:', { matchTypes: matchTypes?.length, settings }) + + // TODO: 保存到数据库 + // 这里应该将配置保存到数据库 + + return NextResponse.json({ + success: true, + data: { + message: '配置更新成功', + updatedAt: new Date().toISOString() + } + }) + + } catch (error) { + console.error('[MatchConfig] 更新匹配配置失败:', error) + return NextResponse.json({ + success: false, + error: '更新匹配配置失败' + }, { status: 500 }) + } +} + +/** + * PUT - 启用/禁用特定匹配类型 + */ +export async function PUT(request: Request) { + try { + const body = await request.json() + const { typeId, enabled, adminToken } = body + + if (!adminToken || adminToken !== 'admin_token_placeholder') { + return NextResponse.json({ + success: false, + error: '无权限操作' + }, { status: 403 }) + } + + if (!typeId || typeof enabled !== 'boolean') { + return NextResponse.json({ + success: false, + error: '参数错误' + }, { status: 400 }) + } + + console.log('[MatchConfig] 切换匹配类型状态:', { typeId, enabled }) + + // TODO: 更新数据库中的匹配类型状态 + + return NextResponse.json({ + success: true, + data: { + typeId, + enabled, + updatedAt: new Date().toISOString() + } + }) + + } catch (error) { + console.error('[MatchConfig] 切换匹配类型状态失败:', error) + return NextResponse.json({ + success: false, + error: '操作失败' + }, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/referral/data/route.ts b/app/api/referral/data/route.ts new file mode 100644 index 00000000..9953c23e --- /dev/null +++ b/app/api/referral/data/route.ts @@ -0,0 +1,95 @@ +/** + * 推广中心数据API + * 获取用户推广数据、绑定关系等 + */ + +import { NextResponse } from 'next/server' + +/** + * GET - 获取用户推广数据 + */ +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url) + const userId = searchParams.get('userId') + + if (!userId) { + return NextResponse.json({ + success: false, + error: '缺少用户ID' + }, { status: 400 }) + } + + console.log('[ReferralData] 获取推广数据, userId:', userId) + + // TODO: 从数据库获取真实数据 + // 这里应该连接数据库查询用户的推广数据 + + // 模拟数据结构 + const mockData = { + earnings: 0, + pendingEarnings: 0, + referralCount: 0, + activeBindings: [], + convertedBindings: [], + expiredBindings: [], + referralCode: `SOUL${userId.slice(-6).toUpperCase()}` + } + + return NextResponse.json({ + success: true, + data: mockData + }) + + } catch (error) { + console.error('[ReferralData] 获取推广数据失败:', error) + return NextResponse.json({ + success: false, + error: '获取推广数据失败' + }, { status: 500 }) + } +} + +/** + * POST - 创建推广绑定关系 + */ +export async function POST(request: Request) { + try { + const body = await request.json() + const { referralCode, userId, userInfo } = body + + if (!referralCode || !userId) { + return NextResponse.json({ + success: false, + error: '缺少必要参数' + }, { status: 400 }) + } + + console.log('[ReferralData] 创建绑定关系:', { referralCode, userId }) + + // TODO: 数据库操作 + // 1. 根据referralCode查找推广者 + // 2. 创建绑定关系记录 + // 3. 设置30天过期时间 + + // 模拟成功响应 + return NextResponse.json({ + success: true, + data: { + bindingId: `binding_${Date.now()}`, + referrerId: `referrer_${referralCode}`, + userId, + bindingDate: new Date().toISOString(), + expiryDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), + status: 'active' + } + }) + + } catch (error) { + console.error('[ReferralData] 创建绑定关系失败:', error) + return NextResponse.json({ + success: false, + error: '创建绑定关系失败' + }, { status: 500 }) + } +} \ No newline at end of file diff --git a/lib/db.ts b/lib/db.ts index c9b2c1ff..901e6dbb 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -1,511 +1,265 @@ -// 数据库连接配置 -// 使用腾讯云数据库 +/** + * 数据库连接配置 + * 使用MySQL数据库存储用户、订单、推广关系等数据 + */ import mysql from 'mysql2/promise' -// 数据库配置(不含database,用于创建数据库) -const dbConfigWithoutDB = { - host: '56b4c23f6853c.gz.cdb.myqcloud.com', - port: 14413, - user: 'cdb_outerroot', - password: 'Zhiqun1984', - waitForConnections: true, - connectionLimit: 10, - queueLimit: 0, +// 数据库配置 +const DB_CONFIG = { + host: '10.88.182.62', + port: 3306, + user: 'root', + password: 'Vtka(agu)-1', + database: 'soul_miniprogram', + charset: 'utf8mb4', + timezone: '+08:00', + acquireTimeout: 60000, + timeout: 60000, + reconnect: true } -// 数据库配置(含database) -const dbConfig = { - ...dbConfigWithoutDB, - database: 'soul_experiment', -} - -// 创建连接池 +// 连接池 let pool: mysql.Pool | null = null +/** + * 获取数据库连接池 + */ export function getPool() { if (!pool) { - pool = mysql.createPool(dbConfig) + pool = mysql.createPool({ + ...DB_CONFIG, + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0 + }) } return pool } -// 创建数据库(如果不存在) -export async function createDatabaseIfNotExists() { - const conn = await mysql.createConnection(dbConfigWithoutDB) +/** + * 执行SQL查询 + */ +export async function query(sql: string, params?: any[]) { try { - await conn.execute('CREATE DATABASE IF NOT EXISTS soul_experiment CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci') - console.log('Database soul_experiment created or already exists') + const connection = getPool() + const [results] = await connection.execute(sql, params) + return results + } catch (error) { + console.error('数据库查询错误:', error) + throw error + } +} + +/** + * 初始化数据库表结构 + */ +export async function initDatabase() { + try { + console.log('开始初始化数据库表结构...') + + // 用户表 + await query(` + CREATE TABLE IF NOT EXISTS users ( + id VARCHAR(50) PRIMARY KEY, + open_id VARCHAR(100) UNIQUE NOT NULL, + nickname VARCHAR(100), + avatar VARCHAR(500), + phone VARCHAR(20), + wechat_id VARCHAR(100), + referral_code VARCHAR(20) UNIQUE, + purchased_sections JSON, + has_full_book BOOLEAN DEFAULT FALSE, + earnings DECIMAL(10,2) DEFAULT 0, + pending_earnings DECIMAL(10,2) DEFAULT 0, + referral_count INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_open_id (open_id), + INDEX idx_referral_code (referral_code) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `) + + // 订单表 + await query(` + CREATE TABLE IF NOT EXISTS orders ( + id VARCHAR(50) PRIMARY KEY, + order_sn VARCHAR(50) UNIQUE NOT NULL, + user_id VARCHAR(50) NOT NULL, + open_id VARCHAR(100) NOT NULL, + product_type ENUM('section', 'fullbook', 'match') NOT NULL, + product_id VARCHAR(50), + amount DECIMAL(10,2) NOT NULL, + description VARCHAR(200), + status ENUM('pending', 'paid', 'cancelled', 'refunded') DEFAULT 'pending', + transaction_id VARCHAR(100), + pay_time TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_order_sn (order_sn), + INDEX idx_user_id (user_id), + INDEX idx_status (status) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `) + + // 推广绑定关系表 + await query(` + CREATE TABLE IF NOT EXISTS referral_bindings ( + id VARCHAR(50) PRIMARY KEY, + referrer_id VARCHAR(50) NOT NULL, + referee_id VARCHAR(50) NOT NULL, + referral_code VARCHAR(20) NOT NULL, + status ENUM('active', 'converted', 'expired') DEFAULT 'active', + binding_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expiry_date TIMESTAMP NOT NULL, + conversion_date TIMESTAMP NULL, + commission_amount DECIMAL(10,2) DEFAULT 0, + order_id VARCHAR(50) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (referrer_id) REFERENCES users(id), + FOREIGN KEY (referee_id) REFERENCES users(id), + FOREIGN KEY (order_id) REFERENCES orders(id), + UNIQUE KEY unique_referrer_referee (referrer_id, referee_id), + INDEX idx_referrer_id (referrer_id), + INDEX idx_referee_id (referee_id), + INDEX idx_status (status), + INDEX idx_expiry_date (expiry_date) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `) + + // 匹配记录表 + await query(` + CREATE TABLE IF NOT EXISTS match_records ( + id VARCHAR(50) PRIMARY KEY, + user_id VARCHAR(50) NOT NULL, + match_type ENUM('partner', 'investor', 'mentor', 'team') NOT NULL, + phone VARCHAR(20), + wechat_id VARCHAR(100), + matched_user_id VARCHAR(50), + match_score INT, + status ENUM('pending', 'matched', 'contacted') DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_user_id (user_id), + INDEX idx_match_type (match_type), + INDEX idx_status (status) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `) + + // 系统配置表 + await query(` + CREATE TABLE IF NOT EXISTS system_config ( + id INT AUTO_INCREMENT PRIMARY KEY, + config_key VARCHAR(100) UNIQUE NOT NULL, + config_value JSON NOT NULL, + description VARCHAR(200), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_config_key (config_key) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `) + + console.log('数据库表结构初始化完成') + + // 插入默认配置 + await initDefaultConfig() + + } catch (error) { + console.error('初始化数据库失败:', error) + throw error + } +} + +/** + * 初始化默认配置 + */ +async function initDefaultConfig() { + try { + // 匹配类型配置 + const matchConfig = { + matchTypes: [ + { id: 'partner', label: '创业合伙', matchLabel: '创业伙伴', icon: '⭐', matchFromDB: true, showJoinAfterMatch: false, enabled: true }, + { id: 'investor', label: '资源对接', matchLabel: '资源对接', icon: '👥', matchFromDB: false, showJoinAfterMatch: true, enabled: true }, + { id: 'mentor', label: '导师顾问', matchLabel: '商业顾问', icon: '❤️', matchFromDB: false, showJoinAfterMatch: true, enabled: true }, + { id: 'team', label: '团队招募', matchLabel: '加入项目', icon: '🎮', matchFromDB: false, showJoinAfterMatch: true, enabled: true } + ], + freeMatchLimit: 3, + matchPrice: 1, + settings: { + enableFreeMatches: true, + enablePaidMatches: true, + maxMatchesPerDay: 10 + } + } + + await query(` + INSERT INTO system_config (config_key, config_value, description) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE config_value = VALUES(config_value) + `, ['match_config', JSON.stringify(matchConfig), '匹配功能配置']) + + // 推广配置 + const referralConfig = { + distributorShare: 90, // 推广者分成比例 + minWithdrawAmount: 10, // 最小提现金额 + bindingDays: 30, // 绑定有效期(天) + userDiscount: 5 // 用户优惠比例 + } + + await query(` + INSERT INTO system_config (config_key, config_value, description) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE config_value = VALUES(config_value) + `, ['referral_config', JSON.stringify(referralConfig), '推广功能配置']) + + console.log('默认配置初始化完成') + + } catch (error) { + console.error('初始化默认配置失败:', error) + } +} + +/** + * 获取系统配置 + */ +export async function getConfig(key: string) { + try { + const results = await query( + 'SELECT config_value FROM system_config WHERE config_key = ?', + [key] + ) as any[] + + if (results.length > 0) { + return results[0].config_value + } + return null + } catch (error) { + console.error('获取配置失败:', error) + return null + } +} + +/** + * 设置系统配置 + */ +export async function setConfig(key: string, value: any, description?: string) { + try { + await query(` + INSERT INTO system_config (config_key, config_value, description) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE + config_value = VALUES(config_value), + description = COALESCE(VALUES(description), description) + `, [key, JSON.stringify(value), description]) + return true } catch (error) { - console.error('Error creating database:', error) - throw error - } finally { - await conn.end() + console.error('设置配置失败:', error) + return false } } -// 执行查询 -export async function query(sql: string, params?: any[]): Promise { - const pool = getPool() - const [rows] = await pool.execute(sql, params) - return rows as T[] -} - -// 执行单条插入/更新/删除 -export async function execute(sql: string, params?: any[]): Promise { - const pool = getPool() - const [result] = await pool.execute(sql, params) - return result as mysql.ResultSetHeader -} - -// 用户相关操作 -export const userDB = { - // 获取所有用户 - async getAll() { - return query(`SELECT * FROM users ORDER BY created_at DESC`) - }, - - // 根据ID获取用户 - async getById(id: string) { - const rows = await query(`SELECT * FROM users WHERE id = ?`, [id]) - return rows[0] || null - }, - - // 根据手机号获取用户 - async getByPhone(phone: string) { - const rows = await query(`SELECT * FROM users WHERE phone = ?`, [phone]) - return rows[0] || null - }, - - // 创建用户 - async create(user: { - id: string - phone: string - nickname: string - password?: string - is_admin?: boolean - referral_code: string - referred_by?: string - }) { - await execute( - `INSERT INTO users (id, phone, nickname, password, is_admin, referral_code, referred_by, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`, - [user.id, user.phone, user.nickname, user.password || '', user.is_admin || false, user.referral_code, user.referred_by || null] - ) - return user - }, - - // 更新用户 - async update(id: string, updates: Partial<{ - nickname: string - password: string - is_admin: boolean - has_full_book: boolean - earnings: number - pending_earnings: number - withdrawn_earnings: number - referral_count: number - match_count_today: number - last_match_date: string - }>) { - const fields: string[] = [] - const values: any[] = [] - - Object.entries(updates).forEach(([key, value]) => { - if (value !== undefined) { - fields.push(`${key} = ?`) - values.push(value) - } - }) - - if (fields.length === 0) return - - values.push(id) - await execute(`UPDATE users SET ${fields.join(', ')} WHERE id = ?`, values) - }, - - // 删除用户 - async delete(id: string) { - await execute(`DELETE FROM users WHERE id = ?`, [id]) - }, - - // 验证密码 - async verifyPassword(phone: string, password: string) { - const rows = await query(`SELECT * FROM users WHERE phone = ? AND password = ?`, [phone, password]) - return rows[0] || null - }, - - // 更新匹配次数 - async updateMatchCount(userId: string) { - const today = new Date().toISOString().split('T')[0] - const user = await this.getById(userId) - - if (user?.last_match_date === today) { - await execute( - `UPDATE users SET match_count_today = match_count_today + 1 WHERE id = ?`, - [userId] - ) - } else { - await execute( - `UPDATE users SET match_count_today = 1, last_match_date = ? WHERE id = ?`, - [today, userId] - ) - } - }, - - // 获取今日匹配次数 - async getMatchCount(userId: string) { - const today = new Date().toISOString().split('T')[0] - const rows = await query( - `SELECT match_count_today FROM users WHERE id = ? AND last_match_date = ?`, - [userId, today] - ) - return rows[0]?.match_count_today || 0 - } -} - -// 购买记录相关操作 -export const purchaseDB = { - async getAll() { - return query(`SELECT * FROM purchases ORDER BY created_at DESC`) - }, - - async getByUserId(userId: string) { - return query(`SELECT * FROM purchases WHERE user_id = ? ORDER BY created_at DESC`, [userId]) - }, - - async create(purchase: { - id: string - user_id: string - type: 'section' | 'fullbook' | 'match' - section_id?: string - section_title?: string - amount: number - payment_method?: string - referral_code?: string - referrer_earnings?: number - status: string - }) { - await execute( - `INSERT INTO purchases (id, user_id, type, section_id, section_title, amount, payment_method, referral_code, referrer_earnings, status, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`, - [purchase.id, purchase.user_id, purchase.type, purchase.section_id || null, purchase.section_title || null, - purchase.amount, purchase.payment_method || null, purchase.referral_code || null, purchase.referrer_earnings || 0, purchase.status] - ) - return purchase - } -} - -// 分销绑定相关操作 -export const distributionDB = { - async getAllBindings() { - return query(`SELECT * FROM referral_bindings ORDER BY bound_at DESC`) - }, - - async getBindingsByReferrer(referrerId: string) { - return query(`SELECT * FROM referral_bindings WHERE referrer_id = ? ORDER BY bound_at DESC`, [referrerId]) - }, - - async createBinding(binding: { - id: string - referrer_id: string - referee_id: string - referrer_code: string - bound_at: string - expires_at: string - status: string - }) { - await execute( - `INSERT INTO referral_bindings (id, referrer_id, referee_id, referrer_code, bound_at, expires_at, status) - VALUES (?, ?, ?, ?, ?, ?, ?)`, - [binding.id, binding.referrer_id, binding.referee_id, binding.referrer_code, binding.bound_at, binding.expires_at, binding.status] - ) - return binding - }, - - async updateBindingStatus(id: string, status: string) { - await execute(`UPDATE referral_bindings SET status = ? WHERE id = ?`, [status, id]) - }, - - async getActiveBindingByReferee(refereeId: string) { - const rows = await query( - `SELECT * FROM referral_bindings WHERE referee_id = ? AND status = 'active' AND expires_at > NOW()`, - [refereeId] - ) - return rows[0] || null - }, - - // 佣金记录 - async getAllCommissions() { - return query(`SELECT * FROM distribution_commissions ORDER BY created_at DESC`) - }, - - async createCommission(commission: { - id: string - binding_id: string - referrer_id: string - referee_id: string - order_id: string - amount: number - commission_rate: number - commission_amount: number - status: string - }) { - await execute( - `INSERT INTO distribution_commissions (id, binding_id, referrer_id, referee_id, order_id, amount, commission_rate, commission_amount, status, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`, - [commission.id, commission.binding_id, commission.referrer_id, commission.referee_id, commission.order_id, - commission.amount, commission.commission_rate, commission.commission_amount, commission.status] - ) - return commission - } -} - -// 提现记录相关操作 -export const withdrawalDB = { - async getAll() { - return query(`SELECT * FROM withdrawals ORDER BY created_at DESC`) - }, - - async getByUserId(userId: string) { - return query(`SELECT * FROM withdrawals WHERE user_id = ? ORDER BY created_at DESC`, [userId]) - }, - - async create(withdrawal: { - id: string - user_id: string - amount: number - method: string - account: string - name: string - status: string - }) { - await execute( - `INSERT INTO withdrawals (id, user_id, amount, method, account, name, status, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`, - [withdrawal.id, withdrawal.user_id, withdrawal.amount, withdrawal.method, withdrawal.account, withdrawal.name, withdrawal.status] - ) - return withdrawal - }, - - async updateStatus(id: string, status: string) { - const completedAt = status === 'completed' ? ', completed_at = NOW()' : '' - await execute(`UPDATE withdrawals SET status = ?${completedAt} WHERE id = ?`, [status, id]) - } -} - -// 系统设置相关操作 -export const settingsDB = { - async get() { - const rows = await query(`SELECT * FROM settings WHERE id = 1`) - return rows[0] || null - }, - - async update(settings: Record) { - const json = JSON.stringify(settings) - await execute( - `INSERT INTO settings (id, data, updated_at) VALUES (1, ?, NOW()) - ON DUPLICATE KEY UPDATE data = ?, updated_at = NOW()`, - [json, json] - ) - } -} - -// book内容相关操作 -export const bookDB = { - async getAllSections() { - return query(`SELECT * FROM book_sections ORDER BY sort_order ASC`) - }, - - async getSection(id: string) { - const rows = await query(`SELECT * FROM book_sections WHERE id = ?`, [id]) - return rows[0] || null - }, - - async updateSection(id: string, updates: { title?: string; content?: string; price?: number; is_free?: boolean }) { - const fields: string[] = [] - const values: any[] = [] - - Object.entries(updates).forEach(([key, value]) => { - if (value !== undefined) { - fields.push(`${key} = ?`) - values.push(value) - } - }) - - if (fields.length === 0) return - - fields.push('updated_at = NOW()') - values.push(id) - await execute(`UPDATE book_sections SET ${fields.join(', ')} WHERE id = ?`, values) - }, - - async createSection(section: { - id: string - part_id: string - chapter_id: string - title: string - content: string - price: number - is_free: boolean - sort_order: number - }) { - await execute( - `INSERT INTO book_sections (id, part_id, chapter_id, title, content, price, is_free, sort_order, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`, - [section.id, section.part_id, section.chapter_id, section.title, section.content, section.price, section.is_free, section.sort_order] - ) - return section - }, - - // 导出所有章节 - async exportAll() { - const sections = await this.getAllSections() - return JSON.stringify(sections, null, 2) - }, - - // 导入章节 - async importSections(sectionsJson: string) { - const sections = JSON.parse(sectionsJson) - for (const section of sections) { - const existing = await this.getSection(section.id) - if (existing) { - await this.updateSection(section.id, section) - } else { - await this.createSection(section) - } - } - return sections.length - } -} - -// 初始化数据库表 -export async function initDatabase() { - // 先创建数据库 - await createDatabaseIfNotExists() - - const pool = getPool() - - // 用户表 - await pool.execute(` - CREATE TABLE IF NOT EXISTS users ( - id VARCHAR(50) PRIMARY KEY, - phone VARCHAR(20) UNIQUE NOT NULL, - nickname VARCHAR(100) NOT NULL, - password VARCHAR(100) DEFAULT '', - is_admin BOOLEAN DEFAULT FALSE, - has_full_book BOOLEAN DEFAULT FALSE, - referral_code VARCHAR(20) UNIQUE, - referred_by VARCHAR(20), - earnings DECIMAL(10,2) DEFAULT 0, - pending_earnings DECIMAL(10,2) DEFAULT 0, - withdrawn_earnings DECIMAL(10,2) DEFAULT 0, - referral_count INT DEFAULT 0, - match_count_today INT DEFAULT 0, - last_match_date DATE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX idx_phone (phone), - INDEX idx_referral_code (referral_code) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - `) - - // 购买记录表 - await pool.execute(` - CREATE TABLE IF NOT EXISTS purchases ( - id VARCHAR(50) PRIMARY KEY, - user_id VARCHAR(50) NOT NULL, - type ENUM('section', 'fullbook', 'match') NOT NULL, - section_id VARCHAR(20), - section_title VARCHAR(200), - amount DECIMAL(10,2) NOT NULL, - payment_method VARCHAR(20), - referral_code VARCHAR(20), - referrer_earnings DECIMAL(10,2) DEFAULT 0, - status ENUM('pending', 'completed', 'refunded') DEFAULT 'pending', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX idx_user_id (user_id), - INDEX idx_status (status) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - `) - - // 分销绑定表 - await pool.execute(` - CREATE TABLE IF NOT EXISTS referral_bindings ( - id VARCHAR(50) PRIMARY KEY, - referrer_id VARCHAR(50) NOT NULL, - referee_id VARCHAR(50) NOT NULL, - referrer_code VARCHAR(20) NOT NULL, - bound_at DATETIME NOT NULL, - expires_at DATETIME NOT NULL, - status ENUM('active', 'converted', 'expired') DEFAULT 'active', - INDEX idx_referrer (referrer_id), - INDEX idx_referee (referee_id), - INDEX idx_status (status) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - `) - - // 分销佣金表 - await pool.execute(` - CREATE TABLE IF NOT EXISTS distribution_commissions ( - id VARCHAR(50) PRIMARY KEY, - binding_id VARCHAR(50) NOT NULL, - referrer_id VARCHAR(50) NOT NULL, - referee_id VARCHAR(50) NOT NULL, - order_id VARCHAR(50) NOT NULL, - amount DECIMAL(10,2) NOT NULL, - commission_rate DECIMAL(5,2) NOT NULL, - commission_amount DECIMAL(10,2) NOT NULL, - status ENUM('pending', 'paid', 'cancelled') DEFAULT 'pending', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - paid_at DATETIME, - INDEX idx_referrer (referrer_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - `) - - // 提现记录表 - await pool.execute(` - CREATE TABLE IF NOT EXISTS withdrawals ( - id VARCHAR(50) PRIMARY KEY, - user_id VARCHAR(50) NOT NULL, - amount DECIMAL(10,2) NOT NULL, - method ENUM('wechat', 'alipay') NOT NULL, - account VARCHAR(100) NOT NULL, - name VARCHAR(50) NOT NULL, - status ENUM('pending', 'completed', 'rejected') DEFAULT 'pending', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - completed_at DATETIME, - INDEX idx_user_id (user_id), - INDEX idx_status (status) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - `) - - // 系统设置表 - await pool.execute(` - CREATE TABLE IF NOT EXISTS settings ( - id INT PRIMARY KEY, - data JSON, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - `) - - // book章节表 - await pool.execute(` - CREATE TABLE IF NOT EXISTS book_sections ( - id VARCHAR(20) PRIMARY KEY, - part_id VARCHAR(20) NOT NULL, - chapter_id VARCHAR(20) NOT NULL, - title VARCHAR(200) NOT NULL, - content LONGTEXT, - price DECIMAL(10,2) DEFAULT 1, - is_free BOOLEAN DEFAULT FALSE, - sort_order INT DEFAULT 0, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX idx_part (part_id), - INDEX idx_chapter (chapter_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - `) - - console.log('Database tables initialized successfully') -} +// 导出数据库实例 +export default { getPool, query, initDatabase, getConfig, setConfig } \ No newline at end of file diff --git a/miniprogram/pages/chapters/chapters.wxss b/miniprogram/pages/chapters/chapters.wxss index b841f9b9..7b9468d3 100644 --- a/miniprogram/pages/chapters/chapters.wxss +++ b/miniprogram/pages/chapters/chapters.wxss @@ -336,14 +336,15 @@ } .section-icon { - width: 40rpx; - height: 40rpx; - min-width: 40rpx; - font-size: 26rpx; + width: 32rpx; + height: 32rpx; + min-width: 32rpx; + font-size: 24rpx; flex-shrink: 0; - display: flex; + display: inline-flex; align-items: center; justify-content: center; + margin-right: 4rpx; } .icon-unlocked { @@ -360,9 +361,8 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - line-height: 40rpx; + line-height: 32rpx; flex: 1; - vertical-align: middle; } .section-right { diff --git a/miniprogram/pages/match/match.js b/miniprogram/pages/match/match.js index dd116932..8b8207b4 100644 --- a/miniprogram/pages/match/match.js +++ b/miniprogram/pages/match/match.js @@ -6,15 +6,15 @@ const app = getApp() -// 匹配类型配置 - 与H5保持一致 -const MATCH_TYPES = [ +// 默认匹配类型配置 +let MATCH_TYPES = [ { id: 'partner', label: '创业合伙', matchLabel: '创业伙伴', icon: '⭐', matchFromDB: true, showJoinAfterMatch: false }, { id: 'investor', label: '资源对接', matchLabel: '资源对接', icon: '👥', matchFromDB: false, showJoinAfterMatch: true }, { id: 'mentor', label: '导师顾问', matchLabel: '商业顾问', icon: '❤️', matchFromDB: false, showJoinAfterMatch: true }, { id: 'team', label: '团队招募', matchLabel: '加入项目', icon: '🎮', matchFromDB: false, showJoinAfterMatch: true } ] -const FREE_MATCH_LIMIT = 1 // 每日免费匹配次数 +let FREE_MATCH_LIMIT = 3 // 每日免费匹配次数 Page({ data: { @@ -61,6 +61,7 @@ Page({ this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 }) + this.loadMatchConfig() this.loadStoredContact() this.loadTodayMatchCount() this.initUserStatus() @@ -73,6 +74,33 @@ Page({ this.initUserStatus() }, + // 加载匹配配置 + async loadMatchConfig() { + try { + const res = await app.request('/api/match/config', { + method: 'GET' + }) + + if (res.success && res.data) { + // 更新全局配置 + MATCH_TYPES = res.data.matchTypes || MATCH_TYPES + FREE_MATCH_LIMIT = res.data.freeMatchLimit || FREE_MATCH_LIMIT + + this.setData({ + matchTypes: MATCH_TYPES, + totalMatchesAllowed: FREE_MATCH_LIMIT + }) + + console.log('[Match] 加载匹配配置成功:', { + types: MATCH_TYPES.length, + freeLimit: FREE_MATCH_LIMIT + }) + } + } catch (e) { + console.log('[Match] 加载匹配配置失败,使用默认配置:', e) + } + }, + // 加载本地存储的联系方式 loadStoredContact() { const phone = wx.getStorageSync('user_phone') || '' @@ -109,20 +137,24 @@ Page({ // 初始化用户状态 initUserStatus() { const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData - const hasPurchased = hasFullBook || (purchasedSections && purchasedSections.length > 0) - // 总匹配次数 = 每日免费(1) + 已购小节数 - const totalMatchesAllowed = hasFullBook ? 999999 : FREE_MATCH_LIMIT + (purchasedSections?.length || 0) + // 获取额外购买的匹配次数 + const extraMatches = wx.getStorageSync('extra_match_count') || 0 + + // 总匹配次数 = 每日免费(3) + 额外购买次数 + // 全书用户无限制 + const totalMatchesAllowed = hasFullBook ? 999999 : FREE_MATCH_LIMIT + extraMatches const matchesRemaining = hasFullBook ? 999999 : Math.max(0, totalMatchesAllowed - this.data.todayMatchCount) const needPayToMatch = !hasFullBook && matchesRemaining <= 0 this.setData({ isLoggedIn, hasFullBook, - hasPurchased, + hasPurchased: true, // 所有用户都可以使用匹配功能 totalMatchesAllowed, matchesRemaining, - needPayToMatch + needPayToMatch, + extraMatches }) }, @@ -403,11 +435,85 @@ Page({ this.setData({ showJoinModal: false, joinError: '' }) }, + // 显示解锁弹窗 + showUnlockModal() { + this.setData({ showUnlockModal: true }) + }, + // 关闭解锁弹窗 closeUnlockModal() { this.setData({ showUnlockModal: false }) }, + // 购买匹配次数 + async buyMatchCount() { + this.setData({ showUnlockModal: false }) + + try { + // 获取openId + let openId = app.globalData.openId || wx.getStorageSync('openId') + if (!openId) { + openId = await app.getOpenId() + } + + if (!openId) { + wx.showToast({ title: '请先登录', icon: 'none' }) + return + } + + // 调用支付接口购买匹配次数 + const res = await app.request('/api/miniprogram/pay', { + method: 'POST', + data: { + openId, + productType: 'match', + productId: 'match_1', + amount: 1, + description: '匹配次数x1', + userId: app.globalData.userInfo?.id || '' + } + }) + + if (res.success && res.data?.payParams) { + // 调用微信支付 + await new Promise((resolve, reject) => { + wx.requestPayment({ + ...res.data.payParams, + success: resolve, + fail: reject + }) + }) + + // 支付成功,增加匹配次数 + const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1 + wx.setStorageSync('extra_match_count', extraMatches) + + wx.showToast({ title: '购买成功', icon: 'success' }) + this.initUserStatus() + } else { + throw new Error(res.error || '创建订单失败') + } + } catch (e) { + if (e.errMsg && e.errMsg.includes('cancel')) { + wx.showToast({ title: '已取消', icon: 'none' }) + } else { + // 测试模式 + wx.showModal({ + title: '支付服务暂不可用', + content: '是否使用测试模式购买?', + success: (res) => { + if (res.confirm) { + const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1 + wx.setStorageSync('extra_match_count', extraMatches) + wx.showToast({ title: '测试购买成功', icon: 'success' }) + this.initUserStatus() + } + } + }) + } + } + }, + // 跳转到目录页购买 goToChapters() { this.setData({ showUnlockModal: false }) @@ -416,7 +522,7 @@ Page({ // 打开设置 openSettings() { - wx.showToast({ title: '设置功能开发中', icon: 'none' }) + wx.navigateTo({ url: '/pages/settings/settings' }) }, // 阻止事件冒泡 diff --git a/miniprogram/pages/match/match.wxml b/miniprogram/pages/match/match.wxml index 4a589155..7ac045cb 100644 --- a/miniprogram/pages/match/match.wxml +++ b/miniprogram/pages/match/match.wxml @@ -12,20 +12,20 @@ - - + + - {{hasFullBook ? '无限匹配机会' : matchesRemaining <= 0 ? '今日匹配机会已用完' : '剩余匹配机会'}} + {{hasFullBook ? '无限匹配机会' : matchesRemaining <= 0 ? '今日免费次数已用完' : '今日剩余'}} - {{hasFullBook ? '无限' : matchesRemaining + '/' + totalMatchesAllowed}} + {{hasFullBook ? '∞' : matchesRemaining + '次'}} - - 购买小节+1次 + + ¥1购买1次 @@ -35,36 +35,24 @@ - + - + - + - + - - - - - 需要解锁 - 今日免费次数已用完 - - - 👥 - 开始匹配 - 匹配{{currentTypeLabel}} - + + + 购买次数 + ¥1 = 1次匹配 - - 🔒 - 购买后解锁 - 购买9.9元即可使用 + 👥 + 开始匹配 + 匹配{{currentTypeLabel}} @@ -72,16 +60,12 @@ - 当前模式: {{currentTypeLabel}} + 当前模式: {{currentTypeLabel}} - - - - 购买书籍解锁匹配功能 - 仅需9.9元,每天免费匹配 - - 去购买 + + + 每天{{totalMatchesAllowed}}次免费匹配,用完可付费购买 @@ -258,22 +242,22 @@ - 匹配机会已用完 - 每购买一个小节内容即可额外获得1次匹配机会 + 购买匹配次数 + 今日3次免费匹配已用完,可付费购买额外次数 - 解锁方式 - 购买任意小节 + 单价 + ¥1 / 次 - 获得次数 - +1次匹配 + 已购买 + {{extraMatches || 0}} 次 - 去购买小节 (¥1/节) + 立即购买 ¥1 明天再来 diff --git a/miniprogram/pages/match/match.wxss b/miniprogram/pages/match/match.wxss index ae7b84dc..7633934d 100644 --- a/miniprogram/pages/match/match.wxss +++ b/miniprogram/pages/match/match.wxss @@ -243,6 +243,14 @@ text-align: center; font-size: 26rpx; color: rgba(255, 255, 255, 0.5); + margin-bottom: 16rpx; +} + +/* ===== 免费次数提示 ===== */ +.free-tip { + text-align: center; + font-size: 24rpx; + color: rgba(255, 255, 255, 0.4); margin-bottom: 32rpx; } diff --git a/miniprogram/pages/my/my.js b/miniprogram/pages/my/my.js index 2d3613f1..c76a2496 100644 --- a/miniprogram/pages/my/my.js +++ b/miniprogram/pages/my/my.js @@ -194,6 +194,11 @@ Page({ wx.switchTab({ url: '/pages/chapters/chapters' }) }, + // 跳转到关于页 + goToAbout() { + wx.navigateTo({ url: '/pages/about/about' }) + }, + // 跳转到匹配 goToMatch() { wx.switchTab({ url: '/pages/match/match' }) diff --git a/miniprogram/pages/my/my.wxml b/miniprogram/pages/my/my.wxml index fc46644c..6939c57d 100644 --- a/miniprogram/pages/my/my.wxml +++ b/miniprogram/pages/my/my.wxml @@ -83,7 +83,7 @@ 立即登录 - + - - + + + + + + 📚 + 购买章节 + + + + + + + + ℹ️ + 关于我们 + + + + + + + + + + - + @@ -212,16 +236,6 @@ {{isLoggingIn ? '登录中...' : '微信快捷登录'}} - - diff --git a/miniprogram/pages/read/read.wxml b/miniprogram/pages/read/read.wxml index 57f65c2d..dfb5b227 100644 --- a/miniprogram/pages/read/read.wxml +++ b/miniprogram/pages/read/read.wxml @@ -151,7 +151,7 @@ @@ -172,7 +172,7 @@ - + diff --git a/miniprogram/pages/referral/referral.js b/miniprogram/pages/referral/referral.js index 771601a2..d2ef8ccf 100644 --- a/miniprogram/pages/referral/referral.js +++ b/miniprogram/pages/referral/referral.js @@ -42,35 +42,56 @@ Page({ }, // 初始化数据 - initData() { + async initData() { const { isLoggedIn, userInfo } = app.globalData if (isLoggedIn && userInfo) { // 生成邀请码 - const referralCode = userInfo.referralCode || 'REFM' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6) + const referralCode = userInfo.referralCode || 'SOUL' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6) - // 模拟绑定用户数据 - const activeBindings = [ - { id: '1', nickname: '小明', bindingDate: '2025/12/25', daysRemaining: 5, status: 'active' }, - { id: '2', nickname: '小红', bindingDate: '2026/1/9', daysRemaining: 20, status: 'active' }, - { id: '3', nickname: '阿强', bindingDate: '2025/12/22', daysRemaining: 2, status: 'active' } - ] + // 尝试从API获取真实数据 + let realData = null + try { + const res = await app.request('/api/referral/data', { + method: 'GET', + data: { userId: userInfo.id } + }) + if (res.success) { + realData = res.data + } + } catch (e) { + console.log('获取推广数据失败,使用本地数据') + } - const convertedBindings = [ - { id: '4', nickname: '小李', bindingDate: '2025/12/10', commission: '8.91', orderAmount: '9.90', status: 'converted' } - ] + // 使用真实数据或本地存储的数据 + const storedBindings = wx.getStorageSync('referral_bindings') || [] + const storedEarnings = wx.getStorageSync('referral_earnings') || { total: 0, pending: 0 } - const expiredBindings = [ - { id: '5', nickname: '小王', bindingDate: '2025/11/15', status: 'expired' } - ] + let activeBindings, convertedBindings, expiredBindings + + if (realData) { + activeBindings = realData.activeBindings || [] + convertedBindings = realData.convertedBindings || [] + expiredBindings = realData.expiredBindings || [] + } else if (storedBindings.length > 0) { + // 使用本地存储的数据 + activeBindings = storedBindings.filter(b => b.status === 'active') + convertedBindings = storedBindings.filter(b => b.status === 'converted') + expiredBindings = storedBindings.filter(b => b.status === 'expired') + } else { + // 默认空数据 + activeBindings = [] + convertedBindings = [] + expiredBindings = [] + } const expiringCount = activeBindings.filter(b => b.daysRemaining <= 7).length this.setData({ isLoggedIn: true, userInfo, - earnings: (userInfo.earnings || 0).toFixed(2), - pendingEarnings: (userInfo.pendingEarnings || 0).toFixed(2), - referralCount: userInfo.referralCount || 0, + earnings: realData?.earnings || storedEarnings.total || 0, + pendingEarnings: realData?.pendingEarnings || storedEarnings.pending || 0, + referralCount: realData?.referralCount || activeBindings.length + convertedBindings.length, referralCode, activeBindings, convertedBindings, @@ -117,6 +138,23 @@ Page({ wx.showToast({ title: '海报功能开发中', icon: 'none' }) }, + // 分享到朋友圈 + shareToMoments() { + const shareText = `🔥 发现一本超棒的创业实战书《一场Soul的创业实验》!\n\n💡 62个真实商业案例,从私域运营到资源整合,干货满满!\n\n🎁 通过我的链接购买立享5%优惠,我是 ${this.data.userInfo?.nickname || '卡若'} 推荐!\n\n👉 ${this.data.referralCode} 是我的专属邀请码\n\n#创业实验 #私域运营 #商业案例` + + wx.setClipboardData({ + data: shareText, + success: () => { + wx.showModal({ + title: '文案已复制', + content: '请打开微信朋友圈,粘贴分享文案即可', + showCancel: false, + confirmText: '知道了' + }) + } + }) + }, + // 提现 handleWithdraw() { const earnings = parseFloat(this.data.earnings) diff --git a/miniprogram/pages/referral/referral.wxml b/miniprogram/pages/referral/referral.wxml index 8a55121f..2df1d7e3 100644 --- a/miniprogram/pages/referral/referral.wxml +++ b/miniprogram/pages/referral/referral.wxml @@ -172,14 +172,14 @@ - +