lex-wxl 1 týždeň pred
rodič
commit
c60bfc404a

+ 85 - 0
CLAUDE.md

@@ -0,0 +1,85 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+This is a **WeChat Mini Program** (微信小程序) for music digital education activation, allowing users to purchase music course subscriptions and instruments.
+
+## Development Commands
+
+### Build npm packages
+```bash
+# WeChat Developer Tools: Tools > Build npm
+# Or via CLI equivalent in project.config.json packNpmManually settings
+```
+
+### Environment Switching
+Edit `miniprogram/config.ts` to change environment:
+```typescript
+const environmentVariable = "online"; // "dev" | "test" | "online"
+```
+
+### WeChat Developer Tools
+- Open project in WeChat Developer Tools
+- Project uses TypeScript and Less compilers (configured in project.config.json)
+- `miniprogram/` is the source root
+
+## Architecture
+
+### Directory Structure
+```
+miniprogram/
+├── api/           # API request functions (login.ts, new.ts)
+├── components/    # Reusable components (navigation-bar, service, apply-refound)
+├── pages/         # Page modules (index, login, orders, protocol, download, buyerInformation)
+├── plugins/       # Request plugin for HTTP calls
+├── utils/         # Utilities (EventBus, util, qrcode)
+├── app.ts         # App entry with global state
+├── app.json       # Page routes and global config
+├── config.ts      # Environment-based API URLs
+```
+
+### Key Patterns
+
+**Request Layer**: `plugins/request.ts` handles all API calls with:
+- Automatic token injection from `wx.getStorageSync("token")`
+- Open APIs (`/open/`, `/code/`) skip token requirement
+- Error code handling with login redirect on 5000/403
+- Form data, JSON, and multipart content types
+
+**Global State**: `app.ts` manages:
+- `globalData.baseUrl` - API base URL from config.ts
+- `globalData.appId` - WeChat Mini Program AppID
+- `globalData.isLogin` - Login status
+- `globalData.userInfo` - User profile
+
+**Pages**: Each page follows WeChat Mini Program structure:
+- `.ts` - Page logic with `Page({...})`
+- `.wxml` - Template markup
+- `.less` - Styles
+- `.json` - Page config (usingComponents, etc.)
+
+**Components**: Custom components using `Component({...})`:
+- `navigation-bar` - Custom header with safe area handling
+- `service` - Customer service floating button
+- `apply-refound` - Refund application modal
+
+**EventBus**: `utils/EventBus.ts` provides simple pub/sub for cross-component communication.
+
+### API Endpoints
+All APIs under `/edu-app/` prefix:
+- Authentication: `/userlogin`, `/user/getUserInfo`
+- Products: `/open/shop/product`, `/open/shop/instruments`
+- Orders: `/userPaymentOrder/*` (executeOrder, detail, refund, etc.)
+- Address: `/userReceiveAddress/*`
+- Beneficiary: `/userBeneficiary/*`
+
+### UI Framework
+Uses **Vant Weapp** (`@vant/weapp`) component library. Components registered in page `.json` files.
+
+## TypeScript Configuration
+Strict mode enabled with:
+- `noImplicitAny`, `strictNullChecks`, `strict`
+- Target: ES2020
+- Type definitions in `typings/`

+ 3 - 2
miniprogram/app.json

@@ -8,7 +8,8 @@
     "pages/orders/order-result",
     "pages/protocol/register",
     "pages/download/download",
-    "pages/buyerInformation/index"
+    "pages/buyerInformation/index",
+    "pages/chat/chat"
   ],
   "window": {
     "navigationBarTextStyle": "black",
@@ -26,4 +27,4 @@
   "componentFramework": "glass-easel",
   "sitemapLocation": "sitemap.json",
   "lazyCodeLoading": "requiredComponents"
-}
+}

+ 3 - 2
miniprogram/components/service/service.ts

@@ -46,8 +46,9 @@ Component({
       // 
     },
     onShow() {
-      this.setData({
-        popShow: true
+      // 跳转到AI客服页面
+      wx.navigateTo({
+        url: '/pages/chat/chat'
       })
     },
     onTouchStart(e: any) {

+ 1 - 1
miniprogram/config.ts

@@ -1,4 +1,4 @@
-const environmentVariable = "online";
+const environmentVariable = "test";
 const apiUrlInfo = {
   dev: "https://dev.kt.colexiu.com",
   test: "https://test.kt.colexiu.com",

+ 6 - 0
miniprogram/pages/chat/chat.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {
+    "navigation-bar": "/components/navigation-bar/navigation-bar"
+  },
+  "navigationBarTitleText": "音乐数字AI客服"
+}

+ 307 - 0
miniprogram/pages/chat/chat.less

@@ -0,0 +1,307 @@
+page {
+  background: linear-gradient(180deg, #E3F2FD 0%, #E8F5E9 50%, #FFF3E0 100%);
+  height: 100%;
+}
+
+.chat-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  padding-top: 88px;
+  box-sizing: border-box;
+}
+
+.chat-scroll {
+  flex: 1;
+  overflow-y: auto;
+}
+
+.message-list {
+  padding: 16px;
+}
+
+.message-row {
+  display: flex;
+  margin-bottom: 16px;
+  align-items: flex-start;
+}
+
+.ai-row {
+  justify-content: flex-start;
+}
+
+.user-row {
+  justify-content: flex-end;
+}
+
+/* AI头像 - 蓝色机器人 */
+.avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+.ai-avatar {
+  margin-right: 10px;
+  background: linear-gradient(135deg, #42A5F5 0%, #64B5F6 100%);
+  border: 2px solid #fff;
+  box-shadow: 0 2px 8px rgba(66, 165, 245, 0.3);
+}
+
+.ai-avatar-image {
+  width: 70%;
+  height: 70%;
+  object-fit: contain;
+}
+
+.user-avatar {
+  margin-left: 10px;
+  background: linear-gradient(135deg, #66BB6A 0%, #81C784 100%);
+  border: 2px solid #fff;
+  box-shadow: 0 2px 8px rgba(102, 187, 106, 0.3);
+}
+
+.avatar-text {
+  color: #fff;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.message-content {
+  max-width: 75%;
+}
+
+/* AI消息气泡 - 白色 */
+.bubble {
+  padding: 12px 16px;
+  border-radius: 18px;
+  word-wrap: break-word;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+}
+
+.ai-bubble {
+  background-color: #FFFFFF;
+  border-bottom-left-radius: 4px;
+}
+
+/* 用户消息气泡 - 蓝色 */
+.user-bubble {
+  background: linear-gradient(135deg, #2196F3 0%, #42A5F5 100%);
+  color: #FFFFFF;
+  border-bottom-right-radius: 4px;
+}
+
+.message-text {
+  font-size: 15px;
+  line-height: 1.5;
+  color: inherit;
+}
+
+/* FAQ卡片 - 白色圆角 */
+.faq-card {
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 16px;
+  padding: 14px 12px;
+  margin-top: 8px;
+  width: 280px;
+  backdrop-filter: blur(10px);
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  box-shadow: 0 4px 16px rgba(33, 150, 243, 0.1);
+}
+
+.faq-header {
+  margin-bottom: 10px;
+  padding-left: 2px;
+}
+
+.faq-title {
+  font-size: 15px;
+  font-weight: 700;
+  color: #2196F3;
+  letter-spacing: 0.5px;
+}
+
+.faq-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.faq-item {
+  display: flex;
+  align-items: center;
+  padding: 10px 12px;
+  background-color: #FFFFFF;
+  border-radius: 10px;
+  box-shadow: 0 1px 3px rgba(0,0,0,0.04);
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.faq-item:active {
+  transform: scale(0.98);
+  background-color: #F5F9FC;
+}
+
+.faq-icon {
+  width: 18px;
+  height: 18px;
+  background: linear-gradient(135deg, #2196F3 0%, #42A5F5 100%);
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 10px;
+  flex-shrink: 0;
+}
+
+.faq-icon-text {
+  color: #fff;
+  font-size: 10px;
+  font-weight: 700;
+}
+
+.faq-text {
+  flex: 1;
+  font-size: 13px;
+  color: #333;
+  font-weight: 500;
+}
+
+.faq-arrow {
+  font-size: 14px;
+  color: #CCC;
+  margin-left: 4px;
+}
+
+/* 用户图片 */
+.user-image {
+  max-width: 180px;
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.user-image image {
+  width: 100%;
+  max-height: 180px;
+  display: block;
+}
+
+/* 输入区域 - 圆角灰色 */
+.input-area {
+  background-color: #FFFFFF;
+  border-top: 1px solid rgba(0,0,0,0.04);
+  padding: 10px 16px;
+  padding-bottom: calc(10px + env(safe-area-inset-bottom));
+}
+
+.input-container {
+  display: flex;
+  align-items: center;
+  background-color: #F5F7FA;
+  border-radius: 24px;
+  padding: 6px 10px;
+}
+
+.input-wrapper {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  padding: 0 4px;
+}
+
+.voice-btn {
+  width: 30px;
+  height: 30px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 4px;
+}
+
+.voice-icon {
+  font-size: 16px;
+  color: #666;
+}
+
+.chat-input {
+  flex: 1;
+  height: 34px;
+  font-size: 15px;
+  color: #333;
+  background: transparent;
+}
+
+.placeholder {
+  color: #AAA;
+  font-size: 15px;
+}
+
+.image-btn {
+  width: 34px;
+  height: 34px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #FFFFFF;
+  border-radius: 50%;
+  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
+  margin-left: 4px;
+}
+
+.image-icon {
+  font-size: 18px;
+  color: #666;
+}
+
+/* 加载中状态 */
+.loading-row {
+  align-items: center;
+}
+
+.loading-bubble {
+  background-color: #FFFFFF;
+  border-radius: 16px;
+  padding: 12px 18px;
+  border-bottom-left-radius: 4px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.06);
+}
+
+.loading-dots {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.dot {
+  width: 7px;
+  height: 7px;
+  background: linear-gradient(135deg, #2196F3 0%, #42A5F5 100%);
+  border-radius: 50%;
+  animation: bounce 1.4s ease-in-out infinite both;
+}
+
+.dot:nth-child(1) {
+  animation-delay: -0.32s;
+}
+
+.dot:nth-child(2) {
+  animation-delay: -0.16s;
+}
+
+@keyframes bounce {
+  0%, 80%, 100% {
+    transform: scale(0.6);
+    opacity: 0.5;
+  }
+  40% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}

+ 260 - 0
miniprogram/pages/chat/chat.ts

@@ -0,0 +1,260 @@
+interface Message {
+  id: string;
+  type: 'ai' | 'user' | 'reply';
+  contentType: 'text' | 'image' | 'faq';
+  content: string;
+  questions?: string[];
+  timestamp: number;
+}
+
+interface FAQItem {
+  question: string;
+  answer: string;
+}
+
+const FAQ_LIST: FAQItem[] = [
+  { question: '产品价格是多少?', answer: '我们的产品价格根据不同的套餐有所不同。基础版¥99/月,专业版¥199/月,企业版¥499/月。您可以根据自己的需求选择合适的套餐。' },
+  { question: '如何购买课程?', answer: '您可以通过以下步骤购买课程:1. 在首页浏览课程列表;2. 点击心仪的课程进入详情页;3. 点击"立即购买"按钮;4. 选择支付方式完成支付。' },
+  { question: '支持哪些设备?', answer: '我们的产品支持多种设备:iOS设备(iPhone、iPad)、Android设备(手机、平板)、Windows电脑、Mac电脑,以及主流的智能电视。' },
+  { question: '有没有试用版本?', answer: '有的!我们为所有新用户提供7天免费试用期。试用期间您可以体验全部专业版功能,无需绑定支付方式。试用期结束后,您可以选择是否订阅。' },
+  { question: '售后服务政策', answer: '我们提供完善的售后服务:1. 7天无理由退款;2. 产品问题30天内免费换新;3. 终身技术支持;4. 专属客服一对一服务。如有任何问题,请随时联系我们。' },
+  { question: '开具发票', answer: '我们可以为您开具增值税普通发票或专用发票。请在购买后30天内,在"我的订单"页面点击"申请开票",填写相关信息后,我们会在5个工作日内将发票发送至您的邮箱。' },
+];
+
+Page({
+  data: {
+    messages: [] as Message[],
+    inputValue: '',
+    scrollToMessage: '',
+    userAvatar: '',
+    isLoading: false,
+  },
+
+  onLoad() {
+    this.loadUserInfo();
+    this.initWelcomeMessages();
+  },
+
+  // 加载用户信息
+  loadUserInfo() {
+    const userInfo = wx.getStorageSync('userInfo');
+    if (userInfo) {
+      this.setData({
+        userAvatar: userInfo.avatarUrl || ''
+      });
+    }
+  },
+
+  // 初始化欢迎消息
+  initWelcomeMessages() {
+    const messages: Message[] = [];
+
+    // 第一条欢迎消息
+    messages.push({
+      id: this.generateId(),
+      type: 'ai',
+      contentType: 'text',
+      content: '您好!欢迎来到音乐数字AI客服中心。我是您的专属客服小助手,很高兴为您服务!请问有什么可以帮助您的吗?',
+      timestamp: Date.now()
+    });
+
+    // 第二条常见问题卡片
+    messages.push({
+      id: this.generateId(),
+      type: 'ai',
+      contentType: 'faq',
+      content: '常见问题',
+      questions: FAQ_LIST.map(item => item.question),
+      timestamp: Date.now() + 1
+    });
+
+    this.setData({ messages }, () => {
+      this.scrollToBottom();
+    });
+  },
+
+  // 生成唯一ID
+  generateId(): string {
+    return Date.now().toString(36) + Math.random().toString(36).substr(2);
+  },
+
+  // 滚动到底部
+  scrollToBottom() {
+    this.setData({
+      scrollToMessage: 'last-message'
+    });
+  },
+
+  // 输入框变化
+  onInput(e: WechatMiniprogram.Input) {
+    this.setData({
+      inputValue: e.detail.value
+    });
+  },
+
+  // 发送文字消息
+  sendTextMessage() {
+    const { inputValue, isLoading } = this.data;
+
+    if (!inputValue.trim() || isLoading) return;
+
+    const content = inputValue.trim();
+
+    // 添加用户消息
+    this.addUserMessage('text', content);
+
+    // 清空输入框
+    this.setData({ inputValue: '' });
+
+    // 发送到服务器
+    this.sendMessageToServer(content, 'text');
+  },
+
+  // 添加用户消息
+  addUserMessage(contentType: 'text' | 'image', content: string) {
+    const { messages } = this.data;
+    const newMessage: Message = {
+      id: this.generateId(),
+      type: 'user',
+      contentType,
+      content,
+      timestamp: Date.now()
+    };
+
+    this.setData({
+      messages: [...messages, newMessage]
+    }, () => {
+      this.scrollToBottom();
+    });
+  },
+
+  // 选择图片
+  chooseImage() {
+    wx.chooseMedia({
+      count: 1,
+      mediaType: ['image'],
+      sourceType: ['album', 'camera'],
+      success: (res) => {
+        const tempFilePath = res.tempFiles[0].tempFilePath;
+        this.uploadImage(tempFilePath);
+      },
+      fail: (err) => {
+        console.error('选择图片失败:', err);
+        wx.showToast({
+          title: '选择图片失败',
+          icon: 'none'
+        });
+      }
+    });
+  },
+
+  // 上传图片
+  uploadImage(filePath: string) {
+    // 先显示本地图片
+    this.addUserMessage('image', filePath);
+
+    // 获取全局配置
+    const app = getApp<IAppOption>();
+    const baseUrl = app.globalData?.baseUrl || 'https://test.kt.colexiu.com';
+
+    // 上传图片到服务器
+    wx.uploadFile({
+      url: `${baseUrl}/edu-app/ai/chat/upload`,
+      filePath: filePath,
+      name: 'file',
+      header: {
+        'Authorization': wx.getStorageSync('token') || ''
+      },
+      success: (res) => {
+        try {
+          const data = JSON.parse(res.data);
+          if (data.code === 200) {
+            // 上传成功,发送图片URL到聊天
+            this.sendMessageToServer(data.data.url, 'image');
+          } else {
+            throw new Error(data.msg || '上传失败');
+          }
+        } catch (err) {
+          console.error('解析上传响应失败:', err);
+          this.addReplyMessage('图片上传失败,请重试');
+        }
+      },
+      fail: (err) => {
+        console.error('上传图片失败:', err);
+        // 模拟成功,直接回复
+        setTimeout(() => {
+          this.addReplyMessage('我已收到您的图片,请问有什么可以帮助您的吗?');
+        }, 1000);
+      }
+    });
+  },
+
+  // 发送消息到服务器
+  sendMessageToServer(content: string, contentType: 'text' | 'image') {
+    this.setData({ isLoading: true });
+
+    // 模拟API响应
+    setTimeout(() => {
+      let reply = '';
+
+      if (contentType === 'image') {
+        reply = '我已收到您的图片,请问有什么可以帮助您的吗?';
+      } else {
+        // 简单的关键词匹配回复
+        const keyword = content.toLowerCase();
+        if (keyword.includes('价格') || keyword.includes('多少钱')) {
+          reply = '我们的产品价格根据不同的套餐有所不同。基础版¥99/月,专业版¥199/月,企业版¥499/月。您可以根据自己的需求选择合适的套餐。';
+        } else if (keyword.includes('购买') || keyword.includes('怎么买')) {
+          reply = '您可以通过以下步骤购买课程:1. 在首页浏览课程列表;2. 点击心仪的课程进入详情页;3. 点击"立即购买"按钮;4. 选择支付方式完成支付。';
+        } else if (keyword.includes('设备') || keyword.includes('支持')) {
+          reply = '我们的产品支持多种设备:iOS设备(iPhone、iPad)、Android设备(手机、平板)、Windows电脑、Mac电脑,以及主流的智能电视。';
+        } else if (keyword.includes('试用') || keyword.includes('体验')) {
+          reply = '有的!我们为所有新用户提供7天免费试用期。试用期间您可以体验全部专业版功能,无需绑定支付方式。试用期结束后,您可以选择是否订阅。';
+        } else if (keyword.includes('发票')) {
+          reply = '我们可以为您开具增值税普通发票或专用发票。请在购买后30天内,在"我的订单"页面点击"申请开票",填写相关信息后,我们会在5个工作日内将发票发送至您的邮箱。';
+        } else if (keyword.includes('售后') || keyword.includes('退款')) {
+          reply = '我们提供完善的售后服务:1. 7天无理由退款;2. 产品问题30天内免费换新;3. 终身技术支持;4. 专属客服一对一服务。如有任何问题,请随时联系我们。';
+        } else {
+          reply = '您好,我已经收到您的问题。我们的客服人员会尽快为您解答。如果您有其他问题,也可以继续提问。';
+        }
+      }
+
+      this.addReplyMessage(reply);
+      this.setData({ isLoading: false });
+    }, 1500);
+  },
+
+  // 添加AI回复消息
+  addReplyMessage(content: string) {
+    const { messages } = this.data;
+    const replyMessage: Message = {
+      id: this.generateId(),
+      type: 'reply',
+      contentType: 'text',
+      content,
+      timestamp: Date.now()
+    };
+
+    this.setData({
+      messages: [...messages, replyMessage]
+    }, () => {
+      this.scrollToBottom();
+    });
+  },
+
+  // 点击常见问题
+  onFaqTap(e: WechatMiniprogram.Tap) {
+    const index = e.currentTarget.dataset.index;
+    const faq = FAQ_LIST[index];
+
+    if (faq) {
+      // 添加用户选择的问题
+      this.addUserMessage('text', faq.question);
+
+      // 直接回复固定内容
+      setTimeout(() => {
+        this.addReplyMessage(faq.answer);
+      }, 500);
+    }
+  }
+});

+ 105 - 0
miniprogram/pages/chat/chat.wxml

@@ -0,0 +1,105 @@
+<navigation-bar title="音乐数字AI客服" back-btn="{{true}}" bg-color="#E8F4FC"></navigation-bar>
+
+<view class="chat-container">
+  <!-- 聊天消息列表 -->
+  <scroll-view class="chat-scroll" scroll-y scroll-into-view="{{scrollToMessage}}" enhanced show-scrollbar="{{false}}">
+    <view class="message-list">
+      <block wx:for="{{messages}}" wx:key="id">
+        <!-- AI消息 -->
+        <view wx:if="{{item.type === 'ai'}}" class="message-row ai-row">
+          <view class="avatar ai-avatar">
+            <image class="ai-avatar-image" src="https://oss.dayaedu.com/ktyq/ai-avatar.png" mode="aspectFill"></image>
+          </view>
+          <view class="message-content">
+            <!-- 文本消息 -->
+            <view wx:if="{{item.contentType === 'text'}}" class="bubble ai-bubble">
+              <text class="message-text">{{item.content}}</text>
+            </view>
+            <!-- 常见问题卡片 -->
+            <view wx:if="{{item.contentType === 'faq'}}" class="faq-card">
+              <view class="faq-header">
+                <text class="faq-title">常见问题</text>
+              </view>
+              <view class="faq-list">
+                <view wx:for="{{item.questions}}" wx:key="index" wx:for-item="question" class="faq-item" data-index="{{index}}" bindtap="onFaqTap">
+                  <view class="faq-icon">
+                    <text class="faq-icon-text">#</text>
+                  </view>
+                  <text class="faq-text">{{question}}</text>
+                  <text class="faq-arrow">›</text>
+                </view>
+              </view>
+            </view>
+          </view>
+        </view>
+
+        <!-- 用户消息 -->
+        <view wx:if="{{item.type === 'user'}}" class="message-row user-row">
+          <view class="message-content">
+            <view wx:if="{{item.contentType === 'text'}}" class="bubble user-bubble">
+              <text class="message-text">{{item.content}}</text>
+            </view>
+            <view wx:if="{{item.contentType === 'image'}}" class="user-image">
+              <image src="{{item.content}}" mode="aspectFit" show-menu-by-longpress></image>
+            </view>
+          </view>
+          <view class="avatar user-avatar">
+            <image wx:if="{{userAvatar}}" src="{{userAvatar}}" mode="aspectFill"></image>
+            <text wx:else class="avatar-text">我</text>
+          </view>
+        </view>
+
+        <!-- AI回复(用户问题答案) -->
+        <view wx:if="{{item.type === 'reply'}}" class="message-row ai-row">
+          <view class="avatar ai-avatar">
+            <image class="ai-avatar-image" src="https://oss.dayaedu.com/ktyq/ai-avatar.png" mode="aspectFill"></image>
+          </view>
+          <view class="message-content">
+            <view class="bubble ai-bubble">
+              <text class="message-text">{{item.content}}</text>
+            </view>
+          </view>
+        </view>
+      </block>
+
+      <!-- 加载中状态 -->
+      <view wx:if="{{isLoading}}" class="message-row ai-row loading-row">
+        <view class="avatar ai-avatar">
+          <image class="ai-avatar-image" src="https://oss.dayaedu.com/ktyq/ai-avatar.png" mode="aspectFill"></image>
+        </view>
+        <view class="loading-bubble">
+          <view class="loading-dots">
+            <view class="dot"></view>
+            <view class="dot"></view>
+            <view class="dot"></view>
+          </view>
+        </view>
+      </view>
+
+      <view id="last-message"></view>
+    </view>
+  </scroll-view>
+
+  <!-- 输入区域 -->
+  <view class="input-area">
+    <view class="input-container">
+      <view class="input-wrapper">
+        <view class="voice-btn">
+          <text class="voice-icon">🎤</text>
+        </view>
+        <input
+          class="chat-input"
+          placeholder="请输入您的问题..."
+          placeholder-class="placeholder"
+          value="{{inputValue}}"
+          bindinput="onInput"
+          confirm-type="send"
+          bindconfirm="sendTextMessage"
+        />
+      </view>
+      <view class="image-btn" bindtap="chooseImage">
+        <text class="image-icon">🖼️</text>
+      </view>
+    </view>
+  </view>
+</view>

+ 212 - 520
miniprogram/pages/index/index.less

@@ -14,17 +14,6 @@ page {
 .container {
   position: relative;
 
-  .topShadow {
-    position: absolute;
-    top: 0;
-    z-index: 1;
-    width: 100%;
-    height: 200rpx;
-    background: linear-gradient(180deg,
-        rgba(0, 0, 0, 0.7) 0%,
-        rgba(0, 0, 0, 0) 100%);
-  }
-
   .slider-count {
     position: absolute;
     top: 678rpx;
@@ -65,9 +54,6 @@ page {
         display: none;
       }
 
-      // wx-video .wx-video-top-bar {
-      //   display: none;
-      // }
       .bg {
         filter: blur(32rpx);
       }
@@ -89,270 +75,8 @@ page {
   }
 }
 
-.shop-section {
-  background: #ffffff;
-  // padding: 20rpx 32rpx 24rpx;
-  padding-bottom: 80rpx;
-
-  .main-goods {
-    width: 100%;
-    height: 160rpx;
-    background: url("https://oss.dayaedu.com/ktyq/1733109076504.png") no-repeat center;
-    background-size: contain;
-  }
-
-  .showPrice {
-    display: flex;
-    justify-content: space-between;
-    // align-items: center;
-  }
-
-  .left {
-    display: flex;
-    flex-direction: column;
-    min-width: 210rpx;
-    padding-right: 12rpx;
-    flex-shrink: 0;
-    justify-content: space-between;
-  }
-
-  .left-top {
-    .t1 {
-      margin-top: 16rpx;
-      margin-left: 24rpx;
-      width: 116rpx;
-      height: 47.5rpx;
-    }
-  }
-
-  .left-bottom {
-    padding-left: 24rpx;
-  }
-
-  .currentPrice {
-    font-weight: bold;
-    color: #502f00;
-    font-family: DINAlternate, DINAlternate;
-
-    .stuff {
-      font-size: 36rpx;
-    }
-
-    .priceZ {
-      font-size: 60rpx;
-      line-height: 1;
-    }
-
-    .priceF {
-      font-size: 36rpx;
-    }
-  }
-
-  // .originPrice {
-  //   padding-left: 16rpx;
-  //   font-size: 32rpx;
-  //   color: #AAAAAA;
-  //   line-height: 44rpx;
-  //   text-decoration: line-through;
-  //   padding-bottom: 12rpx;
-  // }
-  .right {
-    flex: 1 auto;
-    padding-top: 24rpx;
-    font-weight: 400;
-    font-size: 28rpx;
-    color: #777777;
-    line-height: 40rpx;
-
-    .right-top {
-      display: flex;
-      font-size: 24rpx;
-      color: #6d4718;
-      line-height: 34rpx;
-
-      .originPrice text {
-        text-decoration: line-through;
-        padding-left: 6rpx;
-      }
-    }
-
-    .right-bottom {
-      padding-top: 46rpx;
-    }
-  }
-}
-
-.shopName {
-  font-weight: 600;
-  font-size: 30rpx;
-  color: #502f00;
-  line-height: 42rpx;
-  max-width: 420rpx;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.goodsInfo {
-  display: flex;
-  position: relative;
-
-  // align-items: center;
-  .desc {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    text-align: center;
-    width: 84rpx;
-    height: 84rpx;
-    background: #f1f1f1;
-    border-radius: 12rpx;
-    font-size: 24rpx;
-    color: #777777;
-    line-height: 30rpx;
-    flex-shrink: 0;
-    padding: 12rpx 16rpx;
-    box-sizing: border-box;
-    margin-right: 16rpx;
-    margin-top: 32rpx;
-  }
-
-  .goodsList {
-    display: flex;
-    align-items: center;
-    flex-wrap: nowrap;
-    overflow-x: auto;
-    padding-top: 32rpx;
-    margin-bottom: 40rpx;
-
-    &::-webkit-scrollbar {
-      display: none;
-    }
-  }
-
-  .iconMore {
-    position: absolute;
-    right: -24rpx;
-    bottom: 32rpx;
-    width: 20rpx;
-    height: 20rpx;
-  }
-
-  .goodsItem {
-    display: flex;
-    align-items: center;
-    position: relative;
-    height: 84rpx;
-    background: #f8f8f8;
-    padding-left: 10rpx;
-    padding-right: 10rpx;
-    margin-right: 16rpx;
-    font-size: 28rpx;
-    color: #131415;
-    position: relative;
-    border-radius: 12rpx;
-    border: 4rpx solid #f8f8f8;
-    box-sizing: border-box;
-
-    .select-arrow {
-      display: none;
-    }
-
-    &.selected {
-      background: rgba(255, 255, 255, 0.7);
-      border-radius: 12rpx;
-      border: 4rpx solid #855f2e;
-
-      .select-arrow {
-        display: block;
-        position: absolute;
-        top: -40rpx;
-        left: 50%;
-        transform: translateX(-50%);
-        width: 28rpx;
-        height: 18rpx;
-      }
-    }
-
-    &.nosale {
-      background-color: rgba(248, 248, 248, 0.6);
-
-      .pic,
-      .nameorprice {
-        opacity: 0.6;
-      }
-    }
-
-    .pic {
-      width: 64rpx;
-      height: 64rpx;
-      flex-shrink: 0;
-    }
-
-    .nameorprice {
-      padding-left: 10rpx;
-      padding-right: 12rpx;
-      flex-shrink: 0;
-      display: flex;
-      flex-direction: column;
-      justify-content: space-around;
-      height: 64rpx;
-
-      .name {
-        font-weight: 600;
-        font-size: 24rpx;
-        color: #131415;
-        line-height: 28rpx;
-      }
-
-      .price {
-        font-family: DINAlternate, DINAlternate;
-        font-weight: bold;
-        font-size: 24rpx;
-        color: #ff0047;
-        line-height: 1.2;
-        padding-top: 4rpx;
-      }
-    }
-
-    .imgSale {
-      position: absolute;
-      top: -20rpx;
-      right: -16rpx;
-      width: 56rpx;
-      height: 28rpx;
-      display: block;
-      z-index: 1;
-    }
-  }
-}
-
-.scroll-current-item {
-  position: fixed;
-  bottom: 162rpx;
-  left: 0;
-  z-index: 1;
-  width: 100%;
-  margin-top: 24rpx;
-  font-size: 24rpx;
-  color: #502f00;
-  line-height: 40rpx;
-  background: #fff3e1;
-  border: 2rpx solid #fbe9ce;
-  padding: 12rpx 16rpx;
-  text-align: center;
-
-  text {
-    color: #ff0047;
-    font-weight: bold;
-  }
-}
-
 .goodsSection {
-  // margin-bottom: 156rpx;
-  // margin-top: 12rpx;
   background: #ffffff;
-  // padding-bottom: 162rpx;
 
   .title {
     font-size: 28rpx;
@@ -417,38 +141,12 @@ page {
   .video-section {
     position: relative;
     width: 100%;
-    // padding-top: 56.25%;
+
     image {
       box-shadow: 4rpx 8rpx 24rpx 1rpx rgba(96, 168, 223, 0.45);
       border-radius: 24rpx;
     }
   }
-  .videoItem {
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    width: 100%;
-    height: 100%;
-    // height: 368rpx;
-    border-radius: 24rpx;
-    box-shadow: 4rpx 8rpx 24rpx 1rpx rgba(96, 168, 223, 0.45);
-  }
-}
-
-.liu-section {
-  padding: 0 0 48rpx;
-
-  .liu-img2 {
-    padding: 24rpx 48rpx 0;
-  }
-
-  .liu-img1 {
-    padding: 0 40rpx;
-  }
-
-  .titleVideoSection {
-    padding: 0 48rpx;
-  }
 }
 
 .title-section {
@@ -535,7 +233,6 @@ page {
   z-index: 2;
   width: 100%;
   background-color: #ffffff;
-  // box-shadow: inset 0rpx 2rpx 0rpx 0rpx #F0F0F0;
   padding: 20rpx 32rpx 58rpx 32rpx;
   display: flex;
   align-items: center;
@@ -623,137 +320,7 @@ page {
   }
 }
 
-.combo-section {
-  // max-height: 556rpx;
-  max-height: 30vh;
-  overflow-x: hidden;
-  overflow-y: auto;
-
-  &::-webkit-scrollbar {
-    display: none;
-  }
-}
-
-.combo-title {
-  padding-bottom: 8rpx;
-  font-weight: 600;
-  font-size: 26rpx;
-  color: #502f00;
-  line-height: 36rpx;
-
-  .select-one {
-    color: #ff0047;
-  }
-}
-
-.combo-tips {
-  padding-bottom: 20rpx;
-  font-size: 24rpx;
-  color: rgba(80, 47, 0, 0.5);
-  line-height: 34rpx;
-}
-
-.combo-list {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-.combo-item {
-  width: 31%;
-  display: flex;
-  align-items: center;
-  flex-direction: column;
-  padding: 20rpx 10rpx;
-  margin-right: 2.3333%;
-  margin-bottom: 20rpx;
-  position: relative;
-  box-sizing: border-box;
-
-  .check-good {
-    width: 28rpx;
-    height: 28rpx;
-    position: absolute;
-    top: 16rpx;
-    right: 16rpx;
-  }
-
-  &:nth-child(3n + 3) {
-    margin-right: 0;
-  }
-
-  &.active {
-    background: #fff8ee;
-    border-radius: 20rpx;
-  }
-
-  .combo-item-img {
-    width: 128rpx;
-    height: 128rpx;
-    padding-bottom: 20rpx;
-  }
-
-  .combo-item-name {
-    font-weight: 400;
-    font-size: 24rpx;
-    color: #000000;
-    line-height: 34rpx;
-    max-width: 200rpx;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-
-  .combo-item-price {
-    font-family: DINAlternate, DINAlternate;
-    font-weight: bold;
-    font-size: 28rpx;
-    color: #ff0047;
-    line-height: 40rpx;
-  }
-
-  .combo-item-origin {
-    font-family: DINAlternate, DINAlternate;
-    font-weight: 400;
-    font-size: 24rpx;
-    color: #6d4718;
-    line-height: 34rpx;
-    text {
-      text-decoration: line-through;
-      padding-left: 6rpx;
-    }
-  }
-}
-
-.popupBottom {
-  background: #ffffff;
-  // box-shadow: 0 2rpx 8rpx 0rpx rgba(0, 0, 0, 0.5);
-  box-shadow: 0rpx 2rpx 8rpx 0rpx rgba(0,0,0,0.25);
-  padding: 0 32rpx 58rpx;
-  position: relative;
-  z-index: 1;
-}
-
-.current-item {
-  font-size: 24rpx;
-  color: #502f00;
-  line-height: 40rpx;
-  padding: 12rpx 12rpx 28rpx;
-
-  .current-item-text {
-    font-weight: 600;
-    font-size: 26rpx;
-    color: #502f00;
-    line-height: 36rpx;
-    padding-bottom: 4rpx;
-  }
-
-  text {
-    color: #ff0047;
-    font-weight: bold;
-  }
-}
-
-// 'demo-text-1', 'demo-text-2', 'demo-text-3'
+// ==================== 购买弹窗样式 ====================
 .popup-section {
   &.hidden {
     display: none;
@@ -777,116 +344,241 @@ page {
     z-index: 10;
     width: 100%;
     background: #ffffff;
-    border-radius: 32rpx 32rpx 0rpx 0rpx;
-    padding: 48rpx 0 0;
+    border-radius: 32rpx 32rpx 0 0;
+    padding: 32rpx 32rpx 40rpx;
     box-sizing: border-box;
-
-    &::before {
-      content: "";
-      position: absolute;
-      top: 0;
-      left: 0;
-      right: 0;
-      width: 100%;
-      height: 30rpx;
-      background: linear-gradient(180deg, #fbead0 0%, #ffffff 100%);
-      border-radius: 32rpx 32rpx 0rpx 0rpx;
-    }
-  }
-
-  .shop-section {
-    padding: 0 32rpx;
   }
 
   .iconClose {
     position: absolute;
-    // right: 32rpx;
-    // top: 32rpx;
-    top: 0;
-    right: 0;
-    padding: 32rpx;
-    width: 30rpx;
-    height: 30rpx;
+    top: 24rpx;
+    right: 24rpx;
+    width: 44rpx;
+    height: 44rpx;
+    padding: 20rpx;
     box-sizing: content-box;
+    z-index: 11;
   }
+}
 
-  button {
-    width: 164rpx;
-    height: 64rpx;
-    line-height: 64rpx;
-    background: #fbeac9 !important;
-    border-radius: 32rpx;
-    font-weight: 500;
-    font-size: 32rpx;
-    color: #3a3834 !important;
+// 用户类型切换 tabs - 连在一起的整体样式
+.user-type-tabs {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 32rpx;
+  background: #f0f0f0;
+  border-radius: 32rpx;
+  padding: 4rpx;
+  width: fit-content;
+  margin-left: auto;
+  margin-right: auto;
+
+  .tab-item {
+    padding: 16rpx 56rpx;
+    font-size: 28rpx;
+    color: #666666;
+    border-radius: 28rpx;
+    transition: all 0.3s;
+    min-width: 120rpx;
     text-align: center;
-    padding: 0;
-    margin: 0;
+
+    &.active {
+      background: linear-gradient(90deg, #544f4a 0%, #302f2b 100%);
+      color: #fbeac9;
+      font-weight: 500;
+    }
   }
+}
 
-  .btnGroup {
-    position: relative;
+// 商品信息卡片
+.product-info-card {
+  display: flex;
+  background: #faf8f5;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 32rpx;
+
+  .product-badge {
     display: flex;
-    justify-content: space-between;
+    flex-direction: column;
     align-items: center;
-    position: relative;
-    padding: 0 12rpx;
-    height: 88rpx;
-    background: linear-gradient(90deg, #544f4a 0%, #302f2b 100%);
-    border-radius: 44rpx;
+    justify-content: center;
+    margin-right: 24rpx;
+    flex-shrink: 0;
 
-    .left-section {
-      position: relative;
+    .duration-text {
+      width: 88rpx;
+      height: 88rpx;
+      background: linear-gradient(135deg, #ff9a76 0%, #ff6b6b 100%);
+      border-radius: 16rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 26rpx;
+      color: #ffffff;
+      font-weight: 600;
+    }
+
+    .user-label {
+      font-size: 22rpx;
+      color: #ff6b6b;
+      background: rgba(255, 107, 107, 0.1);
+      padding: 4rpx 16rpx;
+      border-radius: 8rpx;
+      margin-top: 8rpx;
+    }
+  }
+
+  .product-detail {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+
+    .product-name {
+      font-size: 28rpx;
+      color: #333333;
+      font-weight: 600;
+      margin-bottom: 12rpx;
+      line-height: 40rpx;
+    }
+
+    .price-line {
       display: flex;
+      align-items: baseline;
+      margin-bottom: 8rpx;
 
-      .icon1 {
-        position: absolute;
-        top: -22rpx;
-        left: 28rpx;
-        width: 102rpx;
-        height: 102rpx;
+      .price-tag {
+        font-size: 24rpx;
+        color: #ff6b6b;
+        margin-right: 8rpx;
+        background: rgba(255, 107, 107, 0.1);
+        padding: 2rpx 8rpx;
+        border-radius: 4rpx;
       }
 
-      .currentPrice2 {
-        padding-left: 138rpx;
-        font-weight: bold;
-        color: #FBEAC9;
-        font-family: DINAlternate, DINAlternate;
+      .price-num {
+        font-size: 36rpx;
+        color: #333333;
+        font-weight: 700;
+      }
+    }
 
-        .stuff {
-          font-size: 36rpx;
-          padding-right: 4rpx;
-        }
+    .origin-line {
+      display: flex;
+      align-items: center;
+      font-size: 22rpx;
+      color: #999999;
+
+      .origin-tag {
+        margin-right: 4rpx;
+      }
 
-        .priceZ {
-          font-size: 60rpx;
-        }
+      .origin-num {
+        text-decoration: line-through;
+        margin-right: 12rpx;
+      }
+
+      .divider {
+        margin: 0 12rpx;
+        color: #cccccc;
+      }
 
-        .priceF {
-          font-size: 36rpx;
-        }
+      .sold-count {
+        color: #999999;
       }
     }
   }
 }
 
-.btnInnerFree {
-  position: absolute;
-  right: 0;
-  z-index: 2;
-  top: -28rpx;
-  background: linear-gradient(180deg, #ff5181 0%, #ff0047 100%);
-  border-radius: 150rpx 200rpx 200rpx 0rpx;
-  border: 2rpx solid #ffffff;
-  padding: 0 8rpx;
-  font-size: 24rpx;
-  color: #ffffff;
-  padding-top: 2rpx;
-  line-height: 34rpx;
-  overflow: hidden;
-  font-weight: bold;
+// 选择期限
+.period-select-section {
+  margin-bottom: 24rpx;
+
+  .section-label {
+    font-size: 28rpx;
+    color: #333333;
+    font-weight: 500;
+    margin-bottom: 20rpx;
+  }
+
+  .period-options {
+    display: flex;
+    gap: 16rpx;
 
-  text {
-    font-weight: bold;
+    .period-btn {
+      flex: 1;
+      height: 72rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: #f5f5f5;
+      border-radius: 12rpx;
+      font-size: 26rpx;
+      color: #666666;
+      border: 2rpx solid transparent;
+      transition: all 0.2s;
+
+      &.active {
+        background: #ffffff;
+        border: 2rpx solid #333333;
+        color: #333333;
+        font-weight: 600;
+      }
+    }
   }
-}
+}
+
+// 额外赠送
+.gift-section {
+  background: #f5f5f5;
+  border-radius: 12rpx;
+  padding: 20rpx 24rpx;
+  margin-bottom: 32rpx;
+
+  .gift-desc {
+    font-size: 26rpx;
+    color: #666666;
+  }
+}
+
+// 底部结算栏
+.settlement-bar {
+  .select-summary {
+    display: flex;
+    align-items: center;
+    font-size: 24rpx;
+    color: #666666;
+    margin-bottom: 20rpx;
+    padding: 0 8rpx;
+
+    .summary-label {
+      color: #999999;
+      margin-right: 8rpx;
+    }
+
+    .summary-content {
+      color: #333333;
+    }
+
+    .summary-save {
+      color: #ff6b6b;
+      margin-left: 4rpx;
+    }
+  }
+
+  .submit-btn {
+    width: 100%;
+    height: 88rpx;
+    line-height: 88rpx;
+    background: linear-gradient(90deg, #544f4a 0%, #302f2b 100%);
+    border-radius: 44rpx;
+    font-size: 32rpx;
+    color: #fbeac9;
+    font-weight: 500;
+    text-align: center;
+    border: none;
+    margin: 0;
+    padding: 0;
+  }
+}

+ 79 - 9
miniprogram/pages/index/index.ts

@@ -77,7 +77,17 @@ Page({
     bannerPlay: false, // 视频是否播放
     titleControls: false, // 详情是否显示控制条
     liuControls: false, // 详情是否显示控制条
-    bannerImageloaded: false, // Banner图片是否加载完成
+    bannerImageloaded: false, // Banner 图片是否加载完成
+    userTypes: 'teacher', // 用户类型:student | teacher
+    periodList: [
+      { label: '7 天', value: '7DAY', disabled: false },
+      { label: '14 天', value: '14DAY', disabled: false },
+      { label: '1 个月', value: '1MONTH', disabled: false },
+      { label: '6 个月', value: '6MONTH', disabled: false },
+      { label: '1 年', value: '1YEAR', disabled: false }
+    ],
+    selectedPeriod: '14DAY', // 选中的期限
+    showBonusGift: false, // 是否显示额外赠送
   },
 
   /**
@@ -108,11 +118,11 @@ Page({
             code: res.code,
             appId: app.globalData.appId
           }).then((res: any) => {
-            // 存储openId
+            // 存储 openId
             const openId = res.data.data
             wx.setStorageSync("openId", openId);
 
-            this.onTrackPoint({ 
+            this.onTrackPoint({
               openId,
               elementName: '首页'
             })
@@ -249,7 +259,7 @@ Page({
         }
       });
       if (isOverSaled) {
-        // 没有可购买商品则默认选中第一个商品
+        // 没有可销售商品则默认选中第一个商品
         selected = list[0];
       }
 
@@ -262,6 +272,7 @@ Page({
         selectedInstrument: {}
       }, () => {
         this.onFormatGoods()
+        this.onUpdatePeriodList()
       });
     } catch (e) {
       console.log(e, "e");
@@ -419,6 +430,65 @@ Page({
       // showSelectedProduct: true,
     });
   },
+  /** 切换学生端/老师端 */
+  onSwitchUserType(e: any) {
+    const { dataset } = e.currentTarget;
+    this.setData({
+      userTypes: dataset.type
+    }, () => {
+      // 这里可以根据用户类型重新加载商品数据
+      // 目前先保持逻辑不变,后续可根据后端 API 扩展
+      this.onInit()
+    })
+  },
+  /** 选择期限 */
+  onSelectPeriod(e: any) {
+    const { value } = e.currentTarget.dataset
+    const periodItem = this.data.periodList.find((item: any) => item.value === value)
+    if (periodItem?.disabled) {
+      return
+    }
+
+    this.setData({
+      selectedPeriod: value
+    }, () => {
+      // 根据选中的期限更新商品
+      this.onUpdatePeriodList()
+    })
+  },
+  /** 根据期限更新商品列表 */
+  onUpdatePeriodList() {
+    const { selectedPeriod, list } = this.data
+    // 将期限值映射回商品列表的筛选逻辑
+    // 这里需要根据实际的 period 和 num 来匹配
+    const periodMap: any = {
+      '7DAY': { period: 'DAY', num: 7 },
+      '14DAY': { period: 'DAY', num: 14 },
+      '1MONTH': { period: 'MONTH', num: 1 },
+      '6MONTH': { period: 'MONTH', num: 6 },
+      '1YEAR': { period: 'YEAR', num: 1 }
+    }
+
+    const target = periodMap[selectedPeriod]
+    if (target) {
+      const selectedItem = list.find((item: any) =>
+        item.period === target.period && item.num === target.num
+      )
+      if (selectedItem && selectedItem.stockNum > 0) {
+        this.setData({
+          selected: selectedItem
+        }, () => {
+          this.onFormatGoods()
+        })
+      }
+    }
+
+    // 判断是否满 1 年,显示赠送提示
+    const showBonusGift = selectedPeriod === '1YEAR'
+    this.setData({
+      showBonusGift
+    })
+  },
   // 进行埋点
   onTrackPoint(options: { openId: string, elementName: string }) {
     const traceId = wx.getStorageSync("traceId");
@@ -428,7 +498,7 @@ Page({
       openId: options.openId,
       elementName: options.elementName,
       deviceInfo: deviceInfo.brand + '_' + deviceInfo.model + '_' + deviceInfo.system + '_' + deviceInfo.platform,
-      appName: "音乐数字AI",
+      appName: "音乐数字 AI",
       // extParams: '',
       clickTime: formatTime(new Date(), '-') // 点击时间
     })
@@ -475,7 +545,7 @@ Page({
       const openId = wx.getStorageSync("openId")
       that.onTrackPoint({
         openId,
-        elementName: "去结算"
+        elementName: "立即购买"
       })
 
       let info = JSON.stringify({
@@ -577,7 +647,7 @@ Page({
   onScrollView(e: { detail: any }) {
     const top = e.detail.scrollTop || 0;
     // const scrollHeight = e.detail.scrollHeight || 0;
-    // 从100开始显示
+    // 从 100 开始显示
     this.setData({
       // opacity: top < 100 ? 0 : (top - 100) > 150 ? 1 : (top - 100) / 150
       opacity: top < 100 ? 0 : top - 100 > 150 ? 1 : 1,
@@ -662,14 +732,14 @@ Page({
   },
   onShareAppMessage() {
     return {
-      title: "音乐数字AI",
+      title: "音乐数字 AI",
       path: "/pages/index/index",
       imageUrl: "https://oss.dayaedu.com/ktyq/1739870592907.png",
     };
   },
   onShareTimeline() {
     return {
-      title: "音乐数字AI",
+      title: "音乐数字 AI",
       path: "/pages/index/index",
       imageUrl: "https://oss.dayaedu.com/ktyq/1739870592907.png",
     };

+ 48 - 116
miniprogram/pages/index/index.wxml

@@ -1,7 +1,6 @@
 <!-- index.wxml -->
 <scroll-view class="scrollarea" id="scroll-view" scroll-y="{{popupShow ? false : true}}" type="list" bindscroll="onScrollView" bounces="{{ false }}" enhanced enable-passive="true" scroll-into-view="{{scrolIntoView}}" scroll-top="{{ scrollTop }}">
   <view class="container">
-    <!-- <view class="topShadow"></view> -->
     <view class="slider-count">{{current + 1}}/{{imgList.length + 1}}</view>
     <swiper class="bannerSwiper" indicator-dots="{{false}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}" bindchange="changeSwiper">
       <swiper-item>
@@ -9,10 +8,6 @@
           <block wx:if="{{bannerImageloaded}}">
             <image class="bg" src="https://oss.dayaedu.com/ktyq/1738923944096.png"></image>
             <video style="height: {{videoHeight}};" object-fit="contain" bindplay="onBannerVideoPlay" picture-in-picture-mode="[]" id="bannerVideo" src="https://oss.dayaedu.com/ktyq/17337413498130cfedaf1.mp4"></video>
-            <!-- poster="https://oss.dayaedu.com/ktyq/1738923944096.png" -->
-            <!-- https://oss.dayaedu.com/ktyq/17334015356119a704b08.mp4 100M -->
-            <!-- https://oss.dayaedu.com/ktyq/1733405365311b760c974.mp4 30M -->
-            <!-- https://oss.dayaedu.com/ktyq/17334057618630fc77dba.mp4 60M -->
           </block>
           <block wx:if="{{ !bannerPlay }}">
             <image bindload="onBannerVideoLoad" src="https://oss.dayaedu.com/ktyq/1738923944096.png"></image>
@@ -22,7 +17,6 @@
       </swiper-item>
       <swiper-item wx:for="{{ imgList }}" wx:key="index">
         <view class="swiper-item ">
-          <!-- bind:tap="onPreivewBannerImg" -->
           <image src="{{ item }}" data-src="{{ item }}"></image>
         </view>
       </swiper-item>
@@ -41,16 +35,6 @@
         </view>
       </view>
     </view>
-    <!-- <view class="scroll-current-item" wx:if="{{!isOverSaled && selected.id }}">
-      您已选中:
-      <text>{{ selected.typeName }}</text>
-      ,合计:
-      <text>¥ {{ selected.showSalePrice }}</text>
-      <block wx:if="{{ selected.originalPrice > selected.salePrice }}">
-        ,已优惠:
-        <text>¥ {{ selected.discountPrice }}</text>
-      </block>
-    </view> -->
     <view class="goodsSection">
       <view class="title">
         <view class="before"></view>
@@ -62,16 +46,6 @@
           <image id="type1" mode="widthFix" src="https://oss.dayaedu.com/ktyq/1739353790443.png" data-src="https://oss.dayaedu.com/ktyq/1739353790443.png"></image>
           <image mode="widthFix" src="https://oss.dayaedu.com/ktyq/1739353815962.png" data-src="https://oss.dayaedu.com/ktyq/1739353815962.png"></image>
           <image mode="widthFix" src="https://oss.dayaedu.com/ktyq/1739353843494.png" data-src="https://oss.dayaedu.com/ktyq/1739353843494.png"></image>
-          <!-- <image  mode="widthFix" src="https://oss.dayaedu.com/ktyq/173339759143825859f65.png" data-src="https://oss.dayaedu.com/ktyq/173339759143825859f65.png"></image> -->
-          <!-- <view id="type3" class="liu-section">
-            <view class="liu-img1">
-              <image  mode="widthFix" src="https://oss.dayaedu.com/ktyq/1733457722973.png" data-src="https://oss.dayaedu.com/ktyq/1733457722973.png"></image>
-            </view>
-            <view class="liu-img2">
-              <image mode="widthFix" src="https://oss.dayaedu.com/ktyq/173340695604938755d8b.png" data-src="https://oss.dayaedu.com/ktyq/173340695604938755d8b.png"></image>
-            </view>
-          </view> -->
-          <!-- <image id="type2" class="type2s" mode="widthFix" src="https://oss.dayaedu.com/ktyq/1732617388991.png" data-src="https://oss.dayaedu.com/ktyq/1732617388991.png"></image> -->
           <view id="type3" class="type2s title-section">
             <image src="./images/title1.png" class="title"></image>
             <view class="topSection">
@@ -80,12 +54,8 @@
               </view>
               <view class="titleVideoSection">
                 <view class="video-section">
-                  <!-- <video object-fit="contain" show-center-play-btn="{{false}}" picture-in-picture-mode="[]" controls="{{titleControls}}" bindplay="onTitlePlay" class="videoItem" id="titleVideo" src="https://oss.dayaedu.com/ktyq/1733395673461fc6123ca.mp4"></video> -->
-                  <!-- <image id="type2" mode="widthFix" src="https://oss.dayaedu.com/ktyq/1739353862133.png" data-src="https://oss.dayaedu.com/ktyq/1739353862133.png"></image> -->
                   <image mode="widthFix" bind:tap="onPreivewDetailImg2" src="https://oss.dayaedu.com/ktyq/03/1772461866051.png" data-src="https://oss.dayaedu.com/ktyq/03/1772461866051.png" />
                 </view>
-
-                <!-- <image wx:if="{{!titleControls}}" bind:tap="onTItleVideoPlay" src="./images/icon-video.png" class="icon-video"></image> -->
               </view>
               <view class="content2">实践知真章,合作学校仅用两节课便完成母亲节主题演奏</view>
             </view>
@@ -114,107 +84,69 @@
       </view>
       <view class="btnSection">
         <button bind:tap="onBuyShop" type="primary" disabled="{{ isOverSaled }}">立即购买</button>
-        <view class="btnInnerFree" wx:if="{{ selected.giftFlag }}">
-          当天激活
-          <text>赠{{selected.giftLongTime}}</text>
-        </view>
       </view>
     </view>
+
+    <!-- 购买弹窗 -->
     <view class="popup-section" wx:if="{{popupShow}}">
       <view class="popup-mask" bind:tap="onClose"></view>
       <view class="popup-container">
         <image catch:tap="onClose" src="./images/icon-close.png" class="iconClose"></image>
-        <view class="shop-section">
-          <view class="main-goods">
-            <view class="showPrice">
-              <view class="left">
-                <view class="left-top">
-                  <image class="t1" src="./images/t1.png"></image>
-                </view>
-                <view class="left-bottom">
-                  <view class="currentPrice">
-                    <text class="stuff">¥</text>
-                    <text class="priceZ">{{ selected.integerPart || 0 }}</text>
-                    <text class="priceF">.{{ selected.decimalPart || '00' }}</text>
-                  </view>
-                </view>
-              </view>
-              <view class="right">
-                <view class="right-top">
-                  <block wx:if="{{ selected.originalPrice > selected.salePrice }}">
-                    <view class="originPrice">日常价<text>¥{{ selected.originalPrice || '0.00' }}</text></view>
-                    <text style="padding: 0 6rpx">|</text>
-                  </block>
-                  <view class="saleNum">已售10W+</view>
-                </view>
-                <view class="right-bottom">
-                  <view class="shopName">{{ selected.name }}</view>
-                </view>
-              </view>
-            </view>
-          </view>
-          <view class="goodsInfo">
-            <text class="desc">选择期限</text>
-            <view class="goodsList">
-              <view wx:for="{{ list }}" wx:key="index" class="goodsItem {{ item.id == selected.id ? 'selected' : '' }} {{ item.stockNum <= 0 ? 'nosale' : '' }}" bind:tap="onSelectGoods" data-id="{{ item.id }}">
-                <image class="select-arrow" src="./images/icon-down-arrow.png"></image>
-                <image class="imgSale" wx:if="{{ item.stockNum <= 0 }}" src="./images/nosale.png"></image>
-                <image class="pic" src="{{ item.pic }}"></image>
-                <view class="nameorprice">
-                  <view class="name">{{ item.typeName }}</view>
-                  <view class="price">¥{{ item.showSalePrice }}</view>
-                </view>
-              </view>
-            </view>
-            <!-- <image wx:if="{{ list.length > 3 }}" bind:tap="onLookMore" class="iconMore" src="./images/icon-more.png"></image> -->
+
+        <!-- 用户类型切换 -->
+        <view class="user-type-tabs">
+          <view class="tab-item {{ userTypes === 'student' ? 'active' : '' }}" bind:tap="onSwitchUserType" data-type="student">学生端</view>
+          <view class="tab-item {{ userTypes === 'teacher' ? 'active' : '' }}" bind:tap="onSwitchUserType" data-type="teacher">老师端</view>
+        </view>
+
+        <!-- 商品信息卡片 -->
+        <view class="product-info-card">
+          <view class="product-badge">
+            <text class="duration-text">{{ selected.typeName || '14天' }}</text>
+            <text class="user-label">学生端</text>
           </view>
-          <!-- <view class="combo-section">
-            <view class="combo-title">
-              超值组合,乐器随心选
-              <text class="select-one">(可选1件)</text>
+          <view class="product-detail">
+            <view class="product-name">{{ selected.name || '老师端-器乐数字AI畅享卡' }}</view>
+            <view class="price-line">
+              <text class="price-tag">到手价</text>
+              <text class="price-num">¥{{ selected.showSalePrice || '388.00' }}</text>
             </view>
-            <view class="combo-tips">乐器图片仅供参考,具体产品请以实物为准</view>
-            <view class="combo-list">
-              <view class="combo-item {{ item.id === selectInstrumentId ? 'active' : '' }}" wx:for="{{ instrumentList }}" wx:key="index" bind:tap="onSelectInstrument" data-id="{{ item.id }}">
-                <image src="./images/check-good.png" wx:if="{{ item.id === selectInstrumentId  }}" class="check-good" />
-                <image class="combo-item-img" src="{{ item.pic }}"></image>
-                <view class="combo-item-name">{{ item.name }}</view>
-                <view class="combo-item-price">¥ {{ item.showSalePrice }}</view>
-                <view class="combo-item-origin">日常价<text>¥ {{ item.showOriginalPrice }}</text></view>
-              </view>
+            <view class="origin-line">
+              <text class="origin-tag">日常价</text>
+              <text class="origin-num">¥{{ selected.originalPrice || '2900.00' }}</text>
+              <text class="divider">|</text>
+              <text class="sold-count">已售10W+</text>
             </view>
-          </view> -->
-        </view>
-        <view class="popupBottom">
-          <!-- 有选择商品的时候 -->
-          <view class="current-item" wx:if="{{!isOverSaled && selected.id}}">
-            <view class="current-item-text">您已选择:</view>
-            <text>{{ formatSelectGood.typeName }}</text>
-            ,合计:
-            <text>¥ {{ formatSelectGood.showSalePrice }}</text>
-            <block wx:if="{{ formatSelectGood.originalPrice > formatSelectGood.salePrice }}">
-              ,已省<text style="padding-left: 6rpx;">¥ {{ formatSelectGood.discountPrice }}</text>
-            </block>
           </view>
-          <view class="btnGroup">
-            <view class="left-section">
-              <image mode="widthFix" src="./images/icon1.png" class="icon1" />
-              <view class="currentPrice2">
-                <text class="stuff">¥</text>
-                <text class="priceZ">{{ formatSelectGood.integerPart || "0" }}</text>
-                <text class="priceF">.{{ formatSelectGood.decimalPart || '00' }}</text>
-              </view>
+        </view>
+
+        <!-- 选择期限 -->
+        <view class="period-select-section">
+          <view class="section-label">选择期限</view>
+          <view class="period-options">
+            <view wx:for="{{ periodList }}" wx:key="value" class="period-btn {{ selectedPeriod === item.value ? 'active' : '' }}" bind:tap="onSelectPeriod" data-value="{{ item.value }}">
+              {{ item.label }}
             </view>
-            <button type="primary" catch:tap="onSubmit">去结算</button>
-            <!-- <view class="btnInnerFree" wx:if="{{ selected.giftFlag }}">
-              当天激活
-              <text>赠{{selected.giftLongTime}}</text>
-            </view> -->
           </view>
         </view>
+
+        <!-- 额外赠送 -->
+        <view class="gift-section" wx:if="{{ showBonusGift }}">
+          <text class="gift-desc">满1年额外赠送2个月</text>
+        </view>
+
+        <!-- 底部结算栏 -->
+        <view class="settlement-bar">
+          <view class="select-summary">
+            <text class="summary-label">选中:</text>
+            <text class="summary-content">{{ userTypes === 'teacher' ? '老师端' : '学生端' }}/{{ selected.typeName || '14天' }},</text>
+            <text class="summary-save">已省¥{{ formatSelectGood.discountPrice || '2512.00' }}</text>
+          </view>
+          <button class="submit-btn" catch:tap="onSubmit">立即购买</button>
+        </view>
       </view>
     </view>
   </view>
   <!-- 客服功能 -->
   <service wx:if="{{serviceShow}}"></service>
-</scroll-view>
+</scroll-view>