精品课
This commit is contained in:
parent
979c332454
commit
0de0b6732b
106
README_FLOATING_INVOICE_BTN.md
Normal file
106
README_FLOATING_INVOICE_BTN.md
Normal file
@ -0,0 +1,106 @@
|
||||
# 订单记录页面悬浮发票按钮
|
||||
|
||||
## 功能说明
|
||||
|
||||
在订单记录页面添加了一个悬浮的开具发票按钮,用户可以随时点击进入发票开具流程,无需返回首页。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 悬浮按钮设计
|
||||
- **位置**: 固定在页面右下角,距离右边距30rpx,距离底部120rpx
|
||||
- **尺寸**: 120rpx × 120rpx 的圆形按钮
|
||||
- **样式**: 渐变背景色(红色到橙色),带有阴影效果
|
||||
- **层级**: z-index: 999,确保按钮始终显示在最上层
|
||||
|
||||
### 2. 按钮内容
|
||||
- **图标**: 使用 `paperplane` 图标,表示发票功能
|
||||
- **文字**: "开具发票" 标签,清晰说明按钮功能
|
||||
- **布局**: 图标在上,文字在下,垂直居中对齐
|
||||
|
||||
### 3. 交互效果
|
||||
- **点击反馈**: 点击时有缩放和阴影变化效果
|
||||
- **过渡动画**: 所有状态变化都有平滑的过渡效果
|
||||
- **悬停效果**: 按钮具有现代化的视觉反馈
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 访问订单记录页面
|
||||
- 在课程相关页面中进入"订单记录"
|
||||
- 页面右下角会显示悬浮的发票按钮
|
||||
|
||||
### 2. 点击开具发票
|
||||
- 点击悬浮按钮
|
||||
- 自动跳转到发票选择页面
|
||||
- 选择要开票的课程后进入发票信息填写页面
|
||||
|
||||
### 3. 完成开票流程
|
||||
- 填写发票信息
|
||||
- 提交表单
|
||||
- 返回订单记录页面
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 按钮结构
|
||||
```vue
|
||||
<!-- 悬浮发票按钮 -->
|
||||
<view class="floating-invoice-btn" @click="goToInvoice">
|
||||
<uni-icons type="paperplane" size="24" color="#fff"></uni-icons>
|
||||
<text class="btn-text">开具发票</text>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 跳转方法
|
||||
```javascript
|
||||
// 跳转到开具发票页面
|
||||
const goToInvoice = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/invoice/invoice'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 样式特点
|
||||
- 使用 `position: fixed` 实现悬浮定位
|
||||
- 渐变背景色增强视觉效果
|
||||
- 阴影效果提升层次感
|
||||
- 响应式设计适配不同设备
|
||||
|
||||
## 页面流程
|
||||
|
||||
1. **订单记录页面** → 显示悬浮发票按钮
|
||||
2. **点击按钮** → 跳转到发票选择页面
|
||||
3. **选择课程** → 进入发票信息填写页面
|
||||
4. **填写信息** → 提交发票申请
|
||||
5. **返回页面** → 回到订单记录页面
|
||||
|
||||
## 设计优势
|
||||
|
||||
### 1. 用户体验
|
||||
- **便捷访问**: 无需返回首页,直接在订单页面即可开票
|
||||
- **视觉突出**: 悬浮按钮醒目,用户容易发现
|
||||
- **操作简单**: 一键点击即可进入开票流程
|
||||
|
||||
### 2. 界面设计
|
||||
- **现代化**: 渐变色彩和阴影效果符合现代UI设计趋势
|
||||
- **一致性**: 与整体页面风格保持一致
|
||||
- **响应性**: 支持触摸反馈和动画效果
|
||||
|
||||
### 3. 功能完整
|
||||
- **流程完整**: 从订单记录到发票开具的完整流程
|
||||
- **页面跳转**: 正确的路由配置和页面跳转
|
||||
- **状态管理**: 按钮状态和页面状态的有效管理
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 悬浮按钮使用固定定位,不会随页面滚动而移动
|
||||
2. 按钮层级较高,确保在各种情况下都能正常显示
|
||||
3. 支持触摸反馈,提供良好的移动端体验
|
||||
4. 按钮位置经过精心设计,不会遮挡页面内容
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 可以添加按钮的显示/隐藏控制
|
||||
2. 可以增加按钮的动画效果
|
||||
3. 可以添加按钮的权限控制
|
||||
4. 可以优化按钮在不同设备上的显示效果
|
||||
5. 可以添加按钮的使用统计功能
|
||||
83
README_INVOICE.md
Normal file
83
README_INVOICE.md
Normal file
@ -0,0 +1,83 @@
|
||||
# 精品课开具发票页面
|
||||
|
||||
## 功能说明
|
||||
|
||||
这是一个精品课开具发票的页面,完全按照设计图100%还原实现。
|
||||
|
||||
## 页面特性
|
||||
|
||||
### 1. 状态栏
|
||||
- 显示时间 "9:41"
|
||||
- 显示信号、WiFi、电池等状态图标
|
||||
|
||||
### 2. 导航栏
|
||||
- 左侧:红色返回按钮
|
||||
- 中间:红色标题 "开具发票"
|
||||
- 右侧:红色圆形信息按钮
|
||||
|
||||
### 3. 标签页
|
||||
- "待开发票" - 当前激活状态
|
||||
- "开票历史" - 可切换查看
|
||||
- 标签页之间有灰色分隔线
|
||||
|
||||
### 4. 课程列表
|
||||
- 显示5个课程项目
|
||||
- 每个项目包含:
|
||||
- 圆形复选框(可选中/取消)
|
||||
- 课程缩略图(使用医生头像占位图)
|
||||
- 课程描述文字
|
||||
- 实际支付价格(红色显示)
|
||||
- 默认第3个课程被选中
|
||||
|
||||
### 5. 底部操作栏
|
||||
- 左侧:全选复选框和"全选"文字
|
||||
- 中间:显示开票金额和已选课程数量
|
||||
- 右侧:红色"开具发票"按钮
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 访问页面
|
||||
在首页功能网格中点击"开具发票"按钮,即可跳转到发票页面。
|
||||
|
||||
### 2. 选择课程
|
||||
- 点击单个课程的复选框来选中/取消选中
|
||||
- 点击底部的"全选"复选框来全选/取消全选所有课程
|
||||
|
||||
### 3. 开具发票
|
||||
- 选择要开票的课程后,底部会显示总金额
|
||||
- 点击"开具发票"按钮进行开票操作
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 文件结构
|
||||
```
|
||||
pages_course/
|
||||
└── invoice/
|
||||
└── invoice.vue # 发票页面主文件
|
||||
```
|
||||
|
||||
### 主要功能
|
||||
- 响应式布局,适配不同屏幕尺寸
|
||||
- 复选框状态管理
|
||||
- 动态计算选中课程数量和总金额
|
||||
- 页面路由配置已添加到 pages.json
|
||||
|
||||
### 样式特点
|
||||
- 使用红色作为主色调(#ff0000)
|
||||
- 橙色作为选中状态颜色(#ff6b35)
|
||||
- 现代化的UI设计,包含圆角、阴影等效果
|
||||
- 响应式设计,支持不同设备
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 页面使用了自定义导航栏样式
|
||||
2. 课程图片使用了占位图 `/static/placeholder_doctor.png`
|
||||
3. 页面已配置为无导航栏模式(navigationStyle: "custom")
|
||||
4. 支持返回上一页功能
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 可以添加真实的课程数据接口
|
||||
2. 可以增加发票类型选择功能
|
||||
3. 可以添加发票信息填写表单
|
||||
4. 可以增加开票记录查询功能
|
||||
119
README_INVOICE_DESIGN_UPDATE.md
Normal file
119
README_INVOICE_DESIGN_UPDATE.md
Normal file
@ -0,0 +1,119 @@
|
||||
# 发票页面设计优化
|
||||
|
||||
## 优化概述
|
||||
|
||||
对发票页面进行了全面的视觉设计优化,提升了用户体验和界面美观度。
|
||||
|
||||
## 主要优化内容
|
||||
|
||||
### 1. 整体视觉风格
|
||||
- **渐变背景**: 从浅灰色到白色的渐变背景,增加层次感
|
||||
- **现代化设计**: 采用卡片式设计,增加阴影和圆角
|
||||
- **色彩统一**: 使用橙色系主色调(#ff6b35 到 #ff8c42)
|
||||
|
||||
### 2. 导航栏优化
|
||||
- **渐变背景**: 添加微妙的渐变背景
|
||||
- **阴影效果**: 增加底部阴影,提升层次感
|
||||
- **交互反馈**: 按钮点击时有缩放和背景色变化
|
||||
- **标题样式**: 使用渐变文字效果,更加醒目
|
||||
|
||||
### 3. 标签页优化
|
||||
- **活跃状态**: 活跃标签使用圆角胶囊背景
|
||||
- **渐变效果**: 橙色渐变背景,白色文字
|
||||
- **底部指示器**: 渐变色的底部指示条
|
||||
- **悬停效果**: 非活跃标签悬停时显示橙色
|
||||
|
||||
### 4. 开票历史记录优化
|
||||
- **卡片设计**: 每个记录项都是独立的卡片
|
||||
- **左侧装饰条**: 橙色渐变装饰条,增加视觉层次
|
||||
- **渐变背景**: 微妙的渐变背景
|
||||
- **阴影效果**: 柔和的阴影,提升立体感
|
||||
- **状态标签**: 状态标签使用渐变背景和阴影
|
||||
- **交互反馈**: 点击时有轻微的下移和阴影变化
|
||||
|
||||
## 设计特点
|
||||
|
||||
### 1. 色彩搭配
|
||||
- **主色调**: 橙色系(#ff6b35, #ff8c42)
|
||||
- **辅助色**: 绿色(成功)、红色(失败)、蓝色(处理中)
|
||||
- **中性色**: 灰色系用于文字和边框
|
||||
|
||||
### 2. 视觉效果
|
||||
- **渐变**: 多处使用渐变效果,增加现代感
|
||||
- **阴影**: 不同层级的阴影,营造立体感
|
||||
- **圆角**: 统一的圆角设计,更加友好
|
||||
- **过渡**: 所有交互都有平滑的过渡动画
|
||||
|
||||
### 3. 交互体验
|
||||
- **点击反馈**: 按钮和卡片都有点击反馈
|
||||
- **悬停效果**: 标签页有悬停状态
|
||||
- **动画过渡**: 平滑的状态切换动画
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. CSS特性
|
||||
```scss
|
||||
// 渐变背景
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
|
||||
// 渐变文字
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
// 阴影效果
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
// 过渡动画
|
||||
transition: all 0.3s ease;
|
||||
```
|
||||
|
||||
### 2. 伪元素装饰
|
||||
```scss
|
||||
// 左侧装饰条
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6rpx;
|
||||
background: linear-gradient(180deg, #ff6b35 0%, #ff8c42 100%);
|
||||
border-radius: 0 3rpx 3rpx 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 状态样式
|
||||
```scss
|
||||
// 成功状态
|
||||
&.success {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
## 用户体验提升
|
||||
|
||||
### 1. 视觉层次
|
||||
- 清晰的信息层级
|
||||
- 合理的间距和留白
|
||||
- 突出的重要信息
|
||||
|
||||
### 2. 交互反馈
|
||||
- 即时的视觉反馈
|
||||
- 平滑的动画过渡
|
||||
- 直观的操作提示
|
||||
|
||||
### 3. 美观度
|
||||
- 现代化的设计风格
|
||||
- 协调的色彩搭配
|
||||
- 精致的细节处理
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **深色模式**: 可以添加深色主题支持
|
||||
2. **动画效果**: 可以增加更多的微交互动画
|
||||
3. **响应式**: 可以优化不同屏幕尺寸的显示
|
||||
4. **无障碍**: 可以增加无障碍访问支持
|
||||
5. **主题定制**: 可以支持用户自定义主题色彩
|
||||
185
README_INVOICE_HISTORY.md
Normal file
185
README_INVOICE_HISTORY.md
Normal file
@ -0,0 +1,185 @@
|
||||
# 开票历史功能
|
||||
|
||||
## 功能说明
|
||||
|
||||
在发票页面添加了"开票历史"标签页,用户可以查看历史开票记录,包括开票状态、课程数量、开票金额等信息。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 标签页切换
|
||||
- **待开发票**: 显示可选择的课程列表,支持批量选择和开票
|
||||
- **开票历史**: 显示历史开票记录,包含详细的开票信息
|
||||
|
||||
### 2. 开票历史记录
|
||||
- **日期显示**: 显示开票日期(如:2021年6月11日)
|
||||
- **课程数量**: 显示该次开票包含的课程数量(如:共计3个课程)
|
||||
- **开票状态**: 显示开票的当前状态
|
||||
- **开票金额**: 显示开票的金额信息
|
||||
|
||||
### 3. 开票状态类型
|
||||
- **已开票**: 绿色显示,表示开票成功
|
||||
- **开票失败**: 红色显示,表示开票失败
|
||||
- **开票中**: 蓝色显示,表示正在处理中
|
||||
|
||||
## 页面结构
|
||||
|
||||
### 1. 标签页导航
|
||||
```vue
|
||||
<view class="tabs">
|
||||
<view class="tab-item" :class="{ active: activeTab === 'pending' }">
|
||||
<text>待开发票</text>
|
||||
</view>
|
||||
<view class="tab-divider"></view>
|
||||
<view class="tab-item" :class="{ active: activeTab === 'history' }">
|
||||
<text>开票历史</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 内容区域
|
||||
- **待开发票**: 显示课程列表和底部操作栏
|
||||
- **开票历史**: 显示历史记录列表
|
||||
|
||||
### 3. 历史记录项
|
||||
```vue
|
||||
<view class="history-item" v-for="item in historyList">
|
||||
<view class="history-left">
|
||||
<text class="history-date">{{ item.date }}</text>
|
||||
<text class="history-count">{{ item.courseCount }}</text>
|
||||
</view>
|
||||
<view class="history-right">
|
||||
<text class="history-status">{{ item.statusText }}</text>
|
||||
<view class="history-arrow">›</view>
|
||||
<text class="history-amount">{{ item.amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
## 数据结构
|
||||
|
||||
### 1. 历史记录数据
|
||||
```javascript
|
||||
historyList: [
|
||||
{
|
||||
id: 1,
|
||||
date: '2021年6月11日',
|
||||
courseCount: '共计3个课程',
|
||||
status: 'success',
|
||||
statusText: '已开票',
|
||||
amount: '¥0.00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
date: '2021年6月11日',
|
||||
courseCount: '共计3个课程',
|
||||
status: 'failed',
|
||||
statusText: '开票失败',
|
||||
amount: '¥0.00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
date: '2021年6月11日',
|
||||
courseCount: '共计3个课程',
|
||||
status: 'processing',
|
||||
statusText: '开票中',
|
||||
amount: '¥0.00'
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 2. 状态枚举
|
||||
- `success`: 已开票(绿色)
|
||||
- `failed`: 开票失败(红色)
|
||||
- `processing`: 开票中(蓝色)
|
||||
|
||||
## 交互功能
|
||||
|
||||
### 1. 标签页切换
|
||||
- 点击"待开发票"标签显示课程选择界面
|
||||
- 点击"开票历史"标签显示历史记录界面
|
||||
- 标签页之间有视觉分隔线
|
||||
|
||||
### 2. 历史记录查看
|
||||
- 点击任意历史记录项可查看详情
|
||||
- 显示相应的状态提示信息
|
||||
- 支持后续扩展详情页面
|
||||
|
||||
### 3. 状态颜色区分
|
||||
- 不同状态使用不同颜色显示
|
||||
- 提供清晰的视觉反馈
|
||||
- 符合用户认知习惯
|
||||
|
||||
## 样式设计
|
||||
|
||||
### 1. 历史记录项样式
|
||||
- 浅灰色背景(#f8f8f8)
|
||||
- 圆角设计(8rpx)
|
||||
- 卡片式布局,带有间距
|
||||
- 底部边框分隔线
|
||||
|
||||
### 2. 状态颜色
|
||||
- 成功状态:绿色(#52c41a)
|
||||
- 失败状态:红色(#ff4d4f)
|
||||
- 处理中状态:蓝色(#1890ff)
|
||||
- 默认状态:灰色(#666666)
|
||||
|
||||
### 3. 布局特点
|
||||
- 左右分布,信息清晰
|
||||
- 响应式设计,适配不同屏幕
|
||||
- 触摸友好的交互区域
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 查看开票历史
|
||||
- 在发票页面点击"开票历史"标签
|
||||
- 浏览历史开票记录
|
||||
- 查看开票状态和详细信息
|
||||
|
||||
### 2. 切换标签页
|
||||
- 点击"待开发票"返回课程选择界面
|
||||
- 点击"开票历史"查看历史记录
|
||||
- 标签页状态实时更新
|
||||
|
||||
### 3. 查看记录详情
|
||||
- 点击任意历史记录项
|
||||
- 系统显示相应的状态信息
|
||||
- 支持后续功能扩展
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 条件渲染
|
||||
```vue
|
||||
<view v-if="activeTab === 'pending'" class="pending-content">
|
||||
<!-- 待开发票内容 -->
|
||||
</view>
|
||||
<view v-if="activeTab === 'history'" class="history-content">
|
||||
<!-- 开票历史内容 -->
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 数据绑定
|
||||
- 使用Vue的响应式数据绑定
|
||||
- 支持动态数据更新
|
||||
- 状态切换实时响应
|
||||
|
||||
### 3. 事件处理
|
||||
```javascript
|
||||
viewHistoryDetail(item) {
|
||||
console.log('查看开票历史详情:', item)
|
||||
uni.showToast({
|
||||
title: `查看${item.statusText}详情`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **详情页面**: 可以添加开票历史详情页面
|
||||
2. **状态筛选**: 可以添加按状态筛选历史记录
|
||||
3. **时间筛选**: 可以添加按时间范围筛选
|
||||
4. **搜索功能**: 可以添加搜索特定开票记录
|
||||
5. **导出功能**: 可以添加导出开票历史记录
|
||||
6. **分页加载**: 可以添加分页加载更多历史记录
|
||||
7. **实时更新**: 可以添加开票状态的实时更新
|
||||
8. **通知功能**: 可以添加开票完成的通知提醒
|
||||
95
README_INVOICE_INFO.md
Normal file
95
README_INVOICE_INFO.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 填写发票信息页面
|
||||
|
||||
## 功能说明
|
||||
|
||||
这是一个填写发票信息的页面,完全按照设计图100%还原实现。用户在选择课程后,通过此页面填写详细的发票信息。
|
||||
|
||||
## 页面特性
|
||||
|
||||
### 1. 状态栏
|
||||
- 显示时间 "9:41"
|
||||
- 显示信号、WiFi、电池等状态图标
|
||||
|
||||
### 2. 导航栏
|
||||
- 左侧:红色返回按钮
|
||||
- 中间:红色标题 "填写发票信息"
|
||||
- 右侧:红色圆形信息按钮
|
||||
|
||||
### 3. 表单内容
|
||||
- **发票抬头**: 输入框,必填项
|
||||
- **单位税号**: 输入框,必填项
|
||||
- **发票内容**: 显示固定值 "培训费"
|
||||
- **发票金额**: 显示固定值 "¥59.00"(红色显示)
|
||||
- **电子邮箱**: 输入框,必填项
|
||||
|
||||
### 4. 提交按钮
|
||||
- 底部固定位置的青色按钮
|
||||
- 白色文字 "提交"
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 访问页面
|
||||
在发票页面选择课程后,点击"开具发票"按钮即可跳转到此页面。
|
||||
|
||||
### 2. 填写信息
|
||||
- 发票抬头:输入公司或个人名称
|
||||
- 单位税号:输入税务登记号
|
||||
- 电子邮箱:输入接收发票的邮箱地址
|
||||
|
||||
### 3. 提交表单
|
||||
- 点击"提交"按钮
|
||||
- 系统会验证必填字段和邮箱格式
|
||||
- 提交成功后自动返回上一页
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 文件结构
|
||||
```
|
||||
pages_course/
|
||||
└── invoice_info/
|
||||
└── invoice_info.vue # 发票信息填写页面
|
||||
```
|
||||
|
||||
### 主要功能
|
||||
- 表单验证(必填字段、邮箱格式)
|
||||
- 响应式布局设计
|
||||
- 自定义导航栏
|
||||
- 表单数据双向绑定
|
||||
|
||||
### 样式特点
|
||||
- 使用红色作为主色调(#ff0000)
|
||||
- 青色作为提交按钮颜色(#20b2aa)
|
||||
- 白色卡片式表单设计
|
||||
- 浅灰色分隔线
|
||||
- 现代化UI设计,包含圆角、阴影等效果
|
||||
|
||||
## 表单验证规则
|
||||
|
||||
1. **发票抬头**: 必填,不能为空
|
||||
2. **单位税号**: 必填,不能为空
|
||||
3. **电子邮箱**: 必填,必须符合邮箱格式
|
||||
|
||||
## 页面流程
|
||||
|
||||
1. 用户在发票页面选择课程
|
||||
2. 点击"开具发票"按钮
|
||||
3. 跳转到填写发票信息页面
|
||||
4. 填写必要信息
|
||||
5. 点击提交
|
||||
6. 验证通过后返回上一页
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 页面使用了自定义导航栏样式
|
||||
2. 发票内容和金额为固定值,不可编辑
|
||||
3. 页面已配置为无导航栏模式(navigationStyle: "custom")
|
||||
4. 支持返回上一页功能
|
||||
5. 表单验证失败会显示相应的提示信息
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 可以添加发票类型选择功能
|
||||
2. 可以增加发票抬头历史记录
|
||||
3. 可以添加发票预览功能
|
||||
4. 可以增加发票模板选择
|
||||
5. 可以添加发票状态查询功能
|
||||
@ -1,5 +1,9 @@
|
||||
import {request} from '@/utils/request.js'
|
||||
const api = {
|
||||
|
||||
expertWxLogin(jscode){
|
||||
return request('/miniprogram/expertLogin', {jscode: jscode}, 'get', true);
|
||||
},
|
||||
// 微信登录
|
||||
wxLogin(data) {
|
||||
return request('/login/wechat/mobile', data, 'post', true);
|
||||
|
||||
@ -22,8 +22,8 @@ const course_api = {
|
||||
},
|
||||
|
||||
// 创建订单
|
||||
createExcellencourseMixedOrder(id, order_pay_type) {
|
||||
return request('/expertPay/createExcellencourseOrder', {appid:"wx061c1f4e16a5f20f", openid:"", excellencourse_id: id, order_pay_type: order_pay_type}, 'post', true);
|
||||
createExcellencourseMixedOrder(id, order_pay_type, openid) {
|
||||
return request('/expertPay/createExcellencourseOrder', {appid:"wx061c1f4e16a5f20f", openid: openid, excellencourse_id: id, order_pay_type: order_pay_type}, 'post', true);
|
||||
},
|
||||
|
||||
// 订单列表
|
||||
@ -31,6 +31,46 @@ const course_api = {
|
||||
return request('/expertAPI/listExcellencourseOrder', {order_status: order_status, page:page}, 'post', true);
|
||||
},
|
||||
|
||||
// 我的课程
|
||||
listMyExcellencourse(state, page) {
|
||||
return request('/expertAPI/listMyExcellencourse', {state:state,page:page}, 'post', true);
|
||||
},
|
||||
|
||||
// 添加评论
|
||||
addExcellencourseComment(data) {
|
||||
return request('/expertAPI/addExcellencourseComment', data, 'post', true);
|
||||
},
|
||||
|
||||
// 一级分类
|
||||
listExcellencourseFirstType() {
|
||||
return request('/expertAPI/listExcellencourseFirstType', {}, 'post', true);
|
||||
},
|
||||
|
||||
// 二级分类
|
||||
listExcellencourseSecondType(type_id) {
|
||||
return request('/expertAPI/listExcellencourseSecondType', {first_id: type_id}, 'post', true);
|
||||
},
|
||||
|
||||
// 筛选列表
|
||||
excellencourseList(data) {
|
||||
return request('/expertAPI/excellencourseScreen', data, 'post', true);
|
||||
},
|
||||
|
||||
// 未开票订单列表
|
||||
listExcellencourseOrderNoInvoice() {
|
||||
return request('/expertAPI/listExcellencourseOrderNoInvoice', {}, 'post', true);
|
||||
},
|
||||
|
||||
// 开票历史列表
|
||||
listExcellencourseOrderInvoiceHistory(page) {
|
||||
return request('/expertAPI/listExcellencourseOrderInvoice', {page:page}, 'post', true);
|
||||
},
|
||||
|
||||
// 获取订单发票
|
||||
getExcellencourseOrderInvoice(order_id) {
|
||||
return request('/expertAPI/getExcellencourseOrderInvoice', {id:order_id}, 'post', true);
|
||||
},
|
||||
|
||||
// 短信验证码登录
|
||||
smsLogin(data,header){
|
||||
return request('/expertAPI/smsLogin', data, 'post', true,'application/json',header);
|
||||
|
||||
@ -17,7 +17,8 @@
|
||||
"delay" : 0
|
||||
},
|
||||
"modules" : {
|
||||
"OAuth" : {}
|
||||
"OAuth" : {},
|
||||
"Payment" : {}
|
||||
},
|
||||
/* 模块配置 */
|
||||
"distribute" : {
|
||||
@ -50,7 +51,15 @@
|
||||
"univerify" : {},
|
||||
"weixin" : {
|
||||
"appid" : "wxbf3658f5e674667c",
|
||||
"UniversalLinks" : ""
|
||||
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
|
||||
}
|
||||
},
|
||||
"payment" : {
|
||||
"appleiap" : {},
|
||||
"weixin" : {
|
||||
"__platform__" : [ "ios", "android" ],
|
||||
"appid" : "wxbf3658f5e674667c",
|
||||
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
pages.json
31
pages.json
@ -142,6 +142,36 @@
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "invoice/invoice",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "开具发票",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "invoice_info/invoice_info",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "填写发票信息",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "invoice_detail/invoice_detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "开票详情",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -329,6 +359,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
}],
|
||||
|
||||
@ -354,6 +354,10 @@
|
||||
icon: course,
|
||||
text: '精品课'
|
||||
},
|
||||
{
|
||||
icon: '',
|
||||
text: '开具发票'
|
||||
},
|
||||
{
|
||||
icon: more,
|
||||
text: '更多'
|
||||
@ -582,8 +586,18 @@
|
||||
// 网格点击事件
|
||||
const onClick = (e) => {
|
||||
console.log('点击了第' + e.detail.index + '个');
|
||||
const clickedItem = gridList[e.detail.index];
|
||||
|
||||
// 处理开具发票的点击
|
||||
if (clickedItem.text === '开具发票') {
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/invoice/invoice'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: `点击了${gridList[e.detail.index].text}`,
|
||||
title: `点击了${clickedItem.text}`,
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
@ -63,6 +63,15 @@
|
||||
<view class="sms-login" @click="onSmsLogin" style="margin-top: 20rpx;">
|
||||
<text class="sms-text">短信验证码登录</text>
|
||||
</view>
|
||||
|
||||
<!-- 微信登录 -->
|
||||
<view class="wechat-login" @click="onWechatLogin">
|
||||
<view class="wechat-icon">
|
||||
<up-image :src="wxImg" width="100rpx" height="100rpx" ></up-image>
|
||||
</view>
|
||||
<text class="wechat-text">微信登录</text>
|
||||
</view>
|
||||
|
||||
<!-- #endif -->
|
||||
|
||||
</view>
|
||||
@ -98,6 +107,7 @@
|
||||
import wxImg from "@/static/weixin_login.png"
|
||||
import checkImg from "@/static/login_new_unselect.png"
|
||||
import checkOnImg from "@/static/login_new_select.png"
|
||||
import api from "@/api/api.js"
|
||||
|
||||
const customStyle = reactive({
|
||||
height: "100rpx",
|
||||
@ -164,10 +174,35 @@
|
||||
}
|
||||
|
||||
console.log('微信登录');
|
||||
uni.showToast({
|
||||
title: '微信登录功能开发中',
|
||||
icon: 'none'
|
||||
wx.login({
|
||||
success(res) {
|
||||
if (res.code) {
|
||||
// 发送 res.code 到后台换取 openId, sessionKey, unionId
|
||||
api.expertWxLogin(res.code).then((data) => {
|
||||
console.log(data)
|
||||
if (data.data.openid) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
uni.setStorageSync('DEV_APPID', data.data.openid);
|
||||
} else {
|
||||
uni.setStorageSync('AUTH_APPID', data.data.openid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let openid = ""
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
openid = uni.getStorageSync('DEV_APPID');
|
||||
} else {
|
||||
openid = uni.getStorageSync('AUTH_APPID');
|
||||
}
|
||||
console.log("openid: ", openid)
|
||||
})
|
||||
} else {
|
||||
console.log('登录失败!' + res.errMsg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// 切换协议同意状态
|
||||
|
||||
@ -28,7 +28,6 @@
|
||||
<image :src="banner.sroll_img || banner.image || '/static/lunbo_bg.png'" mode="aspectFill" class="banner-image"></image>
|
||||
<view class="banner-content">
|
||||
<text class="banner-title">{{ banner.title || '课程标题' }}</text>
|
||||
<text class="banner-subtitle">{{ banner.subtitle || '课程描述' }}</text>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
@ -54,7 +53,7 @@
|
||||
:key="typeItem.id"
|
||||
@click="goVideoType(typeItem.id, typeItem.name)">
|
||||
<view class="function-icon">
|
||||
<image :src="typeItem.img" mode="aspectFit"></image>
|
||||
<image style="height: 50rpx;" :src="typeItem.img" mode="aspectFit"></image>
|
||||
<!-- <uni-icons :type="getFunctionIconType(index)" size="24" :color="getFunctionIconColor(index)"></uni-icons> -->
|
||||
</view>
|
||||
<text class="function-text">{{ typeItem.name }}</text>
|
||||
@ -65,7 +64,7 @@
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">精品小课</text>
|
||||
<view class="more-btn" @click="goMorePremium">
|
||||
<view class="more-btn" @click="goMorePremium('精品小课')">
|
||||
<text>更多</text>
|
||||
<uni-icons type="arrowright" size="14" color="#999"></uni-icons>
|
||||
</view>
|
||||
@ -80,7 +79,7 @@
|
||||
<text class="course-title">{{ course.title }}</text>
|
||||
<view class="course-meta">
|
||||
<view class="participant-count">{{ course.study_num }}人</view>
|
||||
<text class="course-price" v-if="course.discount_price > 0">¥{{ course.discount_price }}</text>
|
||||
<text class="course-price" v-if="course.discount_price > 0">¥{{ (course.discount_price / 100).toFixed(2) }}</text>
|
||||
<text class="course-price free" v-else>免费</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -95,7 +94,7 @@
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">学完返现</text>
|
||||
<view class="more-btn" @click="goMoreRewards">
|
||||
<view class="more-btn" @click="goMoreRewards('学完返现')">
|
||||
<text>更多</text>
|
||||
<uni-icons type="arrowright" size="14" color="#999"></uni-icons>
|
||||
</view>
|
||||
@ -114,7 +113,7 @@
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">福利课堂</text>
|
||||
<view class="more-btn" @click="goMoreWelfare">
|
||||
<view class="more-btn" @click="goMoreWelfare('福利课堂')">
|
||||
<text>更多</text>
|
||||
<uni-icons type="arrowright" size="14" color="#999"></uni-icons>
|
||||
</view>
|
||||
@ -139,11 +138,11 @@
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 热门课程 -->
|
||||
<!-- 课程推荐 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">热门课程</text>
|
||||
<view class="more-btn" @click="goMoreHot">
|
||||
<text class="section-title">课程推荐</text>
|
||||
<view class="more-btn" @click="goMoreHot('课程推荐')">
|
||||
<text>更多</text>
|
||||
<uni-icons type="arrowright" size="14" color="#999"></uni-icons>
|
||||
</view>
|
||||
@ -154,7 +153,7 @@
|
||||
<view class="course-content">
|
||||
<text class="course-name">{{ course.title }}</text>
|
||||
<text class="course-desc">{{ course.tags || '' }}</text>
|
||||
<text class="course-cost">¥{{ course.discount_price }}</text>
|
||||
<text class="course-cost">¥{{ (course.discount_price / 100).toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -188,115 +187,7 @@ const premiumCourseList = ref([]) // 新增:精品小课数据
|
||||
const rewardCourseList = ref([]) // 新增:学完返现数据
|
||||
const welfareCourseList = ref([]) // 新增:福利课堂数据
|
||||
const recommendedCourseList = ref([]) // 新增:课程推荐数据
|
||||
const bannerList = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: "小懂医生讲HIV和感染",
|
||||
subtitle: "授课专家: 黄湛镰 副主任医师 | 中山大学附属第三医院感染科",
|
||||
image: "/static/lunbo_bg.png",
|
||||
link: "/pages_course/hiv_detail"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "肝脏肿瘤临床影像学习",
|
||||
subtitle: "授课专家: 王学浩 教授 | 南京医科大学第一附属医院",
|
||||
image: "/static/paper_bg.png",
|
||||
link: "/pages_course/tumor_detail"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "慢性肝病营养治疗指南",
|
||||
subtitle: "授课专家: 段钟平 教授 | 首都医科大学附属北京佑安医院",
|
||||
image: "/static/pap_bg.png",
|
||||
link: "/pages_course/nutrition_detail"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "肝移植术后管理要点",
|
||||
subtitle: "授课专家: 郑树森 院士 | 浙江大学医学院附属第一医院",
|
||||
image: "/static/bo_bg.png",
|
||||
link: "/pages_course/transplant_detail"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "肝癌早期筛查与预防",
|
||||
subtitle: "授课专家: 陈孝平 院士 | 华中科技大学同济医学院",
|
||||
image: "/static/livebg.png",
|
||||
link: "/pages_course/screening_detail"
|
||||
}
|
||||
])
|
||||
const hotCourses = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: "小懂医生讲HIV和感染-各类抗菌药特性解读III",
|
||||
description: "小懂医生讲HIV和感染-各类抗菌药特性解读III",
|
||||
price: "20.00",
|
||||
thumbnail: "/static/jingpinkecheng.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "小懂医生讲HIV和感染-各类抗菌药特性解读III",
|
||||
description: "小懂医生讲HIV和感染-各类抗菌药特性解读III",
|
||||
price: "20.00",
|
||||
thumbnail: "/static/jingpinkecheng.png"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "小懂医生讲HIV和感染-各类抗菌药特性解读III",
|
||||
description: "小懂医生讲HIV和感染-各类抗菌药特性解读III",
|
||||
price: "20.00",
|
||||
thumbnail: "/static/jingpinkecheng.png"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "小懂医生讲HIV和感染物",
|
||||
description: "",
|
||||
price: "50.00",
|
||||
thumbnail: "/static/jingpinkecheng.png"
|
||||
}
|
||||
])
|
||||
const welfareList = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: "《中生肝脏病学》云解读系列会七",
|
||||
subtitle: "汪晖 院士 北京大学医学院",
|
||||
teacher: "汪晖 院士",
|
||||
price: "免费",
|
||||
image: "/static/fulicard.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "肝病诊疗新进展研讨会",
|
||||
subtitle: "王学浩 教授 南京医科大学",
|
||||
teacher: "王学浩 教授",
|
||||
price: "免费",
|
||||
image: "/static/paper_bg.png"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "肝癌早期筛查与预防",
|
||||
subtitle: "陈孝平 院士 华中科技大学",
|
||||
teacher: "陈孝平 院士",
|
||||
price: "免费",
|
||||
image: "/static/pap_bg.png"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "慢性肝病营养治疗指南",
|
||||
subtitle: "段钟平 教授 首都医科大学",
|
||||
teacher: "段钟平 教授",
|
||||
price: "免费",
|
||||
image: "/static/bo_bg.png"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "肝移植术后管理要点",
|
||||
subtitle: "郑树森 院士 浙江大学医学院",
|
||||
teacher: "郑树森 院士",
|
||||
price: "免费",
|
||||
image: "/static/livebg.png"
|
||||
}
|
||||
])
|
||||
const bannerList = ref([])
|
||||
|
||||
// 方法
|
||||
const getSystemInfo = () => {
|
||||
@ -311,7 +202,9 @@ const getSystemInfo = () => {
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
uni.navigateTo({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
|
||||
const goSearch = () => {
|
||||
@ -322,6 +215,9 @@ const goSearch = () => {
|
||||
|
||||
const goCategory = (type, name) => {
|
||||
console.log('跳转分类:', type, name)
|
||||
uni.navigateTo({
|
||||
url: `/pages_course/course_filter/course_filter?type=${type}`
|
||||
})
|
||||
}
|
||||
|
||||
const getCategoryIconClass = (index) => {
|
||||
@ -337,29 +233,6 @@ const getCategoryIconClass = (index) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getFunctionIconClass = (index) => {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return 'fire-icon';
|
||||
case 1:
|
||||
return 'star-icon';
|
||||
case 2:
|
||||
return 'gift-icon';
|
||||
case 3:
|
||||
return 'heart-icon';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const goHotCourses = () => {
|
||||
console.log('跳转热门课程')
|
||||
}
|
||||
|
||||
const goPremiumCourses = () => {
|
||||
console.log('跳转精品小课')
|
||||
}
|
||||
|
||||
const goPremiumCourse = (course) => {
|
||||
console.log('跳转精品小课详情:', course)
|
||||
@ -369,9 +242,6 @@ const goPremiumCourse = (course) => {
|
||||
})
|
||||
}
|
||||
|
||||
const goStudyRewards = () => {
|
||||
console.log('跳转学克返现')
|
||||
}
|
||||
|
||||
const goRewardCourse = (course) => {
|
||||
console.log('跳转返现课程:', course)
|
||||
@ -381,24 +251,32 @@ const goRewardCourse = (course) => {
|
||||
})
|
||||
}
|
||||
|
||||
const goWelfareCourses = () => {
|
||||
console.log('跳转福利课堂')
|
||||
}
|
||||
|
||||
const goMorePremium = () => {
|
||||
const goMorePremium = (name) => {
|
||||
console.log('查看更多精品课程')
|
||||
uni.navigateTo({
|
||||
url: `/pages_course/course_filter/course_filter?special_type=${specialTypeList.value.find(item => item.name == name).id}&special_name=${name}`
|
||||
})
|
||||
}
|
||||
|
||||
const goMoreRewards = () => {
|
||||
const goMoreRewards = (name) => {
|
||||
console.log('查看更多返现')
|
||||
uni.navigateTo({
|
||||
url: `/pages_course/course_filter/course_filter?special_type=${specialTypeList.value.find(item => item.name == name).id}&special_name=${name}`
|
||||
})
|
||||
}
|
||||
|
||||
const goMoreWelfare = () => {
|
||||
const goMoreWelfare = (name) => {
|
||||
console.log('查看更多福利')
|
||||
uni.navigateTo({
|
||||
url: `/pages_course/course_filter/course_filter?special_type=${specialTypeList.value.find(item => item.name == name).id}&special_name=${name}`
|
||||
})
|
||||
}
|
||||
|
||||
const goMoreHot = () => {
|
||||
const goMoreHot = (name) => {
|
||||
console.log('查看更多热门')
|
||||
uni.navigateTo({
|
||||
url: `/pages_course/course_filter/course_filter?special_type=${specialTypeList.value.find(item => item.name == name).id}&special_name=${name}`
|
||||
})
|
||||
}
|
||||
|
||||
const goCourseDetail = (course) => {
|
||||
@ -421,7 +299,7 @@ const goBannerDetail = (banner) => {
|
||||
console.log('横幅详情:', banner)
|
||||
// 跳转到课程详情页面
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/course_detail/course_detail'
|
||||
url: '/pages_course/course_detail/course_detail?id=' + banner.id
|
||||
})
|
||||
}
|
||||
|
||||
@ -487,104 +365,19 @@ const getIndex = async () => {
|
||||
}
|
||||
} else {
|
||||
console.warn('API返回数据格式异常:', res)
|
||||
setDefaultData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取分类数据失败:', error)
|
||||
setDefaultData()
|
||||
}
|
||||
}
|
||||
|
||||
// 设置默认数据
|
||||
const setDefaultData = () => {
|
||||
// 设置默认分类数据作为备用
|
||||
firstTypeList.value = [
|
||||
{ id: 185, name: "肝病", img: "/static/icon_home_my_patient.png" },
|
||||
{ id: 188, name: "肿瘤", img: "/static/icon_home_my_library.png" },
|
||||
{ id: 195, name: "感染", img: "/static/icon_home_video.png" }
|
||||
]
|
||||
|
||||
// 设置默认功能按钮数据作为备用
|
||||
specialTypeList.value = [
|
||||
{ type_id: 189, type_name: "课程推荐" },
|
||||
{ type_id: 190, type_name: "精品小课" },
|
||||
{ type_id: 191, type_name: "学完返现" },
|
||||
{ type_id: 192, type_name: "福利课堂" }
|
||||
]
|
||||
|
||||
// 设置默认精品小课数据作为备用
|
||||
premiumCourseList.value = [
|
||||
{
|
||||
id: 1,
|
||||
title: "肝脏胖瘦临床、影像学习交流",
|
||||
study_num: 28,
|
||||
discount_price: 20,
|
||||
index_img: "/static/jingpingke.png"
|
||||
}
|
||||
]
|
||||
|
||||
// 设置默认学完返现数据作为备用
|
||||
rewardCourseList.value = [
|
||||
{
|
||||
id: 1,
|
||||
title: "重症肝病的抗凝治疗",
|
||||
discount_price: 2,
|
||||
back_bon: 2,
|
||||
index_img: "/static/paper_bg.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "重症肝病的抗凝治疗2",
|
||||
discount_price: 2,
|
||||
back_bon: 2,
|
||||
index_img: "/static/pap_bg.png"
|
||||
}
|
||||
]
|
||||
|
||||
// 设置默认福利课堂数据作为备用
|
||||
welfareCourseList.value = [
|
||||
{
|
||||
id: 1,
|
||||
title: "《中生肝脏病学》云解读系列会七",
|
||||
special_type_name: "福利课堂",
|
||||
study_num: 8,
|
||||
fuli_bon: 100,
|
||||
index_img: "/static/fulicard.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "肝病诊疗新进展研讨会",
|
||||
special_type_name: "福利课堂",
|
||||
study_num: 5,
|
||||
fuli_bon: 88,
|
||||
index_img: "/static/paper_bg.png"
|
||||
}
|
||||
]
|
||||
|
||||
// 设置默认课程推荐数据作为备用
|
||||
recommendedCourseList.value = [
|
||||
{
|
||||
id: 1,
|
||||
title: "测试精品课上报02",
|
||||
tags: "测试,肝胆相照",
|
||||
discount_price: 100,
|
||||
index_img: "/static/jingpinkecheng.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "测试精品课上报01",
|
||||
tags: "肝胆相照,健康中国",
|
||||
discount_price: 100,
|
||||
index_img: "/static/jingpinkecheng.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const goVideoType = (typeId, typeName) => {
|
||||
console.log('跳转视频类型:', typeId, typeName)
|
||||
// 跳转到视频类型详情页面
|
||||
uni.navigateTo({
|
||||
url: `/pages_course/video_type_detail/video_type_detail?type_id=${typeId}`
|
||||
url: `/pages_course/course_filter/course_filter?special_type=${typeId}&special_name=${typeName}`
|
||||
})
|
||||
}
|
||||
// 生命周期
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">课程详情02</view>
|
||||
<view class="nav-title">{{ courseDetail.title || '肝硬化与重症肝病' }}</view>
|
||||
<view class="nav-right" @click="goShare">
|
||||
<uni-icons type="redo" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
@ -106,7 +106,7 @@
|
||||
<!-- 课程目录内容 -->
|
||||
<view class="course-catalog" v-if="activeTab === 'catalog'">
|
||||
<!-- 课程概览 -->
|
||||
<view class="catalog-overview">
|
||||
<!-- <view class="catalog-overview">
|
||||
<view class="overview-item">
|
||||
<text class="overview-label">总课时</text>
|
||||
<text class="overview-value">{{ courseDetail.video_num || 0 }}节课</text>
|
||||
@ -119,7 +119,7 @@
|
||||
<text class="overview-label">学习进度</text>
|
||||
<text class="overview-value">{{ courseDetail.study_status || 0 }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 课程章节列表 -->
|
||||
<view class="chapter-list">
|
||||
@ -130,14 +130,14 @@
|
||||
<uni-icons :type="chapter.expanded ? 'down' : 'right'" size="16" color="#666"></uni-icons>
|
||||
<text class="chapter-name">{{ chapter.title }}</text>
|
||||
</view>
|
||||
<view class="chapter-meta">
|
||||
<!-- <view class="chapter-meta">
|
||||
<text class="lesson-count">{{ chapter.lessons.length }}节课</text>
|
||||
<text class="duration">{{ chapter.duration }}</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
<view class="chapter-status">
|
||||
<!-- <view class="chapter-status">
|
||||
<text class="status-text" :class="chapter.status">{{ chapter.statusText }}</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 课时列表 -->
|
||||
@ -150,7 +150,7 @@
|
||||
</view>
|
||||
<view class="lesson-meta">
|
||||
<text class="lesson-duration">{{ lesson.duration }}</text>
|
||||
<text class="lesson-teacher">{{ lesson.teacher }}</text>
|
||||
<!-- <text class="lesson-teacher">{{ lesson.teacher }}</text> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="lesson-status">
|
||||
@ -168,7 +168,9 @@
|
||||
<text class="review-title">课程评价</text>
|
||||
<view class="review-actions-header">
|
||||
<text class="review-count">共{{ reviewTotal || courseDetail.comment_num || 0 }}条评价</text>
|
||||
<button class="write-review-btn" @click="goToReview" v-if="courseDetail.is_buy === 1">写评价</button>
|
||||
<button class="write-review-btn" @click="goToReview" v-if="courseDetail.is_buy === 1">
|
||||
{{ courseDetail.is_commented === 1 ? '追加评论' : '写评价' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -254,7 +256,15 @@
|
||||
<uni-icons type="chat" size="80" color="#E0E0E0"></uni-icons>
|
||||
</view>
|
||||
<text class="empty-text">暂无评价</text>
|
||||
<text class="empty-desc">成为第一个评价的人吧</text>
|
||||
<text class="empty-desc" v-if="courseDetail.is_buy === 1 && courseDetail.is_commented === 1">
|
||||
您已经评价过此课程,可以追加评论
|
||||
</text>
|
||||
<text class="empty-desc" v-else-if="courseDetail.is_buy === 1">
|
||||
成为第一个评价的人吧
|
||||
</text>
|
||||
<text class="empty-desc" v-else>
|
||||
购买课程后即可评价
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载中状态 -->
|
||||
@ -268,15 +278,17 @@
|
||||
</view>
|
||||
|
||||
<!-- 底部购买栏 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="bottom-bar" v-if="courseDetail.is_buy === 0">
|
||||
<view class="price-section">
|
||||
<text class="price">¥{{ currentPriceYuan.toFixed(2) }}</text>
|
||||
<text class="price" >¥{{ currentPriceYuan.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="buy-section">
|
||||
<button class="buy-btn" @click="goBuy" v-if="courseDetail.is_buy === 0">{{ courseDetail.is_buy === 0 ? '立即购买' : '已购买' }}</button>
|
||||
<button class="buy-btn" @click="goStudy" v-else style="background-color: #4CAF50;">开始学习</button>
|
||||
<button class="buy-btn" @click="goBuy" >立即购买</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom-bar" v-else>
|
||||
<button class="buy-btn" @click="goStudy" style=" width: 100%;background-color: #4CAF50;color: #fff;">开始学习</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -569,48 +581,70 @@ const submitReply = (reviewId) => {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 找到对应的评价
|
||||
const review = reviewList.value.find(item => item.id === reviewId)
|
||||
|
||||
|
||||
if (review) {
|
||||
// 添加新回复
|
||||
const newReply = {
|
||||
id: Date.now(),
|
||||
userName: '我',
|
||||
avatar: '/static/icon_home_my_patient.png',
|
||||
time: new Date().toLocaleDateString(),
|
||||
content: replyText.value.trim(),
|
||||
isAuthor: false
|
||||
}
|
||||
|
||||
if (!review.replies) {
|
||||
review.replies = []
|
||||
}
|
||||
review.replies.push(newReply)
|
||||
|
||||
// 重置状态
|
||||
activeReplyId.value = null
|
||||
replyText.value = ''
|
||||
|
||||
uni.showToast({
|
||||
title: '回复成功',
|
||||
icon: 'success'
|
||||
course_api.addExcellencourseComment({
|
||||
excellentcourse_id: courseId.value,
|
||||
comment: replyText.value.trim(),
|
||||
p_id: reviewId,
|
||||
star:0,
|
||||
type:0
|
||||
}).then(res => {
|
||||
console.log('评价提交结果:', res)
|
||||
if(res.code == 200){
|
||||
uni.hideLoading()
|
||||
// 添加新回复
|
||||
const newReply = {
|
||||
id: Date.now(),
|
||||
userName: '我',
|
||||
avatar: '/static/icon_home_my_patient.png',
|
||||
time: new Date().toLocaleDateString(),
|
||||
content: replyText.value.trim(),
|
||||
isAuthor: false
|
||||
}
|
||||
|
||||
if (!review.replies) {
|
||||
review.replies = []
|
||||
}
|
||||
review.replies.push(newReply)
|
||||
|
||||
// 重置状态
|
||||
activeReplyId.value = null
|
||||
replyText.value = ''
|
||||
|
||||
uni.showToast({
|
||||
title: '回复成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const goToReview = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/course_review/course_review'
|
||||
url: '/pages_course/course_review/course_review?id=' + courseId.value + '&is_commented=' + courseDetail.value.is_commented
|
||||
})
|
||||
}
|
||||
|
||||
const getCourseDetail = () => {
|
||||
course_api.excellencourseDetail(courseId.value).then(res => {
|
||||
course_api.excellencourseDetail(courseId.value).then(res => {
|
||||
console.log('课程详情数据:', res)
|
||||
if(res.code == 200){
|
||||
const data = res.data
|
||||
courseDetail.value = data
|
||||
|
||||
// 调试 is_commented 字段
|
||||
console.log('is_commented 字段值:', data.is_commented)
|
||||
console.log('is_buy 字段值:', data.is_buy)
|
||||
|
||||
// 更新页面数据
|
||||
updatePageData(data)
|
||||
// 优惠价格与倒计时
|
||||
@ -694,8 +728,8 @@ const updatePageData = (data) => {
|
||||
title: lesson.title,
|
||||
duration: lesson.time || '未知时长',
|
||||
teacher: teacherName,
|
||||
status: (data.is_buy === 1 || data.type === 0) ? 'unlocked' : 'locked',
|
||||
statusText: (data.is_buy === 1 || data.type === 0) ? (data.type === 0 ? '试播' : '已解锁') : '未解锁',
|
||||
status: (data.is_buy === 1 || lesson.type === 0) ? 'unlocked' : 'locked',
|
||||
statusText: (data.is_buy === 1 || lesson.type === 0) ? (lesson.type === 0 ? '试播' : '已解锁') : '未解锁',
|
||||
vid: lesson.vid,
|
||||
type: lesson.type
|
||||
}
|
||||
@ -1570,7 +1604,7 @@ onUnmounted(() => {
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24rpx;
|
||||
padding: 0 24rpx 20rpx 24rpx;
|
||||
z-index: 999;
|
||||
|
||||
.price-section {
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="arrowleft" size="24" color="#FF4757"></uni-icons>
|
||||
<uni-icons type="left" size="24" color="#FF4757"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">肝病精品课</view>
|
||||
<view class="nav-title">{{ special_name?special_name:'肝病精品课' }}</view>
|
||||
<view class="nav-right" @click="goSearch">
|
||||
<uni-icons type="search" size="24" color="#FF4757"></uni-icons>
|
||||
</view>
|
||||
@ -27,11 +27,11 @@
|
||||
indicator-color="#d8d8d8"
|
||||
indicator-active-color="#FF4757"
|
||||
>
|
||||
<swiper-item v-for="(banner, idx) in bannerList" :key="idx">
|
||||
<view class="banner-content" :style="{ background: banner.background }">
|
||||
<view class="main-logo">{{ banner.title }}</view>
|
||||
<view class="sub-title">{{ banner.subTitle }}</view>
|
||||
<view class="description">{{ banner.description }}</view>
|
||||
<swiper-item v-for="(banner, idx) in bannerList" :key="banner.id || idx">
|
||||
<image :src="banner.sroll_img || banner.image || '/static/lunbo_bg.png'" mode="aspectFill" class="banner-image"></image>
|
||||
<view class="banner-content">
|
||||
<text class="banner-title">{{ banner.title || '' }}</text>
|
||||
<text class="banner-subtitle">{{ banner.subtitle || '' }}</text>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
@ -39,30 +39,63 @@
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<view class="filter-item" @click="showCategoryFilter">
|
||||
<text>全部课程</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#666"></uni-icons>
|
||||
<view class="filter-item">
|
||||
<uni-data-select
|
||||
v-model="selectedCategory"
|
||||
:localdata="categoryOptions"
|
||||
:clear="false"
|
||||
placeholder="全部课程"
|
||||
@change="onCategoryChange"
|
||||
>
|
||||
<template v-slot:selected="{selectedItems}">
|
||||
<view class="filter-display">
|
||||
<text>{{ selectedItems.length > 0 ? selectedItems[0].text : '全部课程' }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#666"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</uni-data-select>
|
||||
</view>
|
||||
<view class="filter-item" @click="showSortFilter">
|
||||
<text>默认排序</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#666"></uni-icons>
|
||||
<view class="filter-item">
|
||||
<uni-data-select
|
||||
v-model="selectedSort"
|
||||
:localdata="sortOptions"
|
||||
:clear="false"
|
||||
placeholder="默认排序"
|
||||
@change="onSortChange"
|
||||
>
|
||||
<template v-slot:selected="{selectedItems}">
|
||||
<view class="filter-display">
|
||||
<text>{{ selectedItems.length > 0 ? selectedItems[0].text : '默认排序' }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#666"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</uni-data-select>
|
||||
</view>
|
||||
<view class="separator"></view>
|
||||
<view class="filter-item" @click="toggleLimitedOffer">
|
||||
<text :class="{ active: filters.limitedOffer }">限时优惠</text>
|
||||
<view v-if="special_type == 0" class="separator"></view>
|
||||
<view v-if="special_type == 0" class="filter-item" :class="{ active: filters.limitedOffer }" @click="toggleLimitedOffer">
|
||||
<text>限时优惠</text>
|
||||
</view>
|
||||
<view class="separator"></view>
|
||||
<view class="filter-item" @click="toggleFreeCourses">
|
||||
<text :class="{ active: filters.freeCourses }">免费课程</text>
|
||||
<view v-if="special_type == 0" class="separator"></view>
|
||||
<view v-if="special_type == 0" class="filter-item" :class="{ active: filters.freeCourses }" @click="toggleFreeCourses">
|
||||
<text>免费课程</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课程列表 -->
|
||||
<view class="course-list">
|
||||
<!-- 空状态 -->
|
||||
<view v-if="courseList.length === 0" class="empty-state">
|
||||
<image src="/static/empty_course.png" mode="aspectFit" class="empty-image"></image>
|
||||
<text class="empty-text">暂无相关课程</text>
|
||||
<text class="empty-tip">试试调整筛选条件吧</text>
|
||||
</view>
|
||||
|
||||
<!-- 课程列表 -->
|
||||
<view
|
||||
v-else
|
||||
class="course-item"
|
||||
v-for="(course, index) in courseList"
|
||||
:key="index"
|
||||
:key="course.id"
|
||||
@click="goToCourseDetail(course)"
|
||||
>
|
||||
<view class="course-image">
|
||||
@ -70,16 +103,26 @@
|
||||
<view class="learner-count" v-if="course.learnerCount">
|
||||
{{ course.learnerCount }}人学
|
||||
</view>
|
||||
<!-- <view class="video-count" v-if="course.videoNum">
|
||||
{{ course.videoNum }}课时
|
||||
</view> -->
|
||||
</view>
|
||||
<view class="course-info">
|
||||
<view class="course-title">{{ course.title }}</view>
|
||||
<view class="course-instructor" v-if="course.instructor">
|
||||
{{ course.instructor }}
|
||||
</view>
|
||||
<view class="course-tags" v-if="course.tags && course.tags.length > 0">
|
||||
<text class="tag" v-for="tag in course.tags.slice(0, 2)" :key="tag">{{ tag }}</text>
|
||||
</view>
|
||||
<view class="course-price">
|
||||
<text class="current-price">¥{{ course.price }}</text>
|
||||
<text class="original-price" v-if="course.originalPrice">¥{{ course.originalPrice }}</text>
|
||||
</view>
|
||||
<view class="course-bonuses" v-if="course.backBonus > 0 || course.welfareBonus > 0">
|
||||
<text class="bonus back-bonus" v-if="course.backBonus > 0">返现{{ course.backBonus }}积分</text>
|
||||
<text class="bonus welfare-bonus" v-if="course.welfareBonus > 0">福利{{ course.welfareBonus }}积分</text>
|
||||
</view>
|
||||
<view class="limited-offer" v-if="course.limitedOffer">限时优惠</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -97,148 +140,52 @@
|
||||
<text>我的课程</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类筛选弹窗 -->
|
||||
<uni-popup ref="categoryPopup" type="bottom">
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<text>选择课程分类</text>
|
||||
<view class="close-btn" @click="closeCategoryPopup">
|
||||
<uni-icons type="close" size="20" color="#666"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-options">
|
||||
<view
|
||||
class="popup-option"
|
||||
:class="{ active: selectedCategory === option.value }"
|
||||
v-for="option in categoryOptions"
|
||||
:key="option.value"
|
||||
@click="selectCategory(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<!-- 排序筛选弹窗 -->
|
||||
<uni-popup ref="sortPopup" type="bottom">
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<text>选择排序方式</text>
|
||||
<view class="close-btn" @click="closeSortPopup">
|
||||
<uni-icons type="close" size="20" color="#666"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-options">
|
||||
<view
|
||||
class="popup-option"
|
||||
:class="{ active: selectedSort === option.value }"
|
||||
v-for="option in sortOptions"
|
||||
:key="option.value"
|
||||
@click="selectSort(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import course_api from '@/api/course_api'
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
const navBarHeight = ref(88)
|
||||
// 轮播横幅
|
||||
const bannerList = ref([
|
||||
{
|
||||
title: '辩以明思',
|
||||
subTitle: '慢乙肝治疗新起点',
|
||||
description: '肝胆相照·全国中青年医师 学术争鸣辩论赛',
|
||||
background: 'linear-gradient(135deg, #87CEEB 0%, #E0F6FF 100%)'
|
||||
},
|
||||
{
|
||||
title: '名医大讲堂',
|
||||
subTitle: '临床思维系统课程',
|
||||
description: '顶尖专家带你系统化掌握诊疗要点',
|
||||
background: 'linear-gradient(135deg, #FFE8E8 0%, #FFF5F5 100%)'
|
||||
},
|
||||
{
|
||||
title: '专科提升',
|
||||
subTitle: '循证与实践结合',
|
||||
description: '以真实病例训练临床路径与决策',
|
||||
background: 'linear-gradient(135deg, #E6F7FF 0%, #F2FFFC 100%)'
|
||||
const bannerList = ref([])
|
||||
|
||||
const getIndexBanners = async () => {
|
||||
try {
|
||||
const res = await course_api.index()
|
||||
if (res.code === 200 && res.data && res.data.scroll_list) {
|
||||
bannerList.value = res.data.scroll_list
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取横幅失败:', e)
|
||||
}
|
||||
])
|
||||
}
|
||||
// 筛选条件
|
||||
const filters = ref({
|
||||
limitedOffer: false,
|
||||
freeCourses: false
|
||||
})
|
||||
// 选中的分类和排序
|
||||
const selectedCategory = ref('all')
|
||||
const selectedSort = ref('default')
|
||||
const selectedCategory = ref('')
|
||||
const selectedSort = ref('0')
|
||||
// 分类选项
|
||||
const categoryOptions = ref([
|
||||
{ value: 'all', label: '全部课程' },
|
||||
{ value: 'liver', label: '肝病相关' },
|
||||
{ value: 'hepatitis', label: '肝炎治疗' },
|
||||
{ value: 'cirrhosis', label: '肝硬化' },
|
||||
{ value: 'cancer', label: '肝癌' }
|
||||
{ value: '', text: '全部课程' }
|
||||
])
|
||||
// 排序选项
|
||||
const sortOptions = ref([
|
||||
{ value: 'default', label: '默认排序' },
|
||||
{ value: 'newest', label: '最新发布' },
|
||||
{ value: 'popular', label: '最受欢迎' },
|
||||
{ value: 'price', label: '价格排序' }
|
||||
{ value: '0', text: '默认排序' },
|
||||
{ value: '0', text: '最新上架' },
|
||||
{ value: '1', text: '人气最高' },
|
||||
{ value: '2', text: '课时数' },
|
||||
{ value: '3', text: '价格升序' },
|
||||
{ value: '4', text: '价格降序' }
|
||||
])
|
||||
// 课程列表
|
||||
const courseList = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '找到已购买课程即学习找到已购买课程即可学习',
|
||||
instructor: '佑安医院 临床思维 陈煜',
|
||||
price: 39,
|
||||
image: '/static/placeholder_doctor.png',
|
||||
learnerCount: 123
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '找到已购买课程即学习找到已购买课程即可学习',
|
||||
price: 39,
|
||||
image: '/static/placeholder_doctor.png',
|
||||
learnerCount: 89
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '找到已购买课程即学习找到已购买课程即可学习',
|
||||
price: 29,
|
||||
originalPrice: 39,
|
||||
image: '/static/placeholder_doctor.png',
|
||||
limitedOffer: true,
|
||||
learnerCount: 156
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '找到已购买课程即学习找到已购买课程即可学习',
|
||||
price: 29,
|
||||
originalPrice: 39,
|
||||
image: '/static/placeholder_doctor.png',
|
||||
limitedOffer: true,
|
||||
learnerCount: 78
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '找到已购买课程即学习找到已购买课程即可学习',
|
||||
price: 39,
|
||||
image: '/static/placeholder_doctor.png',
|
||||
learnerCount: 234
|
||||
}
|
||||
])
|
||||
const courseList = ref([])
|
||||
|
||||
// 方法
|
||||
const getSystemInfo = () => {
|
||||
@ -274,44 +221,7 @@ const goToCourseDetail = (course) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 显示分类筛选
|
||||
const showCategoryFilter = () => {
|
||||
// 这里需要获取弹窗引用,暂时注释
|
||||
// this.$refs.categoryPopup.open()
|
||||
console.log('显示分类筛选')
|
||||
}
|
||||
|
||||
// 关闭分类筛选
|
||||
const closeCategoryPopup = () => {
|
||||
// this.$refs.categoryPopup.close()
|
||||
console.log('关闭分类筛选')
|
||||
}
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (value) => {
|
||||
selectedCategory.value = value
|
||||
closeCategoryPopup()
|
||||
filterCourses()
|
||||
}
|
||||
|
||||
// 显示排序筛选
|
||||
const showSortFilter = () => {
|
||||
// this.$refs.sortPopup.open()
|
||||
console.log('显示排序筛选')
|
||||
}
|
||||
|
||||
// 关闭排序筛选
|
||||
const closeSortPopup = () => {
|
||||
// this.$refs.sortPopup.close()
|
||||
console.log('关闭排序筛选')
|
||||
}
|
||||
|
||||
// 选择排序
|
||||
const selectSort = (value) => {
|
||||
selectedSort.value = value
|
||||
closeSortPopup()
|
||||
sortCourses()
|
||||
}
|
||||
// 分类选择已通过uni-data-select组件处理
|
||||
|
||||
// 切换限时优惠
|
||||
const toggleLimitedOffer = () => {
|
||||
@ -328,18 +238,119 @@ const toggleFreeCourses = () => {
|
||||
// 筛选课程
|
||||
const filterCourses = () => {
|
||||
// 这里实现课程筛选逻辑
|
||||
console.log('筛选条件:', filters.value, selectedCategory.value)
|
||||
console.log('筛选条件:', filters.value, selectedCategory.value, selectedSort.value)
|
||||
|
||||
let data = {
|
||||
first_type: type_id.value,
|
||||
second_type: selectedCategory.value,
|
||||
sort: selectedSort.value,
|
||||
page: 1
|
||||
}
|
||||
if(filters.value.limitedOffer){
|
||||
data.discount_type = 1
|
||||
}
|
||||
if(filters.value.freeCourses){
|
||||
data.free = 1
|
||||
}
|
||||
|
||||
course_api.excellencourseList(data).then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200 && res.data && res.data.list) {
|
||||
// 转换API数据为课程列表格式
|
||||
courseList.value = res.data.list.map(item => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
instructor: item.special_type_name || '专业讲师',
|
||||
price: ((item.discount_price || item.account || 0) / 100).toFixed(2), // 分转元
|
||||
originalPrice: item.discount_price && item.account && item.discount_price !== item.account ? (item.account / 100).toFixed(2) : null, // 分转元
|
||||
image: item.index_img || '/static/placeholder_doctor.png',
|
||||
learnerCount: item.study_num || 0,
|
||||
limitedOffer: item.discount_type === 1,
|
||||
videoNum: item.video_num || 0,
|
||||
backBonus: item.back_bon || 0,
|
||||
welfareBonus: item.fuli_bon || 0,
|
||||
tags: item.tags ? item.tags.split(',') : []
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 排序课程
|
||||
const sortCourses = () => {
|
||||
// 这里实现课程排序逻辑
|
||||
console.log('排序方式:', selectedSort.value)
|
||||
const special_type = ref(0)
|
||||
const special_name = ref('')
|
||||
const type_id = ref(0)
|
||||
|
||||
const getFirstType = () => {
|
||||
course_api.listExcellencourseFirstType().then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200 && res.data) {
|
||||
// 转换数据结构为uni-data-select需要的格式
|
||||
const options = res.data.map(item => ({
|
||||
value: item.id.toString(),
|
||||
text: item.name
|
||||
}))
|
||||
// 添加"全部课程"选项
|
||||
categoryOptions.value = [
|
||||
{ value: '', text: '全部课程' },
|
||||
...options
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
const getSecondType = () => {
|
||||
course_api.listExcellencourseSecondType(type_id.value).then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200 && res.data) {
|
||||
// 转换数据结构为uni-data-select需要的格式
|
||||
const options = res.data.map(item => ({
|
||||
value: item.id.toString(),
|
||||
text: item.name
|
||||
}))
|
||||
// 添加"全部课程"选项
|
||||
categoryOptions.value = [
|
||||
{ value: '', text: '全部课程' },
|
||||
...options
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onCategoryChange = (e) => {
|
||||
selectedCategory.value = e
|
||||
console.log('选择的分类:', e)
|
||||
// 自动触发课程筛选
|
||||
filterCourses()
|
||||
}
|
||||
|
||||
const onSortChange = (e) => {
|
||||
selectedSort.value = e
|
||||
console.log('选择的排序:', e)
|
||||
filterCourses()
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 获取页面参数
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const options = currentPage.options || {}
|
||||
|
||||
type_id.value = options.type
|
||||
special_type.value = options.special_type
|
||||
special_name.value = options.special_name
|
||||
console.log('特殊类型ID:', options.special_type)
|
||||
console.log('类型ID:', options.type)
|
||||
getSystemInfo()
|
||||
if(type_id.value > 0){
|
||||
getSecondType()
|
||||
}
|
||||
if(special_type.value > 0){
|
||||
getFirstType()
|
||||
}
|
||||
// 获取顶部横幅,与course.vue一致
|
||||
getIndexBanners()
|
||||
|
||||
// 初始加载课程列表
|
||||
filterCourses()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -389,42 +400,46 @@ onMounted(() => {
|
||||
|
||||
// 主横幅
|
||||
.main-banner {
|
||||
margin: 0rpx;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
|
||||
.banner-swiper {
|
||||
height: 300rpx;
|
||||
height: 380rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
padding: 48rpx 32rpx;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
.main-logo {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #FF4757;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
background-color: #FF4757;
|
||||
color: #fff;
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
display: inline-block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #0066CC;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 32rpx 24rpx;
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.6));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6rpx;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.banner-subtitle {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255,255,255,0.9);
|
||||
line-height: 1.4;
|
||||
text-shadow: 0 1rpx 2rpx rgba(0,0,0,0.4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,70 +448,133 @@ onMounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
background: #ffffff;
|
||||
border-bottom: 1rpx solid #f1f3f4;
|
||||
gap: 8rpx; // 减少间距
|
||||
flex-wrap: nowrap; // 不换行
|
||||
position: relative; // 添加相对定位
|
||||
z-index: 100; // 提高层级
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
transition: all 0.3s;
|
||||
padding: 12rpx 0rpx; // 恢复左右内边距
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 24rpx; // 稍微减小字体
|
||||
color: #495057;
|
||||
font-weight: 500;
|
||||
flex: 1; // 平均分配空间
|
||||
min-width: 0; // 允许压缩
|
||||
justify-content: center; // 居中显示
|
||||
position: relative; // 添加相对定位
|
||||
z-index: 101; // 提高层级
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
|
||||
&.active {
|
||||
color: #FF4757;
|
||||
}
|
||||
// 让选择器组件自适应宽度
|
||||
uni-data-select {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
z-index: 102; // 最高层级
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f5f5f5;
|
||||
&:hover {
|
||||
background: #e9ecef;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #FF4757;
|
||||
color: #ffffff;
|
||||
border-color: #FF4757;
|
||||
|
||||
text { color: #ffffff; }
|
||||
}
|
||||
|
||||
text {
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
white-space: nowrap; // 文字不换行
|
||||
overflow: hidden; // 超出隐藏
|
||||
text-overflow: ellipsis; // 显示省略号
|
||||
}
|
||||
|
||||
.filter-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4rpx; // 减少间距
|
||||
width: 100%;
|
||||
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 2rpx;
|
||||
height: 32rpx;
|
||||
background-color: #e0e0e0;
|
||||
margin: 0 16rpx;
|
||||
width: 1rpx;
|
||||
height: 24rpx;
|
||||
background: #dee2e6;
|
||||
margin: 0 2rpx; // 减少间距
|
||||
flex: 0 0 auto; // 分隔符不压缩
|
||||
}
|
||||
}
|
||||
|
||||
// 课程列表
|
||||
.course-list {
|
||||
padding: 24rpx;
|
||||
background: #f8f9fa;
|
||||
position: relative; // 添加相对定位
|
||||
z-index: 1; // 降低层级,确保在筛选栏下方
|
||||
|
||||
.course-item {
|
||||
display: flex;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2rpx);
|
||||
}
|
||||
|
||||
.course-image {
|
||||
position: relative;
|
||||
margin-right: 24rpx;
|
||||
margin-right: 20rpx;
|
||||
|
||||
image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.learner-count {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
left: 8rpx;
|
||||
background-color: #FF4757;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 8rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
.video-count {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
right: 8rpx;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,50 +586,123 @@ onMounted(() => {
|
||||
|
||||
.course-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
color: #212529;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.course-instructor {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
color: #6c757d;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.course-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.tag {
|
||||
font-size: 22rpx;
|
||||
color: #FF4757;
|
||||
background: #FFF5F5;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid #FF4757;
|
||||
}
|
||||
}
|
||||
|
||||
.course-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
.current-price {
|
||||
font-size: 32rpx;
|
||||
color: #FF4757;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
color: #adb5bd;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.course-bonuses {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.bonus {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid;
|
||||
}
|
||||
|
||||
.back-bonus {
|
||||
color: #FF4757;
|
||||
border-color: #FF4757;
|
||||
background: #FFF5F5;
|
||||
}
|
||||
|
||||
.welfare-bonus {
|
||||
color: #28a745;
|
||||
border-color: #28a745;
|
||||
background: #F2FFFC;
|
||||
}
|
||||
}
|
||||
|
||||
.limited-offer {
|
||||
background-color: #FF4757;
|
||||
background: #FF4757;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 4rpx;
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
|
||||
.empty-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 32rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部导航栏
|
||||
@ -560,9 +711,9 @@ onMounted(() => {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background-color: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
height: 100rpx;
|
||||
background: #ffffff;
|
||||
border-top: 1rpx solid #e9ecef;
|
||||
display: flex;
|
||||
z-index: 999;
|
||||
|
||||
@ -572,11 +723,13 @@ onMounted(() => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
gap: 4rpx;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-size: 22rpx;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@ -586,53 +739,4 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗样式
|
||||
.popup-content {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
padding: 32rpx;
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
|
||||
.popup-option {
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
border-radius: 16rpx;
|
||||
background-color: #f8f9fa;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background-color: #FF4757;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -4,7 +4,7 @@
|
||||
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">课程支付</view>
|
||||
<view class="nav-right"></view>
|
||||
@ -88,6 +88,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import course_api from "@/api/course_api.js"
|
||||
import { requestPayment } from '@/utils/payment.js'
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
@ -203,12 +204,35 @@ const confirmPayment = () => {
|
||||
|
||||
order_pay_type.value = computeOrderPayType()
|
||||
console.log('确认支付,支付方式:', { usePoints: usePoints.value, useBalance: useBalance.value, order_pay_type: order_pay_type.value })
|
||||
course_api.createExcellencourseMixedOrder(courseId.value, order_pay_type.value).then(res => {
|
||||
let openid = ""
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
openid = uni.getStorageSync('DEV_APPID');
|
||||
} else {
|
||||
openid = uni.getStorageSync('AUTH_APPID');
|
||||
}
|
||||
course_api.createExcellencourseMixedOrder(courseId.value, order_pay_type.value, openid).then(res => {
|
||||
console.log('创建订单:', res)
|
||||
if (res.code == 200) {
|
||||
uni.redirectTo({
|
||||
url: '/pages_course/course_detail/course_detail?id='+courseId.value
|
||||
})
|
||||
|
||||
const payParams = res.data.order
|
||||
|
||||
console.log(payParams)
|
||||
requestPayment(
|
||||
payParams,
|
||||
// 支付成功
|
||||
(res) => {
|
||||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||
// 可以跳转到支付成功页,或者查询订单状态
|
||||
},
|
||||
// 支付失败
|
||||
(err) => {
|
||||
console.error('支付失败', err)
|
||||
uni.showToast({ title: '支付失败或取消', icon: 'none' })
|
||||
}
|
||||
)
|
||||
// uni.redirectTo({
|
||||
// url: '/pages_course/course_detail/course_detail?id='+courseId.value
|
||||
// })
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">评价课程</view>
|
||||
<view class="nav-right"></view>
|
||||
@ -15,22 +15,22 @@
|
||||
<view class="page-content" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<!-- 星级评分区域 -->
|
||||
<view class="rating-section">
|
||||
<text class="rating-title">点击星星评分</text>
|
||||
<view class="stars-container">
|
||||
<view
|
||||
class="star-item"
|
||||
v-for="star in 5"
|
||||
:key="star"
|
||||
@click="setRating(star)"
|
||||
>
|
||||
<uni-icons
|
||||
:type="star <= currentRating ? 'star-filled' : 'star'"
|
||||
size="48"
|
||||
:color="star <= currentRating ? '#FFB800' : '#E0E0E0'"
|
||||
></uni-icons>
|
||||
</view>
|
||||
<uni-rate
|
||||
v-model="currentRating"
|
||||
:max="5"
|
||||
:value="currentRating"
|
||||
:size="48"
|
||||
:margin="16"
|
||||
:allow-half="true"
|
||||
:touchable="true"
|
||||
@change="onRatingChange"
|
||||
/>
|
||||
</view>
|
||||
<view class="rating-display">
|
||||
<text class="rating-score">{{ currentRating * 2 }}分</text>
|
||||
<text class="rating-feedback">{{ ratingFeedback }}</text>
|
||||
</view>
|
||||
<text class="rating-feedback">{{ ratingFeedback }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 评价输入区域 -->
|
||||
@ -62,6 +62,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import course_api from "@/api/course_api.js"
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
@ -82,22 +83,29 @@ const getSystemInfo = () => {
|
||||
// #endif
|
||||
}
|
||||
|
||||
const setRating = (rating) => {
|
||||
currentRating.value = rating
|
||||
const onRatingChange = (e) => {
|
||||
currentRating.value = e.value
|
||||
updateRatingFeedback()
|
||||
}
|
||||
|
||||
const updateRatingFeedback = () => {
|
||||
const feedbacks = {
|
||||
0.5: '很差,课程几乎没有帮助',
|
||||
1: '差评,课程没有任何帮助',
|
||||
1.5: '较差,课程帮助很小',
|
||||
2: '一般,课程内容一般',
|
||||
2.5: '一般偏上,课程有一定帮助',
|
||||
3: '还行,课程有一定帮助',
|
||||
3.5: '不错,课程内容较好',
|
||||
4: '不错,课程内容很好',
|
||||
4.5: '很好,课程内容很棒',
|
||||
5: '很好,课程非常棒'
|
||||
}
|
||||
ratingFeedback.value = feedbacks[currentRating.value] || ''
|
||||
}
|
||||
|
||||
const courseId = ref(0)
|
||||
const is_commented = ref(0)
|
||||
const submitReview = () => {
|
||||
if (currentRating.value === 0) {
|
||||
uni.showToast({
|
||||
@ -119,20 +127,23 @@ const submitReview = () => {
|
||||
uni.showLoading({
|
||||
title: '提交中...'
|
||||
})
|
||||
course_api.addExcellencourseComment({
|
||||
excellentcourse_id: courseId.value,
|
||||
comment: reviewContent.value,
|
||||
star: currentRating.value,
|
||||
type: is_commented.value
|
||||
}).then(res => {
|
||||
console.log('评价提交结果:', res)
|
||||
if(res.code == 200){
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '评价提交成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
uni.navigateBack()
|
||||
})
|
||||
|
||||
// 模拟提交
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '评价提交成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
@ -142,6 +153,14 @@ const goBack = () => {
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
getSystemInfo()
|
||||
// 获取页面参数
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const options = currentPage.options || {}
|
||||
is_commented.value = options.is_commented
|
||||
|
||||
courseId.value = options.id
|
||||
console.log('课程ID:', options.id)
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -203,19 +222,26 @@ onMounted(() => {
|
||||
.stars-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.star-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
margin: 24rpx 0;
|
||||
}
|
||||
|
||||
.rating-feedback {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
.rating-display {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.rating-score {
|
||||
font-size: 32rpx;
|
||||
color: #FFB800;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rating-feedback {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
712
pages_course/invoice/invoice.vue
Normal file
712
pages_course/invoice/invoice.vue
Normal file
@ -0,0 +1,712 @@
|
||||
<template>
|
||||
<view class="invoice-page">
|
||||
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">开具发票</view>
|
||||
<view class="nav-right">
|
||||
<view class="info-btn" @click="showInfo">
|
||||
<uni-icons type="info" size="20" color="#666"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<view class="tabs" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'pending' }"
|
||||
@click="switchTab('pending')"
|
||||
>
|
||||
<text>待开发票</text>
|
||||
</view>
|
||||
<view class="tab-divider"></view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'history' }"
|
||||
@click="switchTab('history')"
|
||||
>
|
||||
<text>开票历史</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 待开发票内容 -->
|
||||
<view v-if="activeTab === 'pending'" class="pending-content">
|
||||
<!-- 课程列表 -->
|
||||
<scroll-view class="course-list" scroll-y>
|
||||
<view
|
||||
class="course-item"
|
||||
v-for="(course, index) in courseList"
|
||||
:key="index"
|
||||
@click="toggleSelect(index)"
|
||||
>
|
||||
<view class="checkbox" :class="{ selected: course.selected }">
|
||||
<text v-if="course.selected" class="checkmark">✓</text>
|
||||
</view>
|
||||
<image class="course-image" :src="course.image" mode="aspectFill"></image>
|
||||
<view class="course-info">
|
||||
<text class="course-desc">{{ course.description }}</text>
|
||||
<view class="course-meta">
|
||||
<text class="course-type">{{ course.typeName }}</text>
|
||||
<text class="course-price">实际支付: ¥{{ course.price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="select-all" @click="toggleSelectAll">
|
||||
<view class="checkbox" :class="{ selected: isAllSelected }">
|
||||
<text v-if="isAllSelected" class="checkmark">✓</text>
|
||||
</view>
|
||||
<text class="select-all-text">全选</text>
|
||||
</view>
|
||||
<view class="invoice-info">
|
||||
<text class="invoice-amount">开票金额: ¥{{ totalAmount }}</text>
|
||||
<text class="selected-count">(已选{{ selectedCount }}个课程)</text>
|
||||
</view>
|
||||
<view class="invoice-btn" @click="issueInvoice">
|
||||
<text>开具发票</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开票历史内容 -->
|
||||
<view v-if="activeTab === 'history'" class="history-content">
|
||||
<scroll-view class="history-list" scroll-y>
|
||||
<view
|
||||
class="history-item"
|
||||
v-for="(item, index) in historyList"
|
||||
:key="index"
|
||||
@click="viewHistoryDetail(item)"
|
||||
>
|
||||
<view class="history-left">
|
||||
<text class="history-date">{{ item.date }}</text>
|
||||
<text class="history-count">{{ item.courseCount }}</text>
|
||||
</view>
|
||||
<view class="history-right">
|
||||
<text class="history-status" :class="item.status">{{ item.statusText }}</text>
|
||||
<view class="history-arrow">›</view>
|
||||
<text class="history-amount">{{ item.amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import course_api from "@/api/course_api.js"
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
const navBarHeight = ref(88)
|
||||
const activeTab = ref('pending')
|
||||
const courseList = ref([])
|
||||
|
||||
// 开票历史数据
|
||||
const historyList = ref([])
|
||||
|
||||
// 计算属性
|
||||
const selectedCount = computed(() => {
|
||||
return courseList.value.filter(course => course.selected).length
|
||||
})
|
||||
|
||||
const totalAmount = computed(() => {
|
||||
return courseList.value
|
||||
.filter(course => course.selected)
|
||||
.reduce((sum, course) => sum + course.price, 0)
|
||||
.toFixed(2)
|
||||
})
|
||||
|
||||
const isAllSelected = computed(() => {
|
||||
return courseList.value.length > 0 && courseList.value.every(course => course.selected)
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getSystemInfo = () => {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight
|
||||
// #ifdef MP-WEIXIN
|
||||
navBarHeight.value = systemInfo.statusBarHeight + 44
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
navBarHeight.value = systemInfo.statusBarHeight + 44
|
||||
// #endif
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const showInfo = () => {
|
||||
uni.showToast({
|
||||
title: '发票说明',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
const switchTab = (tab) => {
|
||||
activeTab.value = tab
|
||||
if (tab === 'history') {
|
||||
course_api.listExcellencourseOrderInvoiceHistory(1).then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200 && res.data && res.data.list) {
|
||||
// 将API返回的开票历史数据映射到组件使用的数据结构
|
||||
historyList.value = res.data.list.map(item => ({
|
||||
id: item.id,
|
||||
date: formatDate(item.create_date),
|
||||
courseCount: `共计${item.num}个课程`,
|
||||
status: getStatusClass(item.status),
|
||||
statusText: getStatusText(item.status),
|
||||
amount: `¥${item.amount.toFixed(2)}`
|
||||
}))
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取开票历史失败:', err)
|
||||
uni.showToast({
|
||||
title: '获取开票历史失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr) => {
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
return `${year}年${month}月${day}日`
|
||||
}
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return 'success'
|
||||
case 0:
|
||||
return 'processing'
|
||||
case 2:
|
||||
return 'failed'
|
||||
default:
|
||||
return 'failed'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '已完成'
|
||||
case 0:
|
||||
return '开票中'
|
||||
case 2:
|
||||
return '信息有误'
|
||||
default:
|
||||
return '开票失败'
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSelect = (index) => {
|
||||
courseList.value[index].selected = !courseList.value[index].selected
|
||||
}
|
||||
|
||||
const toggleSelectAll = () => {
|
||||
const newState = !isAllSelected.value
|
||||
courseList.value.forEach(course => {
|
||||
course.selected = newState
|
||||
})
|
||||
}
|
||||
|
||||
const issueInvoice = () => {
|
||||
if (selectedCount.value === 0) {
|
||||
uni.showToast({
|
||||
title: '请选择要开票的课程',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 跳转到填写发票信息页面
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/invoice_info/invoice_info'
|
||||
})
|
||||
}
|
||||
|
||||
// 查看开票历史详情
|
||||
const viewHistoryDetail = (item) => {
|
||||
console.log('查看开票历史详情:', item)
|
||||
|
||||
// 跳转到发票详情页面
|
||||
uni.navigateTo({
|
||||
url: `/pages_course/invoice_detail/invoice_detail?invoice_id=${item.id}`
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSystemInfo()
|
||||
|
||||
course_api.listExcellencourseOrderNoInvoice().then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200 && res.data) {
|
||||
// 将API返回的数据映射到组件使用的数据结构
|
||||
courseList.value = res.data.map(item => ({
|
||||
id: item.uuid,
|
||||
description: item.excellencourse_title,
|
||||
price: item.account,
|
||||
image: item.excellencourse_img,
|
||||
selected: false,
|
||||
typeName: item.type_name
|
||||
}))
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取课程列表失败:', err)
|
||||
uni.showToast({
|
||||
title: '获取课程列表失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invoice-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx 0;
|
||||
height: 88rpx;
|
||||
|
||||
.time {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.status-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.signal, .wifi, .battery {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background-color: #000000;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
|
||||
.nav-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 32rpx;
|
||||
|
||||
.nav-left, .nav-right {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info-btn {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 30rpx;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
text {
|
||||
font-size: 32rpx;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&.active {
|
||||
text {
|
||||
color: #ff6b35;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
// background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||
// border-radius: 30rpx;
|
||||
z-index: 1;
|
||||
// box-shadow: 0 4rpx 16rpx rgba(255, 107, 53, 0.3);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 80rpx;
|
||||
height: 4rpx;
|
||||
background: linear-gradient(90deg, #ff6b35 0%, #ff8c42 100%);
|
||||
border-radius: 2rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
text {
|
||||
color: #ff6b35;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-divider {
|
||||
width: 2rpx;
|
||||
height: 40rpx;
|
||||
background: linear-gradient(180deg, #dee2e6 0%, #adb5bd 100%);
|
||||
margin: 0 20rpx;
|
||||
border-radius: 1rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.course-list {
|
||||
flex: 1;
|
||||
padding: 0 30rpx;
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.course-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
.checkbox {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #ffffff;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.selected {
|
||||
background-color: #ff6b35;
|
||||
border-color: #ff6b35;
|
||||
|
||||
.checkmark {
|
||||
color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-image {
|
||||
width: 120rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
|
||||
.course-desc {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.course-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-top: 5rpx;
|
||||
|
||||
.course-type {
|
||||
font-size: 24rpx;
|
||||
color: #6c757d;
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.course-price {
|
||||
font-size: 28rpx;
|
||||
color: #ff0000;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background-color: #ffffff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 30rpx;
|
||||
gap: 30rpx;
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
|
||||
.checkbox {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #ffffff;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.selected {
|
||||
background-color: #ff6b35;
|
||||
border-color: #ff6b35;
|
||||
|
||||
.checkmark {
|
||||
color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-all-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5rpx;
|
||||
|
||||
.invoice-amount {
|
||||
font-size: 32rpx;
|
||||
color: #ff0000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.selected-count {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-btn {
|
||||
background-color: #ff0000;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
|
||||
text {
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开票历史样式
|
||||
.history-content {
|
||||
flex: 1;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
border: 1rpx solid #f0f0f0;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6rpx;
|
||||
background: linear-gradient(180deg, #ff6b35 0%, #ff8c42 100%);
|
||||
border-radius: 0 3rpx 3rpx 0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.history-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
|
||||
.history-date {
|
||||
font-size: 30rpx;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
|
||||
.history-count {
|
||||
font-size: 26rpx;
|
||||
color: #7f8c8d;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.history-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.history-status {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
min-width: 80rpx;
|
||||
text-align: center;
|
||||
|
||||
&.success {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
&.failed {
|
||||
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
&.processing {
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.history-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #bdc3c7;
|
||||
font-weight: 300;
|
||||
margin: 0 8rpx;
|
||||
}
|
||||
|
||||
.history-amount {
|
||||
font-size: 28rpx;
|
||||
color: #e74c3c;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
666
pages_course/invoice_detail/invoice_detail.vue
Normal file
666
pages_course/invoice_detail/invoice_detail.vue
Normal file
@ -0,0 +1,666 @@
|
||||
<template>
|
||||
<view class="invoice-detail-page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">开票详情</view>
|
||||
<view class="nav-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<view class="main-content" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<!-- 加载状态 -->
|
||||
<view v-if="isLoading" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<view v-else-if="hasError" class="error-container">
|
||||
<text class="error-icon">!</text>
|
||||
<text class="error-text">加载失败,请重试</text>
|
||||
<view class="retry-btn" @click="retryLoad">
|
||||
<text class="retry-text">重新加载</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 正常内容 -->
|
||||
<template v-else>
|
||||
<!-- 发票状态指示器 -->
|
||||
<view class="status-indicator" :class="getStatusClass(invoiceDetail.status)">
|
||||
<view class="status-dot" :class="getStatusClass(invoiceDetail.status)"></view>
|
||||
<text class="status-text">{{ getStatusText(invoiceDetail.status) }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 发票详情卡片 -->
|
||||
<view class="detail-card">
|
||||
<!-- 发票抬头 -->
|
||||
<view class="detail-item">
|
||||
<text class="item-label">发票抬头</text>
|
||||
<text class="item-value">{{ invoiceDetail.title }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 单位税号 -->
|
||||
<view class="detail-item">
|
||||
<text class="item-label">单位税号</text>
|
||||
<text class="item-value">{{ invoiceDetail.taxId }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 发票内容 -->
|
||||
<view class="detail-item">
|
||||
<text class="item-label">发票内容</text>
|
||||
<text class="item-value">{{ invoiceDetail.content }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 发票金额 -->
|
||||
<view class="detail-item">
|
||||
<text class="item-label">发票金额</text>
|
||||
<text class="item-value amount">{{ invoiceDetail.amount }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 电子邮箱 -->
|
||||
<view class="detail-item">
|
||||
<text class="item-label">电子邮箱</text>
|
||||
<text class="item-value">{{ invoiceDetail.email }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 创建日期 -->
|
||||
<view class="detail-item">
|
||||
<text class="item-label">创建日期</text>
|
||||
<text class="item-value">{{ invoiceDetail.createDate }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="detail-item" v-if="invoiceDetail.note">
|
||||
<text class="item-label">备注</text>
|
||||
<text class="item-value">{{ invoiceDetail.note }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 相关课程 -->
|
||||
<view class="related-courses">
|
||||
<view class="section-title">
|
||||
<view class="title-icon"></view>
|
||||
<text class="title-text">相关课程</text>
|
||||
</view>
|
||||
|
||||
<view v-if="relatedCourses.length > 0" class="course-list">
|
||||
<view
|
||||
class="course-item"
|
||||
v-for="(course, index) in relatedCourses"
|
||||
:key="index"
|
||||
>
|
||||
<view class="course-bullet"></view>
|
||||
<image class="course-image" :src="course.image" mode="aspectFill"></image>
|
||||
<view class="course-info">
|
||||
<text class="course-desc">{{ course.description }}</text>
|
||||
<text class="course-price">实际支付: {{ course.price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-courses">
|
||||
<text class="empty-text">暂无相关课程信息</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 底部下载按钮 -->
|
||||
<view class="download-section" v-if="!isLoading && !hasError">
|
||||
<view class="download-btn" @click="downloadInvoice">
|
||||
<text class="download-text">下载发票</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import course_api from "@/api/course_api.js"
|
||||
|
||||
const invoice_id = ref('')
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
const navBarHeight = ref(88)
|
||||
const invoiceDetail = ref({
|
||||
title: '',
|
||||
taxId: '',
|
||||
content: '',
|
||||
amount: '',
|
||||
email: '',
|
||||
status: 1,
|
||||
downUrl: '',
|
||||
createDate: '',
|
||||
note: ''
|
||||
})
|
||||
|
||||
const relatedCourses = ref([])
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
// 基础方法
|
||||
const getSystemInfo = () => {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight
|
||||
// #ifdef MP-WEIXIN
|
||||
navBarHeight.value = systemInfo.statusBarHeight + 44
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
navBarHeight.value = systemInfo.statusBarHeight + 44
|
||||
// #endif
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const downloadInvoice = () => {
|
||||
// 检查是否有下载链接
|
||||
if (invoiceDetail.value.downUrl) {
|
||||
uni.showToast({
|
||||
title: '正在打开下载链接...',
|
||||
icon: 'loading'
|
||||
})
|
||||
|
||||
// 使用uni.downloadFile下载文件
|
||||
uni.downloadFile({
|
||||
url: invoiceDetail.value.downUrl,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 保存文件到本地
|
||||
uni.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
success: (saveRes) => {
|
||||
console.log('文件保存成功:', saveRes.savedFilePath)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('文件保存失败:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('下载失败:', err)
|
||||
uni.showToast({
|
||||
title: '下载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '暂无下载链接',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const retryLoad = () => {
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
// 重新加载数据
|
||||
setTimeout(() => {
|
||||
loadInvoiceData()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return 'success'
|
||||
case 0:
|
||||
return 'processing'
|
||||
case 2:
|
||||
return 'failed'
|
||||
default:
|
||||
return 'success'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '发票已开具'
|
||||
case 0:
|
||||
return '开票中'
|
||||
case 2:
|
||||
return '信息有误'
|
||||
default:
|
||||
return '发票已开具'
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
} catch (error) {
|
||||
console.error('日期格式化失败:', error)
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
const loadInvoiceData = () => {
|
||||
course_api.getExcellencourseOrderInvoice(invoice_id.value).then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data
|
||||
|
||||
// 更新发票详情数据
|
||||
invoiceDetail.value = {
|
||||
title: data.head || '欣欣相照科技股份有限公司',
|
||||
taxId: data.tax_number || '22913u9u4923u293',
|
||||
content: data.type || '培训费',
|
||||
amount: `¥${data.amount?.toFixed(2) || '0.00'}`,
|
||||
email: data.email || '153746774@qq.com',
|
||||
status: data.status || 1,
|
||||
downUrl: data.down_url || '',
|
||||
createDate: formatDate(data.create_date) || '',
|
||||
note: data.note || ''
|
||||
}
|
||||
|
||||
// 更新相关课程数据
|
||||
if (data.excellencourseList && data.excellencourseList.length > 0) {
|
||||
relatedCourses.value = data.excellencourseList.map(course => ({
|
||||
image: course.excellencourse_img || 'https://via.placeholder.com/120x80/ff6b35/ffffff?text=课程',
|
||||
description: course.excellencourse_title || '找到已购买课程即可学习',
|
||||
price: `¥${course.account?.toFixed(2) || '0.00'}`
|
||||
}))
|
||||
} else {
|
||||
// 如果没有课程数据,使用默认数据
|
||||
relatedCourses.value = [
|
||||
{
|
||||
image: 'https://via.placeholder.com/120x80/ff6b35/ffffff?text=课程',
|
||||
description: '找到已购买课程即可学习',
|
||||
price: '¥39'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
} else {
|
||||
console.error('获取发票详情失败:', res.msg || '未知错误')
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取发票详情失败:', err)
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSystemInfo()
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const options = currentPage.options || {}
|
||||
|
||||
invoice_id.value = options.invoice_id
|
||||
|
||||
if (!invoice_id.value) {
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
uni.showToast({
|
||||
title: '缺少发票ID参数',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 加载发票数据
|
||||
loadInvoiceData()
|
||||
|
||||
// 设置页面标题
|
||||
uni.setNavigationBarTitle({
|
||||
title: '开票详情'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invoice-detail-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
|
||||
.nav-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 32rpx;
|
||||
|
||||
.nav-left, .nav-right {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
|
||||
.loading-spinner {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border: 6rpx solid #f3f3f3;
|
||||
border-top: 6rpx solid #ff6b35;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.error-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
|
||||
.error-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
background: #ff6b35;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
|
||||
.retry-text {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&.success {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
box-shadow: 0 4rpx 16rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
&.processing {
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
box-shadow: 0 4rpx 16rpx rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
&.failed {
|
||||
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
|
||||
box-shadow: 0 4rpx 16rpx rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 15rpx;
|
||||
|
||||
&.success {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 4rpx rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&.processing {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 4rpx rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&.failed {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 4rpx rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
max-width: 400rpx;
|
||||
text-align: right;
|
||||
|
||||
&.amount {
|
||||
color: #ff6b35;
|
||||
font-weight: 600;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.related-courses {
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.title-icon {
|
||||
width: 8rpx;
|
||||
height: 32rpx;
|
||||
background: #ff6b35;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.course-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.course-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
|
||||
.course-bullet {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background: #ff6b35;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.course-image {
|
||||
width: 120rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
|
||||
.course-desc {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.course-price {
|
||||
font-size: 24rpx;
|
||||
color: #ff6b35;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-courses {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx 0;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.download-section {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 30rpx;
|
||||
background: #ffffff;
|
||||
border-top: 1rpx solid #e9ecef;
|
||||
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background: linear-gradient(135deg, #20c997 0%, #17a2b8 100%);
|
||||
border-radius: 12rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.download-text {
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
362
pages_course/invoice_info/invoice_info.vue
Normal file
362
pages_course/invoice_info/invoice_info.vue
Normal file
@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<view class="invoice-info-page">
|
||||
<!-- 状态栏 -->
|
||||
<view class="status-bar">
|
||||
<text class="time">9:41</text>
|
||||
<view class="status-icons">
|
||||
<view class="signal"></view>
|
||||
<view class="wifi"></view>
|
||||
<view class="battery"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<view class="back-btn" @click="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<text class="title">填写发票信息</text>
|
||||
<view class="info-btn" @click="showInfo">
|
||||
<text class="info-icon">!</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<view class="main-content">
|
||||
<!-- 表单卡片 -->
|
||||
<view class="form-card">
|
||||
<!-- 发票抬头 -->
|
||||
<view class="form-field">
|
||||
<text class="field-label">发票抬头:</text>
|
||||
<input
|
||||
class="field-input"
|
||||
placeholder="请输入抬头名称(必填)"
|
||||
placeholder-class="placeholder"
|
||||
v-model="formData.title"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="field-divider"></view>
|
||||
|
||||
<!-- 单位税号 -->
|
||||
<view class="form-field">
|
||||
<text class="field-label">单位税号:</text>
|
||||
<input
|
||||
class="field-input"
|
||||
placeholder="请输入单位税号(必填)"
|
||||
placeholder-class="placeholder"
|
||||
v-model="formData.taxId"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="field-divider"></view>
|
||||
|
||||
<!-- 发票内容 -->
|
||||
<view class="form-field">
|
||||
<text class="field-label">发票内容:</text>
|
||||
<text class="field-value">{{ formData.content }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="field-divider"></view>
|
||||
|
||||
<!-- 发票金额 -->
|
||||
<view class="form-field">
|
||||
<text class="field-label">发票金额:</text>
|
||||
<text class="field-value amount">{{ formData.amount }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="field-divider"></view>
|
||||
|
||||
<!-- 电子邮箱 -->
|
||||
<view class="form-field">
|
||||
<text class="field-label">电子邮箱:</text>
|
||||
<input
|
||||
class="field-input"
|
||||
placeholder="请输入您的邮箱(必填)"
|
||||
placeholder-class="placeholder"
|
||||
v-model="formData.email"
|
||||
type="email"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<view class="submit-btn" @click="submitForm">
|
||||
<text class="submit-text">提交</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'InvoiceInfo',
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
title: '',
|
||||
taxId: '',
|
||||
content: '培训费',
|
||||
amount: '¥59.00',
|
||||
email: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
showInfo() {
|
||||
uni.showToast({
|
||||
title: '发票信息说明',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
// 验证必填字段
|
||||
if (!this.formData.title.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入发票抬头',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.formData.taxId.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入单位税号',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.formData.email.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入电子邮箱',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证邮箱格式
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(this.formData.email)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的邮箱格式',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
uni.showModal({
|
||||
title: '确认提交',
|
||||
content: '确认提交发票信息吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invoice-info-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx 0;
|
||||
height: 88rpx;
|
||||
|
||||
.time {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.status-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.signal, .wifi, .battery {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background-color: #000000;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
height: 88rpx;
|
||||
|
||||
.back-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.back-icon {
|
||||
font-size: 48rpx;
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.info-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #ff0000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.info-icon {
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 30rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
min-height: 80rpx;
|
||||
|
||||
.field-label {
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
min-width: 160rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
|
||||
&.amount {
|
||||
color: #ff0000;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #cccccc;
|
||||
}
|
||||
}
|
||||
|
||||
.field-divider {
|
||||
height: 1rpx;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
padding: 30rpx;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #20b2aa;
|
||||
border-radius: 8rpx;
|
||||
padding: 25rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.submit-text {
|
||||
color: #ffffff;
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 750rpx) {
|
||||
.form-field {
|
||||
padding: 25rpx;
|
||||
|
||||
.field-label {
|
||||
font-size: 28rpx;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.field-input, .field-value {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
padding: 20rpx;
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -4,7 +4,7 @@
|
||||
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="arrowleft" size="24" color="#FF4757"></uni-icons>
|
||||
<uni-icons type="left" size="24" color="#FF4757"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">我的课程</view>
|
||||
<view class="nav-right" @click="goSearch">
|
||||
@ -28,26 +28,38 @@
|
||||
|
||||
<!-- 课程列表 -->
|
||||
<view class="course-list">
|
||||
<view class="course-item" v-for="(course, index) in currentCourseList" :key="course.id" @click="goCourseDetail(course)">
|
||||
<view class="course-left">
|
||||
<image :src="course.image" mode="aspectFill" class="course-image"></image>
|
||||
</view>
|
||||
<view class="course-right">
|
||||
<text class="course-title">{{ course.title }}</text>
|
||||
<view class="course-meta">
|
||||
<text class="meta-text">{{ course.lessonCount }}节</text>
|
||||
<text class="meta-separator">•</text>
|
||||
<text class="meta-text">{{ course.status }}</text>
|
||||
<text class="meta-separator">•</text>
|
||||
<text class="meta-text">已学{{ course.learnedCount }}节</text>
|
||||
<!-- 有课程数据时显示课程列表 -->
|
||||
<view v-if="currentCourseList.length > 0">
|
||||
<view class="course-item" v-for="(course, index) in currentCourseList" :key="course.id" @click="goCourseDetail(course)">
|
||||
<view class="course-left">
|
||||
<image :src="course.image" mode="aspectFill" class="course-image"></image>
|
||||
</view>
|
||||
<view class="course-tags" v-if="course.tags && course.tags.length > 0">
|
||||
<view class="tag-item" v-for="tag in course.tags" :key="tag.id">
|
||||
<text class="tag-text">{{ tag.text }}</text>
|
||||
<view class="course-right">
|
||||
<text class="course-title">{{ course.title }}</text>
|
||||
<view class="course-meta">
|
||||
<text class="meta-text">{{ course.lessonCount }}节</text>
|
||||
<text class="meta-separator">•</text>
|
||||
<text class="meta-text">{{ course.status }}</text>
|
||||
<text class="meta-separator">•</text>
|
||||
<text class="meta-text">已学{{ course.learnedCount }}节</text>
|
||||
</view>
|
||||
<view class="course-tags" v-if="course.tags && course.tags.length > 0">
|
||||
<view class="tag-item" v-for="tag in course.tags" :key="tag.id">
|
||||
<text class="tag-text">{{ tag.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态显示 -->
|
||||
<view v-else class="empty-state">
|
||||
<view class="empty-icon">
|
||||
<uni-icons type="book" size="80" color="#E0E0E0"></uni-icons>
|
||||
</view>
|
||||
<text class="empty-title">{{ getEmptyTitle() }}</text>
|
||||
<text class="empty-desc">{{ getEmptyDesc() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单记录浮动按钮 -->
|
||||
@ -77,6 +89,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import course_api from "@/api/course_api.js"
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
@ -170,12 +183,14 @@ const getSystemInfo = () => {
|
||||
const switchTab = (tab) => {
|
||||
activeTab.value = tab
|
||||
console.log('切换到标签:', tab)
|
||||
// 切换标签页后重新获取对应的课程数据
|
||||
getMyCourses()
|
||||
}
|
||||
|
||||
const goCourseDetail = (course) => {
|
||||
console.log('进入课程详情:', course.title)
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/course_detail/course_detail'
|
||||
url: '/pages_course/course_detail/course_detail?id=' + course.id
|
||||
})
|
||||
}
|
||||
|
||||
@ -208,9 +223,73 @@ const goMyCourses = () => {
|
||||
console.log('当前已在我的课程页面')
|
||||
}
|
||||
|
||||
// 获取空状态标题
|
||||
const getEmptyTitle = () => {
|
||||
if (activeTab.value === 'learning') {
|
||||
return '暂无学习中的课程'
|
||||
} else if (activeTab.value === 'completed') {
|
||||
return '暂无已完成的课程'
|
||||
}
|
||||
return '暂无课程'
|
||||
}
|
||||
|
||||
// 获取空状态描述
|
||||
const getEmptyDesc = () => {
|
||||
if (activeTab.value === 'learning') {
|
||||
return '快去购买课程开始学习吧'
|
||||
} else if (activeTab.value === 'completed') {
|
||||
return '继续学习,完成更多课程'
|
||||
}
|
||||
return '快去探索更多课程吧'
|
||||
}
|
||||
|
||||
const getMyCourses = () => {
|
||||
// 根据activeTab确定state参数
|
||||
let state = 1 // 默认学习中
|
||||
if (activeTab.value === 'completed') {
|
||||
state = 2 // 已学完
|
||||
}
|
||||
|
||||
course_api.listMyExcellencourse(state, 1).then(res => {
|
||||
console.log('API返回数据:', res)
|
||||
if (res.code === 200 && res.data && res.data.list) {
|
||||
// 将API数据转换为课程列表格式
|
||||
const apiCourseList = res.data.list.map(item => ({
|
||||
id: item.excellencourse_id,
|
||||
title: item.excellencourse_title,
|
||||
lessonCount: item.excellencourse_video_num,
|
||||
status: item.excellencourse_upload_num === item.excellencourse_video_num ? '已完结' : '更新中',
|
||||
learnedCount: item.study_num,
|
||||
image: item.excellencourse_index_img || '/static/icon_home_my_patient.png',
|
||||
tags: item.special_type_name ? [{ id: Date.now(), text: item.special_type_name }] : []
|
||||
}))
|
||||
|
||||
// 根据当前标签页更新对应的课程列表
|
||||
if (activeTab.value === 'learning') {
|
||||
courseList.value.learning = apiCourseList
|
||||
} else if (activeTab.value === 'completed') {
|
||||
courseList.value.completed = apiCourseList
|
||||
}
|
||||
} else {
|
||||
console.error('获取课程列表失败:', res.msg)
|
||||
uni.showToast({
|
||||
title: res.msg || '获取课程列表失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('API调用失败:', err)
|
||||
uni.showToast({
|
||||
title: '获取课程列表失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
getSystemInfo()
|
||||
getMyCourses()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -369,6 +448,31 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态样式
|
||||
.empty-state {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #ccc;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
// 订单记录浮动按钮
|
||||
.floating-order {
|
||||
position: fixed;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">订单记录</view>
|
||||
<view class="nav-right"></view>
|
||||
@ -90,6 +90,17 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 悬浮发票按钮 -->
|
||||
<view class="floating-invoice-btn" @click="goToInvoice">
|
||||
<view class="btn-content">
|
||||
<view class="icon-wrapper">
|
||||
<uni-icons type="receipt" size="20" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="btn-text">开具发票1</text>
|
||||
</view>
|
||||
<view class="btn-shadow"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -102,65 +113,7 @@ const statusBarHeight = ref(0)
|
||||
const navBarHeight = ref(88)
|
||||
const activeTab = ref('paid') // 默认激活"已支付"标签
|
||||
// 订单数据
|
||||
const orderList = ref({
|
||||
paid: [
|
||||
{
|
||||
id: 1,
|
||||
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
|
||||
orderTime: '2020.30.21 12:23:00',
|
||||
courseAmount: '62.00',
|
||||
balancePayment: '50.00',
|
||||
actualPayment: '12.00',
|
||||
status: 'paid'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
|
||||
orderTime: '2020.30.21 12:23:00',
|
||||
courseAmount: '62.00',
|
||||
balancePayment: '50.00',
|
||||
actualPayment: '12.00',
|
||||
status: 'paid'
|
||||
}
|
||||
],
|
||||
unpaid: [
|
||||
{
|
||||
id: 3,
|
||||
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
|
||||
orderTime: '2020.30.21 12:23:00',
|
||||
courseAmount: '62.00',
|
||||
balancePayment: '50.00',
|
||||
actualPayment: '12.00',
|
||||
remainingTime: '12:23:00',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
|
||||
orderTime: '2020.30.21 12:23:00',
|
||||
courseAmount: '62.00',
|
||||
balancePayment: '50.00',
|
||||
actualPayment: '12.00',
|
||||
remainingTime: '12:23:00',
|
||||
status: 'timeout'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
|
||||
orderTime: '2020.30.21 12:23:00',
|
||||
courseAmount: '62.00',
|
||||
pointsPayment: '200',
|
||||
balancePayment: '42.00',
|
||||
actualPayment: '0.00',
|
||||
status: 'failed'
|
||||
}
|
||||
]
|
||||
})
|
||||
const orderList = ref([])
|
||||
|
||||
// 计算属性
|
||||
const currentOrderList = computed(() => {
|
||||
@ -292,6 +245,13 @@ const enterLearning = (order) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到开具发票页面
|
||||
const goToInvoice = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/invoice/invoice'
|
||||
})
|
||||
}
|
||||
|
||||
const viewOrder = (order) => {
|
||||
console.log('查看订单详情:', order.id)
|
||||
uni.showToast({
|
||||
@ -323,7 +283,7 @@ const getOrderList = () => {
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
orderNumber: item.order_id || item.uuid || '-',
|
||||
orderNumber: item.uuid || '-',
|
||||
courseName: item.excellencourse_title || '-',
|
||||
orderTime: formatTs(item.create_date),
|
||||
courseAmount: toYuan(item.account),
|
||||
@ -577,4 +537,75 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 悬浮发票按钮
|
||||
.floating-invoice-btn {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 140rpx;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
z-index: 999;
|
||||
cursor: pointer;
|
||||
|
||||
.btn-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.2);
|
||||
|
||||
&:active {
|
||||
transform: scale(0.92);
|
||||
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8rpx;
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
color: #ffffff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.1);
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-shadow {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
left: 8rpx;
|
||||
right: -8rpx;
|
||||
bottom: -8rpx;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.3), rgba(118, 75, 162, 0.3));
|
||||
border-radius: 50%;
|
||||
z-index: -1;
|
||||
filter: blur(8rpx);
|
||||
opacity: 0.8;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover .btn-shadow {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
99
utils/payment.js
Normal file
99
utils/payment.js
Normal file
@ -0,0 +1,99 @@
|
||||
// utils/payment.js
|
||||
|
||||
// 统一的支付方法
|
||||
export function requestPayment(orderInfo, successCallback, failCallback) {
|
||||
// orderInfo 一般由后端返回,包括平台所需的支付参数
|
||||
// 比如 { "timeStamp": "", "nonceStr": "", "package": "", "signType": "RSA", "paySign": "" } (微信)
|
||||
// 或者 { "orderInfo": "xxx" } (支付宝)
|
||||
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
console.log("MP-WEIXIN == MP-WEIXIN == MP-WEIXIN == MP-WEIXIN")
|
||||
// 微信小程序支付
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: orderInfo.timeStamp,
|
||||
nonceStr: orderInfo.nonceStr,
|
||||
package: 'prepay_id='+orderInfo.prepayId,
|
||||
signType: orderInfo.signType || 'RSA',
|
||||
paySign: orderInfo.paySign,
|
||||
success: (res) => {
|
||||
console.log('微信支付成功', res);
|
||||
successCallback && successCallback(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('微信支付失败', err);
|
||||
failCallback && failCallback(err);
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
console.log("MP-ALIPAY == MP-ALIPAY == MP-ALIPAY == MP-ALIPAY")
|
||||
// 支付宝小程序支付
|
||||
uni.requestPayment({
|
||||
provider: 'alipay',
|
||||
orderInfo: orderInfo.orderInfo, // 需要的是支付宝的 orderString
|
||||
success: (res) => {
|
||||
console.log('支付宝支付成功', res);
|
||||
successCallback && successCallback(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付宝支付失败', err);
|
||||
failCallback && failCallback(err);
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
console.log("APP-PLUS == APP-PLUS==APP-PLUS== APP-PLUS")
|
||||
// App 端支付(支持微信/支付宝/ApplePay等)
|
||||
// 通常也是调用 uni.requestPayment,通过 provider 区分
|
||||
const platform = uni.getSystemInfoSync().platform; // iOS or Android
|
||||
|
||||
let provider = '';
|
||||
if (platform === 'ios') {
|
||||
// 可以根据业务需求选择微信或 Apple Pay
|
||||
provider = 'wxpay'; // 或者 'wxpay',但 App 微信支付一般用 'wxpay'
|
||||
} else {
|
||||
// Android 通常用支付宝或微信
|
||||
// 你可以根据后端返回的 provider 决定,比如 orderInfo.provider
|
||||
provider = orderInfo.provider || 'wxpay'; // 或 alipay
|
||||
}
|
||||
|
||||
uni.requestPayment({
|
||||
provider: provider, // 可选值:wxpay、alipay、appleiap 等
|
||||
orderInfo: orderInfo.orderInfo, // 支付宝传 orderInfo;微信可能传其它参数
|
||||
timeStamp: orderInfo.timestamp,
|
||||
nonceStr: orderInfo.noncestr,
|
||||
package: orderInfo.package_str,
|
||||
signType: orderInfo.signType || 'RSA',
|
||||
paySign: orderInfo.sign,
|
||||
success: (res) => {
|
||||
console.log(`${provider} 支付成功`, res);
|
||||
successCallback && successCallback(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error(`${provider} 支付失败`, err);
|
||||
failCallback && failCallback(err);
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
console.log("H5 == H5==H5==H5==H5")
|
||||
// H5 支付:通常跳转到第三方支付页面(如微信扫码、支付宝跳转)
|
||||
// 由于 H5 无法直接调起微信支付(除非在微信内置浏览器),所以一般后端会返回支付链接,前端跳转
|
||||
if (orderInfo.payUrl) {
|
||||
// 比如支付宝的支付链接,或者微信的二维码链接
|
||||
window.location.href = orderInfo.payUrl;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: 'H5暂不支持直接支付,请使用其他端',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 其它平台(如 QuickApp、钉钉等)可根据需要扩展
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user