很多页面
This commit is contained in:
parent
92bfc36d51
commit
84f59b20f6
117
README_MEETING_UPDATE.md
Normal file
117
README_MEETING_UPDATE.md
Normal file
@ -0,0 +1,117 @@
|
||||
# 会议页面更新说明
|
||||
|
||||
## 更新内容
|
||||
|
||||
### 1. API集成
|
||||
- 集成了真实的会议列表API `meetingListV2U`
|
||||
- 替换了原有的模拟数据
|
||||
- 添加了完整的错误处理机制
|
||||
|
||||
### 2. 数据处理
|
||||
- 将API返回的数据转换为页面所需的格式
|
||||
- 支持分页加载(下拉刷新和上拉加载更多)
|
||||
- 根据会议状态动态生成标签颜色
|
||||
- 支持按月份动态筛选会议数据
|
||||
- 支持月份和地点组合筛选
|
||||
- 统一的搜索筛选接口
|
||||
|
||||
### 3. 会议状态显示
|
||||
- 预告 (status: 1) - 橙色标签
|
||||
- 直播中 (status: 2) - 红色标签
|
||||
- 已结束 (status: 3) - 灰色标签
|
||||
|
||||
### 4. 图片处理
|
||||
- 支持相对路径和绝对URL的图片
|
||||
- 自动添加基础URL前缀
|
||||
- 提供默认占位符图片
|
||||
|
||||
### 5. 交互功能
|
||||
- 会议已结束:点击跳转到详情页
|
||||
- 会议进行中/预告:点击跳转到直播页面
|
||||
- 支持webview页面跳转
|
||||
|
||||
## API数据结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"pageSize": 10,
|
||||
"isFirstPage": true,
|
||||
"isLastPage": true,
|
||||
"pageNum": 1,
|
||||
"pages": 1,
|
||||
"list": [
|
||||
{
|
||||
"title": "会议标题",
|
||||
"begin_date": "2023-09-26 18:30:00",
|
||||
"end_date": "2025-09-26 22:30:00",
|
||||
"liveimg": "live/img/2021/20211202152116.png",
|
||||
"liveurl": "http://zhibo.igandan.com/watch/1633941",
|
||||
"status": 3,
|
||||
"location": "线上",
|
||||
"id": 41,
|
||||
"path": "https://dev-wx.igandan.com/conference/conference_info.htm?id=41"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 主要函数
|
||||
|
||||
### `loadMeetingList()`
|
||||
- 获取会议列表数据
|
||||
- 处理分页逻辑
|
||||
- 数据格式转换
|
||||
|
||||
### `loadMeetingListByMonth(monthValue)`
|
||||
- 按月份筛选会议列表
|
||||
- 支持时间范围查询
|
||||
- 自动计算月份起止时间
|
||||
|
||||
### `meetingListBySearchU(filters)`
|
||||
- 统一的搜索和筛选接口
|
||||
- 支持月份和地点组合筛选
|
||||
- 自动构建筛选参数
|
||||
- 支持分页加载
|
||||
- API参数:
|
||||
- `month`: 月份筛选(格式:9,表示9月份;选择"所有"时传空字符串)
|
||||
- `location`: 地点筛选(省份名称,如:北京市、广东省;选择"全国"时传空字符串)
|
||||
- `page`: 页码
|
||||
|
||||
### `getTagColor(status, beginDate)`
|
||||
- 根据会议状态和日期生成标签颜色
|
||||
- 支持动态颜色生成
|
||||
|
||||
### `generateMonthList()`
|
||||
- 动态生成月份列表(当前月份往后12个月)
|
||||
- 智能标签显示(本月、下月、年份月份)
|
||||
- 支持跨年月份计算
|
||||
|
||||
### `getImageUrl(imagePath)`
|
||||
- 处理图片URL
|
||||
- 支持相对路径和绝对URL
|
||||
|
||||
### `playVideo(item)`
|
||||
- 处理会议点击事件
|
||||
- 根据状态跳转不同页面
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 需要确保 `api.meetingListV2U` 函数已正确定义
|
||||
2. 图片基础URL可能需要根据实际环境调整
|
||||
3. webview页面路径需要确保存在
|
||||
4. 建议添加默认的会议海报占位符图片
|
||||
5. 月份筛选功能使用 `month` 参数(格式:9,表示9月份)
|
||||
6. 地点筛选功能使用 `location` 参数(省份名称,如:北京市、广东省等)
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 添加会议搜索功能
|
||||
2. 支持按时间、地点筛选
|
||||
3. 添加会议收藏功能
|
||||
4. 优化图片加载性能
|
||||
5. 添加会议提醒功能
|
||||
58
api/api.js
58
api/api.js
@ -138,6 +138,14 @@ const api = {
|
||||
return request('/expertPay/getFlowerList', data, 'post', false);
|
||||
},
|
||||
|
||||
getGandanfileMyDownload(data) {
|
||||
return request('/expertAPI/gandanfile/myDownload', data, 'post', false);
|
||||
},
|
||||
|
||||
getGandanfileMyShare(data) {
|
||||
return request('/expertAPI/gandanfile/myShare', data, 'post', false);
|
||||
},
|
||||
|
||||
// 课件详情
|
||||
getGandanFileDetail(data) {
|
||||
return request('/expertAPI/gandanFileDetail', data, 'post', false);
|
||||
@ -242,7 +250,57 @@ const api = {
|
||||
guideTag(data){
|
||||
return request('/expertApp/tagList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 会议列表
|
||||
meetingListV2U(data){
|
||||
return request('/expertAPI/meetingListV2U', data, 'post', false);
|
||||
},
|
||||
|
||||
// 会议列表
|
||||
meetingListBySearchU(data){
|
||||
return request('/expertAPI/meetingListBySearchU', data, 'post', false);
|
||||
},
|
||||
|
||||
myWelfareCard(data){
|
||||
return request('/expertAPI/myWelfareCard', data, 'post', false);
|
||||
},
|
||||
|
||||
exchangeWelfareCard(data){
|
||||
return request('/expertAPI/exchangeWelfareCard', data, 'post', false);
|
||||
},
|
||||
|
||||
unReadList(data){
|
||||
return request('/expertAPI/unReadList', data, 'post', false);
|
||||
},
|
||||
|
||||
appMesageList(data){
|
||||
return request('/expertAPI/appMesageList', data, 'post', false);
|
||||
},
|
||||
|
||||
appMesageRead(data){
|
||||
return request('/expertAPI/appMesageRead', data, 'post', false);
|
||||
},
|
||||
|
||||
bankCardList(data){
|
||||
return request('/pingExpertV2/bankCardList', data, 'post', false);
|
||||
},
|
||||
|
||||
identificationBankCardNew(data){
|
||||
return request('/pingExpertV2/identificationBankCardNew', data, 'post', false);
|
||||
},
|
||||
|
||||
changePassword(data){
|
||||
return request('/expertAPI/modifyPwd', data, 'post', false);
|
||||
},
|
||||
|
||||
changeMobile(data){
|
||||
return request('/expertAPI/modifyMobile', data, 'post', false);
|
||||
},
|
||||
|
||||
smsSend(data){
|
||||
return request('/expertAPI/smsSend', data, 'post', false);
|
||||
},
|
||||
|
||||
// 肝胆新闻相关API
|
||||
// 顶部轮播
|
||||
newsRollNew(data){
|
||||
|
||||
33
api/goods_api.js
Normal file
33
api/goods_api.js
Normal file
@ -0,0 +1,33 @@
|
||||
import {request} from '@/utils/request.js'
|
||||
const goods_api = {
|
||||
goodsNewsList() {
|
||||
return request('/expertAPI/goodsNewsList', {}, 'post', true);
|
||||
},
|
||||
|
||||
goodsList(data) {
|
||||
return request('/expertAPI/goodsListV2', data, 'post', true);
|
||||
},
|
||||
|
||||
goodsTagList(data) {
|
||||
return request('/expertAPI/goodsTagList', data, 'post', true);
|
||||
},
|
||||
|
||||
getGoodsDetail(data) {
|
||||
return request('/expertAPI/getGoodsDetail', data, 'post', true);
|
||||
},
|
||||
|
||||
getTotalPoints(data) {
|
||||
return request('/expertAPI/getTotalPointsU', data, 'post', true);
|
||||
},
|
||||
|
||||
createGoodsOrder(data) {
|
||||
return request('/expertAPI/createGoodsOrder', data, 'post', true);
|
||||
},
|
||||
|
||||
getGoodsOrderList(data) {
|
||||
return request('/expertAPI/goodsOrderList', data, 'post', true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default goods_api;
|
||||
@ -1,301 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>生成占位图片</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.image-item {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.image-preview {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
canvas {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.download-btn {
|
||||
background: #007AFF;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.download-btn:hover {
|
||||
background: #0056CC;
|
||||
}
|
||||
.info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
color: #555;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>精品课页面占位图片生成器</h1>
|
||||
<p style="text-align: center; color: #666;">点击下载按钮保存图片到 /static/ 目录</p>
|
||||
|
||||
<!-- 主横幅 -->
|
||||
<div class="image-item">
|
||||
<h3>1. HIV横幅图片 (hiv_banner.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="hiv_banner" width="700" height="300"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('hiv_banner', 'hiv_banner.png')">下载</button>
|
||||
<div class="info">尺寸: 700x300px | 用途: 主横幅</div>
|
||||
</div>
|
||||
|
||||
<!-- 肝病图标 -->
|
||||
<div class="image-item">
|
||||
<h3>2. 肝病图标 (liver_icon.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="liver_icon" width="120" height="120"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('liver_icon', 'liver_icon.png')">下载</button>
|
||||
<div class="info">尺寸: 120x120px | 用途: 分类图标</div>
|
||||
</div>
|
||||
|
||||
<!-- 肿瘤图标 -->
|
||||
<div class="image-item">
|
||||
<h3>3. 肿瘤图标 (tumor_icon.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="tumor_icon" width="120" height="120"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('tumor_icon', 'tumor_icon.png')">下载</button>
|
||||
<div class="info">尺寸: 120x120px | 用途: 分类图标</div>
|
||||
</div>
|
||||
|
||||
<!-- 感染图标 -->
|
||||
<div class="image-item">
|
||||
<h3>4. 感染图标 (infection_icon.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="infection_icon" width="120" height="120"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('infection_icon', 'infection_icon.png')">下载</button>
|
||||
<div class="info">尺寸: 120x120px | 用途: 分类图标</div>
|
||||
</div>
|
||||
|
||||
<!-- 课程缩略图 -->
|
||||
<div class="image-item">
|
||||
<h3>5. 课程缩略图 (course_thumbnail.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="course_thumbnail" width="240" height="240"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('course_thumbnail', 'course_thumbnail.png')">下载</button>
|
||||
<div class="info">尺寸: 240x240px | 用途: 精品小课缩略图</div>
|
||||
</div>
|
||||
|
||||
<!-- 返现卡片1 -->
|
||||
<div class="image-item">
|
||||
<h3>6. 返现卡片1 (reward_card1.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="reward_card1" width="350" height="200"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('reward_card1', 'reward_card1.png')">下载</button>
|
||||
<div class="info">尺寸: 350x200px | 用途: 学克返现卡片</div>
|
||||
</div>
|
||||
|
||||
<!-- 返现卡片2 -->
|
||||
<div class="image-item">
|
||||
<h3>7. 返现卡片2 (reward_card2.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="reward_card2" width="350" height="200"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('reward_card2', 'reward_card2.png')">下载</button>
|
||||
<div class="info">尺寸: 350x200px | 用途: 学克返现卡片</div>
|
||||
</div>
|
||||
|
||||
<!-- 福利横幅 -->
|
||||
<div class="image-item">
|
||||
<h3>8. 福利横幅 (welfare_banner.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="welfare_banner" width="700" height="200"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('welfare_banner', 'welfare_banner.png')">下载</button>
|
||||
<div class="info">尺寸: 700x200px | 用途: 福利课堂横幅</div>
|
||||
</div>
|
||||
|
||||
<!-- HIV课程图 -->
|
||||
<div class="image-item">
|
||||
<h3>9. HIV课程图 (hiv_course.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="hiv_course" width="240" height="240"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('hiv_course', 'hiv_course.png')">下载</button>
|
||||
<div class="info">尺寸: 240x240px | 用途: 热门课程缩略图</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px; padding: 20px; background: #f8f9fa; border-radius: 8px;">
|
||||
<h3>使用说明:</h3>
|
||||
<ol>
|
||||
<li>点击每个图片下方的"下载"按钮</li>
|
||||
<li>将下载的图片保存到项目的 <code>/static/</code> 目录中</li>
|
||||
<li>确保文件名与页面代码中的引用名称一致</li>
|
||||
<li>重新运行uniapp项目即可看到占位图片效果</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 绘制图片函数
|
||||
function drawImage(canvasId, config) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 创建渐变背景
|
||||
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
|
||||
gradient.addColorStop(0, config.startColor);
|
||||
gradient.addColorStop(1, config.endColor);
|
||||
|
||||
// 填充背景
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 添加文字
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.font = `bold ${config.fontSize}px Arial, sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// 绘制主标题
|
||||
ctx.fillText(config.title, canvas.width / 2, canvas.height / 2 - 10);
|
||||
|
||||
// 绘制副标题(如果有)
|
||||
if (config.subtitle) {
|
||||
ctx.font = `${config.fontSize - 4}px Arial, sans-serif`;
|
||||
ctx.fillText(config.subtitle, canvas.width / 2, canvas.height / 2 + 20);
|
||||
}
|
||||
|
||||
// 添加图标/装饰元素
|
||||
if (config.icon) {
|
||||
ctx.font = `${config.fontSize + 10}px Arial, sans-serif`;
|
||||
ctx.fillText(config.icon, canvas.width / 2, canvas.height / 2 - 40);
|
||||
}
|
||||
}
|
||||
|
||||
// 下载图片函数
|
||||
function downloadImage(canvasId, filename) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
const link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = canvas.toDataURL();
|
||||
link.click();
|
||||
}
|
||||
|
||||
// 页面加载完成后绘制所有图片
|
||||
window.onload = function() {
|
||||
// HIV横幅
|
||||
drawImage('hiv_banner', {
|
||||
startColor: '#64B5F6',
|
||||
endColor: '#2196F3',
|
||||
title: '小懂医生讲HIV和感染',
|
||||
subtitle: '专业医学知识分享',
|
||||
fontSize: 24,
|
||||
icon: '🏥'
|
||||
});
|
||||
|
||||
// 肝病图标
|
||||
drawImage('liver_icon', {
|
||||
startColor: '#FF8A65',
|
||||
endColor: '#FF5722',
|
||||
title: '肝病',
|
||||
fontSize: 16,
|
||||
icon: '🫀'
|
||||
});
|
||||
|
||||
// 肿瘤图标
|
||||
drawImage('tumor_icon', {
|
||||
startColor: '#FFD54F',
|
||||
endColor: '#FF9800',
|
||||
title: '肿瘤',
|
||||
fontSize: 16,
|
||||
icon: '🎯'
|
||||
});
|
||||
|
||||
// 感染图标
|
||||
drawImage('infection_icon', {
|
||||
startColor: '#64B5F6',
|
||||
endColor: '#2196F3',
|
||||
title: '感染',
|
||||
fontSize: 16,
|
||||
icon: '🦠'
|
||||
});
|
||||
|
||||
// 课程缩略图
|
||||
drawImage('course_thumbnail', {
|
||||
startColor: '#E1F5FE',
|
||||
endColor: '#81D4FA',
|
||||
title: '肝脏胖瘦临床',
|
||||
subtitle: '影像学习交流',
|
||||
fontSize: 16,
|
||||
icon: '📚'
|
||||
});
|
||||
|
||||
// 返现卡片1
|
||||
drawImage('reward_card1', {
|
||||
startColor: '#E8F5E8',
|
||||
endColor: '#4CAF50',
|
||||
title: '乙肝临床治疗肝肝',
|
||||
subtitle: '肝炎及关并发作用',
|
||||
fontSize: 14
|
||||
});
|
||||
|
||||
// 返现卡片2
|
||||
drawImage('reward_card2', {
|
||||
startColor: '#FFF3E0',
|
||||
endColor: '#FF9800',
|
||||
title: '胆汁淤积性诊断与制',
|
||||
subtitle: '定及临床手段',
|
||||
fontSize: 14
|
||||
});
|
||||
|
||||
// 福利横幅
|
||||
drawImage('welfare_banner', {
|
||||
startColor: '#F3E5F5',
|
||||
endColor: '#9C27B0',
|
||||
title: '福利课堂 - 免费学习医学知识',
|
||||
fontSize: 20,
|
||||
icon: '🎁'
|
||||
});
|
||||
|
||||
// HIV课程图
|
||||
drawImage('hiv_course', {
|
||||
startColor: '#E3F2FD',
|
||||
endColor: '#1976D2',
|
||||
title: 'HIV感染',
|
||||
subtitle: '抗菌药特性',
|
||||
fontSize: 16,
|
||||
icon: '💊'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
103
pages.json
103
pages.json
@ -39,6 +39,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/meeting/meeting",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "直播",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/education/education",
|
||||
"style": {
|
||||
@ -178,6 +188,16 @@
|
||||
{
|
||||
"root": "pages_app",
|
||||
"pages": [
|
||||
{
|
||||
"path": "changePassword/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "修改登录密码",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "search/search",
|
||||
"style": {
|
||||
@ -259,6 +279,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "myWelfareCard/exchange",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "兑换福利卡",
|
||||
"app": { "bounce": "none" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "ppt/ppt",
|
||||
"style": {
|
||||
@ -521,6 +549,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "idcardAuth/bankCardList",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "常用银行卡",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "myVideo/myVideo",
|
||||
"style": {
|
||||
@ -664,6 +702,71 @@
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages_goods",
|
||||
"pages": [
|
||||
{
|
||||
"path": "pointMall/pointMall",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "积分商城",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "myRedemption/myRedemption",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "我的兑换",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "productDetail/productDetail",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "exchange/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "在线兑换",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "exchange/address_list",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "地址管理",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "exchange/address",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "收货地址",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}],
|
||||
"globalStyle": {
|
||||
|
||||
1206
pages/meeting/meeting.vue
Normal file
1206
pages/meeting/meeting.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -110,7 +110,7 @@
|
||||
<up-image :src="kjmxImg" width="48rpx" height="48rpx"></up-image>
|
||||
<text>课件明细</text>
|
||||
</view>
|
||||
<view class="account-item" @click="goToPage('courseDetail')">
|
||||
<view class="account-item" @click="goToPage('couseDetail')">
|
||||
<up-image :src="kcmxImg" width="48rpx" height="48rpx"></up-image>
|
||||
<text>课程明细</text>
|
||||
</view>
|
||||
@ -331,13 +331,13 @@
|
||||
url="/pages_app/myWelfare/myWelfare"
|
||||
break;
|
||||
case 'myFlower':
|
||||
url="/pages_app/myFlower/myFlower"
|
||||
url="/pages_app/myFlower/myFlower"
|
||||
break;
|
||||
case 'pptDetail':
|
||||
url="/pages_app/myFlower/myFlower"
|
||||
url="/pages_app/myCourseware/myCourseware"
|
||||
break;
|
||||
case 'couseDetail':
|
||||
url="/pages_app/myFlower/myFlower"
|
||||
url="/pages_course/order_record/order_record"
|
||||
break;
|
||||
case 'wechatUnbind':
|
||||
url="/pages_app/wechatContact/wechatContact"
|
||||
@ -349,7 +349,7 @@
|
||||
url="/pages_app/myWelfareCard/myWelfareCard"
|
||||
break;
|
||||
case 'bankCard':
|
||||
url="/pages_app/bindCard/bindCard"
|
||||
url="/pages_app/idcardAuth/bankCardList"
|
||||
break;
|
||||
case 'settings':
|
||||
url="/pages_app/setting/setting"
|
||||
@ -587,7 +587,7 @@
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
margin-bottom: 30rpx;
|
||||
margin-bottom: 230rpx;
|
||||
}
|
||||
|
||||
// 新增模块样式
|
||||
|
||||
@ -61,8 +61,9 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onUnmounted,computed } from 'vue';
|
||||
import phoneImg from "@/static/phone.png"
|
||||
import smsImg from "@/static/sms.png"
|
||||
import phoneImg from "@/static/phone.png"
|
||||
import smsImg from "@/static/sms.png"
|
||||
import api from "@/api/api.js"
|
||||
const mobile = ref('');
|
||||
const code = ref('');
|
||||
const sending = ref(false);
|
||||
@ -94,8 +95,17 @@ const sendCode = () => {
|
||||
return;
|
||||
}
|
||||
if (sending.value) return;
|
||||
api.smsSend({
|
||||
mobile: mobile.value,
|
||||
type: 6
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' });
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '验证码发送失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
// TODO: 调用发送验证码接口
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' });
|
||||
startTimer();
|
||||
};
|
||||
|
||||
@ -108,8 +118,18 @@ const onConfirm = () => {
|
||||
uni.showToast({ title: '请输入正确的验证码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// TODO: 提交更换手机号
|
||||
uni.showToast({ title: '已提交', icon: 'success' });
|
||||
//String oldMobile,newMobile, sms = params.get("sms");
|
||||
api.changeMobile({
|
||||
newMobile: mobile.value,
|
||||
sms: code.value,
|
||||
oldMobile: uni.getStorageSync('userInfo').mobile
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '更换手机号成功', icon: 'success' });
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '更换手机号失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
142
pages_app/changePassword/index.vue
Normal file
142
pages_app/changePassword/index.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<view class="change-pwd-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="修改登录密码"
|
||||
@clickLeft="goBack"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#eeeeee"
|
||||
></uni-nav-bar>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-wrapper">
|
||||
<view class="input-row">
|
||||
<input class="text-input" :password="true" v-model="oldPwd" placeholder="请输入原密码" placeholder-style="color:#c7c7c7" />
|
||||
</view>
|
||||
<view class="input-row">
|
||||
<input class="text-input" :password="!showNewPwd" v-model="newPwd" placeholder="新密码(6~16位数字字母组合)" placeholder-style="color:#c7c7c7" />
|
||||
</view>
|
||||
<view class="input-row">
|
||||
<input class="text-input" :password="!showConfirmPwd" v-model="confirmPwd" placeholder="确认新密码" placeholder-style="color:#c7c7c7" />
|
||||
</view>
|
||||
|
||||
<view class="forgot" @click="goForgot">忘记密码?</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部确定按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<button class="confirm-btn" @click="onConfirm">确 定</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import api from "@/api/api.js"
|
||||
|
||||
const oldPwd = ref('');
|
||||
const newPwd = ref('');
|
||||
const confirmPwd = ref('');
|
||||
const showNewPwd = ref(false);
|
||||
const showConfirmPwd = ref(false);
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const goForgot = () => {
|
||||
// 跳转到忘记密码或短信登录页(如无专页,这里跳到短信登录)
|
||||
uni.navigateTo({ url: '/pages_app/smsLogin/smsLogin' });
|
||||
};
|
||||
|
||||
const isStrong = (pwd) => {
|
||||
return /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,16}$/.test(pwd);
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
if (!oldPwd.value) {
|
||||
return uni.showToast({ title: '请输入原密码', icon: 'none' });
|
||||
}
|
||||
if (!newPwd.value) {
|
||||
return uni.showToast({ title: '请输入新密码', icon: 'none' });
|
||||
}
|
||||
if (!isStrong(newPwd.value)) {
|
||||
return uni.showToast({ title: '密码需6-16位数字字母组合', icon: 'none' });
|
||||
}
|
||||
if (confirmPwd.value !== newPwd.value) {
|
||||
return uni.showToast({ title: '两次输入的密码不一致', icon: 'none' });
|
||||
}
|
||||
|
||||
api.changePassword({
|
||||
old_password: oldPwd.value,
|
||||
password: newPwd.value
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '修改密码成功', icon: 'success' });
|
||||
// 清除登录状态
|
||||
uni.clearStorageSync();
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages_app/login/login'
|
||||
});
|
||||
}, 2000);
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '修改密码失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.change-pwd-page {
|
||||
background: #ffffff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
padding: 40rpx 30rpx 0;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
margin-bottom: 30rpx;
|
||||
background: #ffffff;
|
||||
border: 2rpx solid #eeeeee;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
height: 96rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.forgot {
|
||||
text-align: center;
|
||||
color: #8b8b8b;
|
||||
font-size: 28rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 40rpx;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
border-radius: 50rpx;
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #8B2316;
|
||||
color: #8B2316;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
160
pages_app/idcardAuth/bankCardList.vue
Normal file
160
pages_app/idcardAuth/bankCardList.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<view class="bank-card-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="常用银行卡"
|
||||
@clickLeft="goBack()"
|
||||
right-text="添加"
|
||||
@clickRight="addBankCard"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#eeeeee"
|
||||
>
|
||||
</uni-nav-bar>
|
||||
|
||||
<!-- 银行卡列表 -->
|
||||
<view class="card-list">
|
||||
<view class="card-item" v-for="(card, index) in bankCards" :key="card.uuid">
|
||||
<view class="card-logo">
|
||||
<view class="logo-bg">
|
||||
<text class="logo-text">{{ getBankLogo(card.open_bank) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-info">
|
||||
<view class="card-number">尾号{{ card.card_number }}储蓄卡</view>
|
||||
<view class="bank-name">{{ card.open_bank }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航指示器 -->
|
||||
<view class="bottom-indicator"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import navTo from '@/utils/navTo';
|
||||
import api from '@/api/api';
|
||||
const bankCards = ref([]);
|
||||
|
||||
// 银行卡数据
|
||||
const getBankCardList = () => {
|
||||
api.bankCardList({}).then(res => {
|
||||
if (res.code === 200 && res.data.bankList) {
|
||||
bankCards.value = res.data.bankList;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const addBankCard = () => {
|
||||
navTo({
|
||||
url: '/pages_app/idcardAuth/idcardAuth'
|
||||
});
|
||||
};
|
||||
|
||||
// 获取银行logo文字
|
||||
const getBankLogo = (bankName) => {
|
||||
const bankLogos = {
|
||||
'工商银行': '工',
|
||||
'中国银行': '中',
|
||||
'建设银行': '建',
|
||||
'农业银行': '农',
|
||||
'交通银行': '交',
|
||||
'招商银行': '招',
|
||||
'民生银行': '民',
|
||||
'兴业银行': '兴',
|
||||
'浦发银行': '浦',
|
||||
'光大银行': '光',
|
||||
'华夏银行': '华',
|
||||
'中信银行': '信',
|
||||
'平安银行': '平',
|
||||
'广发银行': '广',
|
||||
'邮储银行': '邮'
|
||||
};
|
||||
return bankLogos[bankName] || bankName.charAt(0);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getBankCardList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bank-card-page {
|
||||
min-height: 100vh;
|
||||
background: #F5F5F5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 银行卡列表样式 */
|
||||
.card-list {
|
||||
padding: 30rpx;
|
||||
|
||||
.card-item {
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.card-logo {
|
||||
margin-right: 30rpx;
|
||||
|
||||
.logo-bg {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: #E60012;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid #E60012;
|
||||
|
||||
.logo-text {
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-info {
|
||||
flex: 1;
|
||||
|
||||
.card-number {
|
||||
font-size: 28rpx;
|
||||
color: #000000;
|
||||
margin-bottom: 8rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bank-name {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部指示器 */
|
||||
.bottom-indicator {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 120rpx;
|
||||
height: 6rpx;
|
||||
background: #333333;
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
</style>
|
||||
@ -3,8 +3,8 @@
|
||||
<!-- 顶部导航栏 -->
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="身份验证"
|
||||
@cviewckLeft="goBack"
|
||||
:title="currentStep === 1 ? '身份验证' : '添加银行卡'"
|
||||
@clickLeft="goBack()"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
@ -18,18 +18,18 @@
|
||||
<view class="progress-bar">
|
||||
<view class="barbox">
|
||||
<view class="imgbox">
|
||||
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
|
||||
<view class="desc ">身份信息</view>
|
||||
<up-image :src="currentStep >= 1 ? stepActiveImg : stepImg" width="46rpx" height="46rpx"></up-image>
|
||||
<view class="desc" :class="{ active: currentStep >= 1 }">身份信息</view>
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
<view class="line" :class="{ active: currentStep >= 2 }"></view>
|
||||
<view class="imgbox">
|
||||
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
|
||||
<view class="desc">添加银行卡</view>
|
||||
<up-image :src="currentStep >= 2 ? stepActiveImg : stepImg" width="46rpx" height="46rpx"></up-image>
|
||||
<view class="desc" :class="{ active: currentStep >= 2 }">添加银行卡</view>
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
<view class="line" :class="{ active: currentStep >= 3 }"></view>
|
||||
<view class="imgbox">
|
||||
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
|
||||
<view class="desc">完成</view>
|
||||
<up-image :src="currentStep >= 3 ? stepActiveImg : stepImg" width="46rpx" height="46rpx"></up-image>
|
||||
<view class="desc" :class="{ active: currentStep >= 3 }">完成</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -57,23 +57,58 @@
|
||||
|
||||
<!-- 输入表单 -->
|
||||
<view class="form-section">
|
||||
<view class="form-item">
|
||||
<text class="form-label">姓名</text>
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="请输入您的姓名"
|
||||
v-model="formData.name"
|
||||
placeholder-style="color: #cccccc"
|
||||
/>
|
||||
<!-- 第一步:身份信息 -->
|
||||
<view v-if="currentStep === 1">
|
||||
<view class="form-item">
|
||||
<text class="form-label">姓名</text>
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="请输入您的姓名"
|
||||
v-model="formData.name"
|
||||
placeholder-style="color: #cccccc"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">身份证号</text>
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="请输入您的身份证号"
|
||||
v-model="formData.idNumber"
|
||||
placeholder-style="color: #cccccc"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">身份证号</text>
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="请输入您的身份证号"
|
||||
v-model="formData.idNumber"
|
||||
placeholder-style="color: #cccccc"
|
||||
/>
|
||||
|
||||
<!-- 第二步:添加银行卡 -->
|
||||
<view v-if="currentStep === 2">
|
||||
<view class="form-item">
|
||||
<text class="form-label">银行卡号</text>
|
||||
<view class="input-container">
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="仅限借记卡"
|
||||
v-model="formData.cardNumber"
|
||||
placeholder-style="color: #cccccc"
|
||||
/>
|
||||
<view class="info-icon">
|
||||
<text class="icon-text">!</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">手机号</text>
|
||||
<view class="input-container">
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="银行预留手机号"
|
||||
v-model="formData.mobile"
|
||||
placeholder-style="color: #cccccc"
|
||||
/>
|
||||
<view class="info-icon">
|
||||
<text class="icon-text">!</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -84,38 +119,215 @@
|
||||
|
||||
<!-- 下一步按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="next-btn" @click="onNextStep">下一步</button>
|
||||
<button class="next-btn" :class="{ loading: isLoading }" @click="onNextStep" :disabled="isLoading">
|
||||
<text v-if="!isLoading">下一步</text>
|
||||
<text v-else>验证中...</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 短信验证码弹框 -->
|
||||
<view v-if="showSmsDialog" class="sms-mask">
|
||||
<view class="sms-dialog">
|
||||
<view class="sms-title">短信验证码</view>
|
||||
<view class="sms-subtitle">请输入手机{{ maskedMobile }}收到的验证码</view>
|
||||
<view class="sms-input-row">
|
||||
<input class="sms-input" v-model="smsCode" placeholder="请输入验证码" placeholder-style="color: #cccccc" />
|
||||
<button class="sms-code-btn" :disabled="countdown > 0 || sendingCode" @click="onGetSmsCode">
|
||||
<text v-if="countdown === 0">获取验证码</text>
|
||||
<text v-else>{{ countdown }}s</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="sms-actions">
|
||||
<button class="sms-cancel" @click="onCancelSms">取消</button>
|
||||
<button class="sms-confirm" @click="onConfirmSms">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import stepImg from "@/static/add_card_no.png"
|
||||
import stepActiveImg from "@/static/add_card_yes.png"
|
||||
import navTo from '@/utils/navTo';
|
||||
import api from '@/api/api';
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
idNumber: ''
|
||||
idNumber: '',
|
||||
cardNumber: '',
|
||||
mobile: ''
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
const currentStep = ref(1); // 当前步骤,1表示身份信息,2表示添加银行卡
|
||||
|
||||
// 短信弹框相关
|
||||
const showSmsDialog = ref(false);
|
||||
const smsCode = ref('');
|
||||
const sendingCode = ref(false);
|
||||
const countdown = ref(0);
|
||||
let countdownTimer = null;
|
||||
|
||||
const maskedMobile = computed(() => {
|
||||
const m = formData.value.mobile || '';
|
||||
if (m && m.length >= 7) {
|
||||
return `${m.slice(0,3)}****${m.slice(-4)}`;
|
||||
}
|
||||
return m || '***********';
|
||||
});
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const onNextStep = () => {
|
||||
if (!formData.value.name.trim()) {
|
||||
uni.showToast({ title: '请输入姓名', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!formData.value.idNumber.trim()) {
|
||||
uni.showToast({ title: '请输入身份证号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showToast({ title: '验证通过,跳转下一步', icon: 'success' });
|
||||
// 这里可以跳转到下一步页面
|
||||
// 身份证号格式验证
|
||||
const validateIdNumber = (idNumber) => {
|
||||
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
|
||||
return reg.test(idNumber);
|
||||
};
|
||||
|
||||
// 姓名格式验证
|
||||
const validateName = (name) => {
|
||||
const reg = /^[\u4e00-\u9fa5]{2,10}$/;
|
||||
return reg.test(name);
|
||||
};
|
||||
|
||||
// 银行卡号验证
|
||||
const validateCardNumber = (cardNumber) => {
|
||||
const reg = /^\d{16,19}$/;
|
||||
return reg.test(cardNumber);
|
||||
};
|
||||
|
||||
// 手机号验证
|
||||
const validateMobile = (mobile) => {
|
||||
const reg = /^1[3-9]\d{9}$/;
|
||||
return reg.test(mobile);
|
||||
};
|
||||
|
||||
const onNextStep = async () => {
|
||||
if (currentStep.value === 1) {
|
||||
// 第一步:身份信息验证
|
||||
if (!formData.value.name.trim()) {
|
||||
uni.showToast({ title: '请输入姓名', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateName(formData.value.name)) {
|
||||
uni.showToast({ title: '请输入正确的姓名(2-10个汉字)', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.value.idNumber.trim()) {
|
||||
uni.showToast({ title: '请输入身份证号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateIdNumber(formData.value.idNumber)) {
|
||||
uni.showToast({ title: '请输入正确的身份证号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
// 调用身份验证API
|
||||
const res = {code:200}
|
||||
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '身份验证成功', icon: 'success' });
|
||||
currentStep.value = 2;
|
||||
isLoading.value = false;
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '身份验证失败', icon: 'none' });
|
||||
isLoading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('身份验证失败:', error);
|
||||
uni.showToast({ title: '网络错误,请重试', icon: 'none' });
|
||||
isLoading.value = false;
|
||||
}
|
||||
} else if (currentStep.value === 2) {
|
||||
// 第二步:银行卡信息验证
|
||||
if (!formData.value.cardNumber.trim()) {
|
||||
uni.showToast({ title: '请输入银行卡号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateCardNumber(formData.value.cardNumber)) {
|
||||
uni.showToast({ title: '请输入正确的银行卡号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.value.mobile.trim()) {
|
||||
uni.showToast({ title: '请输入手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateMobile(formData.value.mobile)) {
|
||||
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 弹出短信验证码弹框
|
||||
showSmsDialog.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onGetSmsCode = async () => {
|
||||
if (countdown.value > 0) return;
|
||||
const res = await api.smsSend({
|
||||
mobile: formData.value.mobile,
|
||||
type: 3
|
||||
});
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '短信验证码发送成功', icon: 'success' });
|
||||
// 开始60秒倒计时
|
||||
countdown.value = 60;
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
countdownTimer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value -= 1;
|
||||
} else {
|
||||
clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '短信验证码发送失败', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const onConfirmSms = async () => {
|
||||
if (!smsCode.value) {
|
||||
uni.showToast({ title: '请输入短信验证码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const res = await api.identificationBankCardNew({
|
||||
phone_number: formData.value.mobile,
|
||||
sms: smsCode.value,
|
||||
id_number: formData.value.idNumber,
|
||||
card_number: formData.value.cardNumber,
|
||||
id_name: formData.value.name
|
||||
});
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '银行卡添加成功', icon: 'success' });
|
||||
navTo({
|
||||
url: '/pages_app/idcardAuth/bankCardList'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '银行卡添加失败', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
const onCancelSms = () => {
|
||||
showSmsDialog.value = false;
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -235,10 +447,11 @@ const onNextStep = () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.form-section {
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
padding: 0;
|
||||
|
||||
|
||||
.form-item {
|
||||
@ -246,58 +459,207 @@ const onNextStep = () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 2rpx solid #eee;
|
||||
padding: 0 30rpx 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #000000;
|
||||
width:120rpx;
|
||||
width: 120rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex:1;
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
border-color: #ff0000;
|
||||
border-color: #8B2316;
|
||||
}
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 80%;
|
||||
|
||||
.form-input {
|
||||
flex: 9;
|
||||
height: 80rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
/* 去除边框后无需变更边框颜色 */
|
||||
}
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
flex:1;
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon-text {
|
||||
font-size: 20rpx;
|
||||
color: #cccccc;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
background: #ffffff;
|
||||
background: #fff3cd;
|
||||
border: 2rpx solid #ffeaa7;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 60rpx;
|
||||
font-size: 26rpx;
|
||||
color: #000000;
|
||||
color: #856404;
|
||||
line-height: 1.6;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "⚠️";
|
||||
position: absolute;
|
||||
left: 30rpx;
|
||||
top: 30rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
padding-left: 80rpx;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
padding: 0 30rox 40rpx;
|
||||
padding: 0 30rpx 40rpx;
|
||||
|
||||
.next-btn {
|
||||
margin:0 30rpx;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #cccccc;
|
||||
background: #8B2316;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
font-size: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
background: #8B2316;
|
||||
background: #6B1A0F;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
background: #cccccc;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #cccccc;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 短信弹框样式 */
|
||||
.sms-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.sms-dialog {
|
||||
width: 680rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.sms-title {
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.sms-subtitle {
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.sms-input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin: 10rpx 0 30rpx;
|
||||
}
|
||||
|
||||
.sms-input {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border: 2rpx solid #eeeeee;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.sms-code-btn {
|
||||
width: 200rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #eeeeee;
|
||||
color: #8B2316;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.sms-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.sms-cancel,
|
||||
.sms-confirm {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.sms-cancel { background: #f5f5f5; color: #000000; margin-right: 20rpx; }
|
||||
.sms-confirm { background: #8B2316; color: #ffffff; margin-left: 20rpx; }
|
||||
</style>
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="消息"
|
||||
right-text="清除"
|
||||
@clickRight="clearMsg"
|
||||
@clickLeft="goBack"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
@ -19,25 +21,28 @@
|
||||
<view class="grid-item" @click="goBenefit">
|
||||
<view class="icon-wrap gift">
|
||||
<uni-icons type="gift" size="34" color="#fff"></uni-icons>
|
||||
<view class="badge" v-if="badgeData.Module_Welfare > 0">{{ badgeData.Module_Welfare }}</view>
|
||||
</view>
|
||||
<text class="label">福利</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="goOrder">
|
||||
<view class="icon-wrap order">
|
||||
<uni-icons type="list" size="34" color="#fff"></uni-icons>
|
||||
<view class="badge" v-if="badgeData.Module_Order > 0">{{ badgeData.Module_Order }}</view>
|
||||
</view>
|
||||
<text class="label">订单</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="goFollow">
|
||||
<view class="icon-wrap visit">
|
||||
<uni-icons type="heart" size="34" color="#fff"></uni-icons>
|
||||
<view class="badge" v-if="followBadge > 0">{{ followBadge }}</view>
|
||||
<view class="badge" v-if="badgeData.Module_Relation > 0">{{ badgeData.Module_Relation }}</view>
|
||||
</view>
|
||||
<text class="label">随访</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="goReply">
|
||||
<view class="icon-wrap reply">
|
||||
<uni-icons type="chatbubble" size="34" color="#fff"></uni-icons>
|
||||
<view class="badge" v-if="badgeData.Module_Comment > 0">{{ badgeData.Module_Comment }}</view>
|
||||
</view>
|
||||
<text class="label">回复我的</text>
|
||||
</view>
|
||||
@ -54,13 +59,35 @@
|
||||
@scrolltolower="onLoadMore"
|
||||
lower-threshold="80"
|
||||
>
|
||||
<view class="group" v-for="(group, gIdx) in msgGroups" :key="gIdx">
|
||||
<view class="group-time">{{ group.time }}</view>
|
||||
<view class="card" v-for="(msg, idx) in group.items" :key="idx">
|
||||
<view class="card-title">系统消息</view>
|
||||
<view class="card-content">{{ msg }}</view>
|
||||
<!-- 回复我的模块 - 聊天列表样式 -->
|
||||
<view v-if="currentModule === 4" class="chat-list">
|
||||
<view class="chat-item" v-for="(msg, idx) in msgList" :key="msg.id">
|
||||
<view class="avatar">
|
||||
<image :src="msg.avatar || '/static/default-avatar.png'" mode="aspectFill" @error="handleImageError"></image>
|
||||
</view>
|
||||
<view class="chat-content">
|
||||
<view class="chat-header">
|
||||
<text class="sender-name">{{ msg.sender_name || msg.title }}</text>
|
||||
<text class="chat-time">{{ msg.create_date }}</text>
|
||||
</view>
|
||||
<view class="message-preview">{{ msg.content }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他模块 - 卡片样式 -->
|
||||
<view v-else>
|
||||
<view class="card" v-for="(msg, idx) in msgList" :key="msg.id">
|
||||
<view class="card-title">{{ msg.title }}</view>
|
||||
<view class="card-content">{{ msg.content }}</view>
|
||||
<view class="card-time">{{ msg.create_date }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="msgList.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无消息</text>
|
||||
</view>
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="loading" class="loading-more">
|
||||
<text class="loading-text">加载中...</text>
|
||||
@ -74,24 +101,21 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import api from '@/api/api.js';
|
||||
import docUrl from '@/utils/docUrl';
|
||||
|
||||
const followBadge = ref(5);
|
||||
// 角标数据
|
||||
const badgeData = ref({
|
||||
Module_Order: 0, // 订单
|
||||
Module_Comment: 0, // 回复我的
|
||||
Module_Welfare: 0, // 福利
|
||||
Module_Relation: 0 // 随访
|
||||
});
|
||||
|
||||
const msgGroups = ref([
|
||||
{
|
||||
time: '2022-04-22 16:32:54',
|
||||
items: ['恭喜您,您的肝胆积分又增加了,快去查看吧']
|
||||
},
|
||||
{
|
||||
time: '2022-04-20 13:33:23',
|
||||
items: ['恭喜您,您的肝胆积分又增加了,快去查看吧']
|
||||
},
|
||||
{
|
||||
time: '2022-04-15 13:36:23',
|
||||
items: ['恭喜您,您的肝胆积分又增加了,快去查看吧']
|
||||
}
|
||||
]);
|
||||
// 消息列表数据
|
||||
const msgList = ref([]);
|
||||
const currentModule = ref(1); // 当前选中的模块,默认显示模块1的消息
|
||||
|
||||
// 刷新/加载状态
|
||||
const refreshing = ref(false);
|
||||
@ -100,13 +124,7 @@
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
|
||||
// 模拟更多数据(请在接入接口后替换)
|
||||
const mockMore = [
|
||||
{ time: '2022-04-10 09:20:11', items: ['系统保养完成,服务更稳定~'] },
|
||||
{ time: '2022-04-05 18:06:42', items: ['积分到账提醒,请注意查收。'] },
|
||||
{ time: '2022-03-30 08:15:00', items: ['本周学术会议日程已发布。'] },
|
||||
{ time: '2022-03-25 10:05:16', items: ['您有新的随访任务待处理。'] }
|
||||
];
|
||||
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack({
|
||||
@ -118,23 +136,46 @@
|
||||
});
|
||||
};
|
||||
|
||||
const goBenefit = () => uni.showToast({ title: '福利', icon: 'none' });
|
||||
const goOrder = () => uni.showToast({ title: '订单', icon: 'none' });
|
||||
const goFollow = () => uni.showToast({ title: '随访', icon: 'none' });
|
||||
const goReply = () => uni.showToast({ title: '回复我的', icon: 'none' });
|
||||
const goBenefit = () => {
|
||||
currentModule.value = 1;
|
||||
getAppMesageList(1);
|
||||
};
|
||||
const goOrder = () => {
|
||||
currentModule.value = 2;
|
||||
getAppMesageList(2);
|
||||
};
|
||||
const goFollow = () => {
|
||||
currentModule.value = 3;
|
||||
getAppMesageList(3);
|
||||
};
|
||||
const goReply = () => {
|
||||
currentModule.value = 4;
|
||||
getAppMesageList(4);
|
||||
};
|
||||
|
||||
const clearMsg = () => {
|
||||
uni.showModal({
|
||||
title: '提醒',
|
||||
content: '是否要清除所有未读消息?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
api.appMesageRead({}).then(res => {
|
||||
console.log(res);
|
||||
});
|
||||
getUnReadList();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 下拉刷新
|
||||
const onRefresh = async () => {
|
||||
if (refreshing.value) return;
|
||||
refreshing.value = true;
|
||||
try {
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
msgGroups.value.unshift({
|
||||
time: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||||
items: ['您有一条新的系统通知,欢迎查看。']
|
||||
});
|
||||
// 重新获取当前模块的消息列表
|
||||
getAppMesageList(currentModule.value);
|
||||
// 重新获取未读消息数量
|
||||
getUnReadList();
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
}
|
||||
@ -145,19 +186,73 @@
|
||||
if (loading.value || noMore.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
const start = (page.value - 1) * 2;
|
||||
const chunk = mockMore.slice(start, start + 2);
|
||||
if (chunk.length === 0) {
|
||||
noMore.value = true;
|
||||
} else {
|
||||
msgGroups.value.push(...chunk);
|
||||
page.value += 1;
|
||||
}
|
||||
// 这里可以根据需要实现分页加载
|
||||
// 目前接口返回的数据已经包含了分页信息
|
||||
// 如果需要加载更多,可以调用 getAppMesageList 并传入页码参数
|
||||
noMore.value = true; // 暂时设置为没有更多数据
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getUnReadList = () => {
|
||||
api.unReadList({}).then(res => {
|
||||
console.log(res);
|
||||
if (res.code === 200 && res.data) {
|
||||
// 更新角标数据
|
||||
badgeData.value = {
|
||||
Module_Order: parseInt(res.data.Module_Order) || 0,
|
||||
Module_Comment: parseInt(res.data.Module_Comment) || 0,
|
||||
Module_Welfare: parseInt(res.data.Module_Welfare) || 0,
|
||||
Module_Relation: parseInt(res.data.Module_Relation) || 0
|
||||
};
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取未读消息列表失败:', err);
|
||||
});
|
||||
};
|
||||
|
||||
const getAppMesageList = (module) => {
|
||||
api.appMesageList({module: module}).then(res => {
|
||||
console.log(res);
|
||||
if (res.code === 200 && res.data && res.data.list) {
|
||||
// 更新消息列表数据
|
||||
msgList.value = res.data.list.map(item => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
content: item.content,
|
||||
create_date: item.create_date,
|
||||
is_read: item.is_read,
|
||||
extra: item.extra,
|
||||
// 从extra字段提取用户信息
|
||||
sender_name: item.extra?.user_name || item.title,
|
||||
avatar: item.extra?.user_photo ?
|
||||
(item.extra.user_photo.startsWith('http') ?
|
||||
item.extra.user_photo :
|
||||
`${getBaseUrl()}${item.extra.user_photo}`) :
|
||||
'/static/default-avatar.png'
|
||||
}));
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取消息列表失败:', err);
|
||||
});
|
||||
};
|
||||
|
||||
// 获取基础URL
|
||||
const getBaseUrl = () => {
|
||||
return docUrl;
|
||||
};
|
||||
|
||||
// 图片加载错误处理
|
||||
const handleImageError = (e) => {
|
||||
// 设置默认头像
|
||||
e.target.src = '/static/default-avatar.png';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getUnReadList();
|
||||
getAppMesageList(1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -243,41 +338,107 @@
|
||||
box-sizing: border-box;
|
||||
bottom: 0rpx;
|
||||
// 加载状态样式
|
||||
.loading-more, .no-more {
|
||||
.loading-more, .no-more, .empty-state {
|
||||
@include flex-center;
|
||||
padding: 28rpx 0;
|
||||
.loading-text, .no-more-text {
|
||||
.loading-text, .no-more-text, .empty-text {
|
||||
font-size: 26rpx;
|
||||
color: $muted;
|
||||
}
|
||||
}
|
||||
|
||||
.group {
|
||||
.group-time {
|
||||
text-align: center;
|
||||
color: $muted;
|
||||
font-size: 24rpx;
|
||||
margin: 26rpx 0 16rpx;
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 26rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
margin-top: 26rpx;
|
||||
|
||||
.card-title {
|
||||
font-size: 30rpx;
|
||||
color: $text-primary;
|
||||
font-weight: 600;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 26rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 26rpx;
|
||||
.card-content {
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 30rpx;
|
||||
color: $text-primary;
|
||||
font-weight: 600;
|
||||
margin-bottom: 14rpx;
|
||||
.card-time {
|
||||
font-size: 24rpx;
|
||||
color: $muted;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
// 聊天列表样式
|
||||
.chat-list {
|
||||
.chat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
background: #fff;
|
||||
margin-top: 24rpx;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1.6;
|
||||
.avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
.sender-name {
|
||||
font-size: 30rpx;
|
||||
color: $text-primary;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-time {
|
||||
font-size: 24rpx;
|
||||
color: $muted;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.message-preview {
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,11 +37,11 @@
|
||||
<view class="summary-bar">
|
||||
<view class="summary-item">
|
||||
<up-image :src="downLoadImg" width="36rpx" height="36rpx" ></up-image>
|
||||
<text class="summary-text">: {{ downloadCount }}</text>
|
||||
<text class="summary-text">{{ activeTab === 'download' ? '下载账户' : '分享账户' }}: {{ downloadCount }}</text>
|
||||
</view>
|
||||
<view class="summary-item">
|
||||
<up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image>
|
||||
<text class="summary-text">: {{ totalAmount }}</text>
|
||||
<text class="summary-text">{{ activeTab === 'download' ? '文件数量' : '分享文件' }}: {{ totalAmount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -55,12 +55,21 @@
|
||||
@scrolltolower="onLoadMore"
|
||||
:lower-threshold="100"
|
||||
>
|
||||
<!-- 空状态 -->
|
||||
<view v-if="coursewareList.length === 0 && !loading" class="empty-state">
|
||||
<text>{{ activeTab === 'download' ? '暂无下载数据' : '暂无分享数据' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="courseware-item" v-for="(item, index) in coursewareList" :key="index" @click="onItemClick(item)">
|
||||
<view class="item-content">
|
||||
<view class="courseware-name">
|
||||
<text class="label">课件名称:</text>
|
||||
<text class="value">{{ item.name }}</text>
|
||||
</view>
|
||||
<view class="courseware-provider" v-if="item.providername">
|
||||
<text class="label">{{ activeTab === 'download' ? '提供者' : '下载者' }}:</text>
|
||||
<text class="value">{{ item.providername }}</text>
|
||||
</view>
|
||||
<view class="courseware-time">
|
||||
<text class="label">时间:</text>
|
||||
<text class="value">{{ item.time }}</text>
|
||||
@ -78,7 +87,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据提示 -->
|
||||
<view v-if="noMore" class="no-more">
|
||||
<view v-if="noMore && coursewareList.length > 0" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@ -86,7 +95,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import api from '@/api/api';
|
||||
import downLoadImg from "@/static/course_download.png"
|
||||
import moneyImg from "@/static/course_yuan.png"
|
||||
const activeTab = ref('download');
|
||||
@ -98,28 +108,11 @@ const pageSize = ref(10);
|
||||
const downloadCount = ref(4);
|
||||
const totalAmount = ref('20.00');
|
||||
|
||||
const coursewareList = ref([
|
||||
{
|
||||
name: '慢性病毒性肝炎患者干扰素治疗不良反应临床处理专家共识',
|
||||
time: '2025-02-21',
|
||||
status: '已支付'
|
||||
},
|
||||
{
|
||||
name: '慢乙肝抗病毒治疗-把握时机正确选择',
|
||||
time: '2024-11-27',
|
||||
status: '已支付'
|
||||
},
|
||||
{
|
||||
name: '肝病相关血小板减少症临床管理中国专家共识(2023)解读',
|
||||
time: '2024-10-06',
|
||||
status: '已支付'
|
||||
},
|
||||
{
|
||||
name: '俞云松:耐药阳性菌感染诊疗思路(CHINET数据云)',
|
||||
time: '2022-10-24',
|
||||
status: '已支付'
|
||||
}
|
||||
]);
|
||||
const coursewareList = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
onRefresh();
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
@ -131,37 +124,173 @@ const switchTab = (tab) => {
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
// 这里可以根据标签加载不同数据
|
||||
onRefresh();
|
||||
};
|
||||
|
||||
const onItemClick = (item) => {
|
||||
uni.showToast({ title: `点击了: ${item.name}`, icon: 'none' });
|
||||
// 这里可以根据需要跳转到详情页或执行下载操作
|
||||
console.log('点击课件:', item);
|
||||
|
||||
if (activeTab.value === 'download') {
|
||||
// 下载标签页的处理逻辑
|
||||
if (item.download_path) {
|
||||
uni.downloadFile({
|
||||
url: item.download_path,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
console.log('打开文档成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('打开文档失败:', err);
|
||||
uni.showToast({ title: '无法打开此文件', icon: 'none' });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('下载失败:', err);
|
||||
uni.showToast({ title: '下载失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.showToast({ title: `点击了下载课件: ${item.name}`, icon: 'none' });
|
||||
}
|
||||
} else {
|
||||
// 分享标签页的处理逻辑
|
||||
uni.showToast({ title: `点击了分享课件: ${item.name}`, icon: 'none' });
|
||||
// 这里可以添加分享相关的操作,比如查看分享详情、重新分享等
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
refreshing.value = true;
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
|
||||
setTimeout(() => {
|
||||
refreshing.value = false;
|
||||
uni.showToast({ title: '刷新完成', icon: 'success' });
|
||||
}, 1000);
|
||||
if (activeTab.value === 'download') {
|
||||
api.getGandanfileMyDownload({ page: page.value }).then(res => {
|
||||
console.log(res);
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data.list;
|
||||
// 更新摘要信息
|
||||
downloadCount.value = res.data.downloadTotalAccount || 0;
|
||||
totalAmount.value = res.data.downloadFileCount || 0;
|
||||
|
||||
// 处理课件列表数据
|
||||
coursewareList.value = data.list.map(item => ({
|
||||
name: item.title || '未知课件',
|
||||
time: item.create_date ? item.create_date.split(' ')[0] : '',
|
||||
status: item.order_status === 'paid' ? '已支付' : '未支付',
|
||||
uuid: item.uuid,
|
||||
order_id: item.order_id,
|
||||
type: item.type,
|
||||
providername: item.providername
|
||||
}));
|
||||
|
||||
// 判断是否还有更多数据
|
||||
noMore.value = data.pageNumber >= data.totalPage;
|
||||
}
|
||||
refreshing.value = false;
|
||||
}).catch(err => {
|
||||
console.error('获取下载列表失败:', err);
|
||||
refreshing.value = false;
|
||||
});
|
||||
} else {
|
||||
api.getGandanfileMyShare({ page: page.value }).then(res => {
|
||||
console.log(res);
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data.list;
|
||||
// 更新摘要信息 - 分享数据使用不同的字段
|
||||
downloadCount.value = res.data.shareTotalAccount || 0;
|
||||
totalAmount.value = res.data.shareloadFileCount || 0;
|
||||
|
||||
// 处理分享列表数据
|
||||
coursewareList.value = data.list.map(item => ({
|
||||
name: item.title || '未知课件',
|
||||
time: item.create_date ? item.create_date.split(' ')[0] : '',
|
||||
status: '已分享', // 分享数据没有支付状态,统一显示为已分享
|
||||
uuid: item.uuid,
|
||||
order_id: item.order_id,
|
||||
type: item.type,
|
||||
providername: item.downloadername, // 分享数据中下载者名称对应提供者
|
||||
downloadername: item.downloadername
|
||||
}));
|
||||
|
||||
// 判断是否还有更多数据
|
||||
noMore.value = data.pageNumber >= data.totalPage;
|
||||
}
|
||||
refreshing.value = false;
|
||||
}).catch(err => {
|
||||
console.error('获取分享列表失败:', err);
|
||||
refreshing.value = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadMore = () => {
|
||||
if (loading.value || noMore.value) return;
|
||||
|
||||
loading.value = true;
|
||||
page.value++;
|
||||
|
||||
setTimeout(() => {
|
||||
if (page.value < 3) {
|
||||
page.value++;
|
||||
uni.showToast({ title: '加载完成', icon: 'success' });
|
||||
} else {
|
||||
noMore.value = true;
|
||||
}
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
if (activeTab.value === 'download') {
|
||||
api.getGandanfileMyDownload({ page: page.value }).then(res => {
|
||||
console.log(res);
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data.list;
|
||||
// 追加新数据到列表
|
||||
const newList = data.list.map(item => ({
|
||||
name: item.title || '未知课件',
|
||||
time: item.create_date ? item.create_date.split(' ')[0] : '',
|
||||
status: item.order_status === 'paid' ? '已支付' : '未支付',
|
||||
uuid: item.uuid,
|
||||
order_id: item.order_id,
|
||||
type: item.type,
|
||||
providername: item.providername
|
||||
}));
|
||||
|
||||
coursewareList.value = [...coursewareList.value, ...newList];
|
||||
|
||||
// 判断是否还有更多数据
|
||||
noMore.value = data.pageNumber >= data.totalPage;
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(err => {
|
||||
console.error('加载更多下载列表失败:', err);
|
||||
page.value--; // 恢复页码
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
api.getGandanfileMyShare({ page: page.value }).then(res => {
|
||||
console.log(res);
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data.list;
|
||||
// 追加新数据到列表
|
||||
const newList = data.list.map(item => ({
|
||||
name: item.title || '未知课件',
|
||||
time: item.create_date ? item.create_date.split(' ')[0] : '',
|
||||
status: '已分享', // 分享数据没有支付状态,统一显示为已分享
|
||||
uuid: item.uuid,
|
||||
order_id: item.order_id,
|
||||
type: item.type,
|
||||
providername: item.downloadername, // 分享数据中下载者名称对应提供者
|
||||
downloadername: item.downloadername
|
||||
}));
|
||||
|
||||
coursewareList.value = [...coursewareList.value, ...newList];
|
||||
|
||||
// 判断是否还有更多数据
|
||||
noMore.value = data.pageNumber >= data.totalPage;
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(err => {
|
||||
console.error('加载更多分享列表失败:', err);
|
||||
page.value--; // 恢复页码
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -240,6 +369,7 @@ const onLoadMore = () => {
|
||||
border-bottom:2rpx solid #eee;
|
||||
.item-content {
|
||||
.courseware-name,
|
||||
.courseware-provider,
|
||||
.courseware-time,
|
||||
.courseware-status {
|
||||
display: flex;
|
||||
@ -269,10 +399,15 @@ const onLoadMore = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more, .no-more {
|
||||
.loading-more, .no-more, .empty-state {
|
||||
text-align: center;
|
||||
padding: 30rpx;
|
||||
color: #999999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 100rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -60,11 +60,9 @@
|
||||
<view v-if="noMore && records.length > 0" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
|
||||
<!-- 调试按钮 -->
|
||||
<view v-if="records.length > 0" class="debug-actions">
|
||||
<button @click="testLoadMore" size="mini" type="primary">测试加载更多</button>
|
||||
<text class="debug-info">当前页: {{ page }}, 加载中: {{ loading }}, 无更多: {{ noMore }}</text>
|
||||
<view class="debug-actions">
|
||||
<!-- <button @click="testLoadMore" size="mini" type="primary">测试加载更多</button>
|
||||
<text class="debug-info">当前页: {{ page }}, 加载中: {{ loading }}, 无更多: {{ noMore }}</text> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
@ -221,7 +219,9 @@ $text: #333;
|
||||
$muted: #999;
|
||||
$card: #ffffff;
|
||||
|
||||
.flower-page { height: calc(100vh - 140rpx); background: $bg; }
|
||||
.flower-page {
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
.stats-bar {
|
||||
display: flex;
|
||||
@ -297,6 +297,7 @@ $card: #ffffff;
|
||||
}
|
||||
|
||||
.debug-actions {
|
||||
height: 50rpx;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
@ -472,7 +472,7 @@
|
||||
const formattedList = listData.map(item => ({
|
||||
type: item.score_type_name,
|
||||
time: item.create_date,
|
||||
amount: -item.score // 支出为负数
|
||||
amount: item.score // 支出为负数
|
||||
}));
|
||||
|
||||
// 更新支出记录列表
|
||||
|
||||
238
pages_app/myWelfareCard/exchange.vue
Normal file
238
pages_app/myWelfareCard/exchange.vue
Normal file
@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="兑换福利卡"
|
||||
@clickLeft="goBack"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#eeeeee"
|
||||
></uni-nav-bar>
|
||||
|
||||
<view class="exchange-page">
|
||||
<!-- 顶部红色横幅 -->
|
||||
<view class="top-banner">
|
||||
<view class="banner-text">
|
||||
<view class="line1">已兑换{{ exchangedCount }}张</view>
|
||||
<view class="line2" @click="goMyWelfare">查看现有权益</view>
|
||||
</view>
|
||||
<view class="help-btn" @click="showHelp">帮助说明</view>
|
||||
</view>
|
||||
|
||||
<!-- 使用统一的自定义居中模态框 -->
|
||||
<view v-if="centerVisible" class="center-modal" @click.self="closeCenter">
|
||||
<view class="center-modal-content">
|
||||
<view class="center-title">{{ centerHelp ? '帮助说明' : '提示' }}</view>
|
||||
<view v-if="centerHelp" class="help-content center-help">
|
||||
<text>1、点击“兑换福利卡”,输入密码即可兑换相应权益</text>
|
||||
<text>2、每张福利卡仅限兑换一次,兑换后权益可在“我的福利-使用福利”中查看</text>
|
||||
<text>3、福利卡长期有效,福利卡不能退换或者折现</text>
|
||||
<text>4、查找文献权益不限文献类型,如指南共识、论文、电子书、课件或者视频</text>
|
||||
</view>
|
||||
<view v-else class="center-body">{{ centerText }}</view>
|
||||
<view class="center-actions">
|
||||
<button class="center-btn" @click="closeCenter">知道了</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 输入提示 -->
|
||||
<view class="tips">请输入16位福利卡密码(不区分大小写)<text class="paste-action" @click="pasteFromClipboard">粘贴</text></view>
|
||||
|
||||
<!-- 四段输入框 -->
|
||||
<view class="code-inputs">
|
||||
<input class="code-box" type="text" v-model="code1" maxlength="4" placeholder="" :focus="f1" @input="handleInput(1, $event)" @paste="handlePaste"/>
|
||||
<input class="code-box" type="text" v-model="code2" maxlength="4" placeholder="" :focus="f2" @input="handleInput(2, $event)"/>
|
||||
<input class="code-box" type="text" v-model="code3" maxlength="4" placeholder="" :focus="f3" @input="handleInput(3, $event)"/>
|
||||
<input class="code-box" type="text" v-model="code4" maxlength="4" placeholder="" :focus="f4" @input="handleInput(4, $event)"/>
|
||||
</view>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<view class="btn-wrapper">
|
||||
<button class="submit-btn" :disabled="!isFull" @click="submit">立即兑换</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import api from '@/api/api';
|
||||
const exchangedCount = ref(5)
|
||||
const code1 = ref('')
|
||||
const code2 = ref('')
|
||||
const code3 = ref('')
|
||||
const code4 = ref('')
|
||||
const f1 = ref(true)
|
||||
const f2 = ref(false)
|
||||
const f3 = ref(false)
|
||||
const f4 = ref(false)
|
||||
const helpVisible = ref(false)
|
||||
const centerVisible = ref(false)
|
||||
const centerText = ref('')
|
||||
const centerHelp = ref(false)
|
||||
const isFull = computed(() => (code1.value+code2.value+code3.value+code4.value).length === 16)
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack({
|
||||
fail() {
|
||||
uni.redirectTo({ url: '/pages/index/index' })
|
||||
}
|
||||
})
|
||||
}
|
||||
const showHelp = () => {
|
||||
centerHelp.value = true
|
||||
centerVisible.value = true
|
||||
}
|
||||
|
||||
// 通用模态框
|
||||
const openCenter = (text) => {
|
||||
centerText.value = text
|
||||
centerHelp.value = false
|
||||
centerVisible.value = true
|
||||
}
|
||||
const closeCenter = () => {
|
||||
centerVisible.value = false
|
||||
centerHelp.value = false
|
||||
}
|
||||
const setFocus = (idx) => {
|
||||
f1.value = idx === 1
|
||||
f2.value = idx === 2
|
||||
f3.value = idx === 3
|
||||
f4.value = idx === 4
|
||||
}
|
||||
const handleInput = (idx, e) => {
|
||||
const raw = (e.detail && e.detail.value) || ''
|
||||
const sanitized = raw.replace(/[^a-zA-Z0-9]/g, '')
|
||||
if (idx === 1) code1.value = sanitized
|
||||
if (idx === 2) code2.value = sanitized
|
||||
if (idx === 3) code3.value = sanitized
|
||||
if (idx === 4) code4.value = sanitized
|
||||
if (sanitized.length === 4 && idx < 4) {
|
||||
nextTick(() => setFocus(idx + 1))
|
||||
}
|
||||
}
|
||||
const submit = () => {
|
||||
if (!isFull.value) return
|
||||
const code = (code1.value+code2.value+code3.value+code4.value).toUpperCase()
|
||||
uni.showToast({ title: '兑换中: '+ code, icon: 'none' })
|
||||
api.exchangeWelfareCard({password: code}).then(res => {
|
||||
console.log(res)
|
||||
if (res.code == 200) {
|
||||
uni.showToast({ title: '兑换成功', icon: 'success' })
|
||||
uni.navigateBack()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goMyWelfare = () => {
|
||||
uni.navigateTo({ url: '/pages_app/myWelfare/myWelfare' })
|
||||
}
|
||||
|
||||
// 处理粘贴(H5等支持paste事件的平台)
|
||||
const handlePaste = (e) => {
|
||||
const text = (e.clipboardData && e.clipboardData.getData('text')) || ''
|
||||
fillByText(text)
|
||||
// 阻止默认粘贴到单个输入框
|
||||
e && e.preventDefault && e.preventDefault()
|
||||
}
|
||||
|
||||
// 从剪贴板读取(App、小程序)
|
||||
const pasteFromClipboard = () => {
|
||||
uni.getClipboardData({
|
||||
success: (res) => {
|
||||
fillByText(res.data || '')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const fillByText = (raw) => {
|
||||
const v = String(raw || '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 16)
|
||||
code1.value = v.slice(0, 4)
|
||||
code2.value = v.slice(4, 8)
|
||||
code3.value = v.slice(8, 12)
|
||||
code4.value = v.slice(12, 16)
|
||||
nextTick(() => setFocus(v.length >= 16 ? 4 : Math.floor((v.length)/4) + 1))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$nav-height: 140rpx;
|
||||
.exchange-page{
|
||||
min-height: 100vh;
|
||||
background: #f5f6f7;
|
||||
}
|
||||
.top-banner{
|
||||
position: relative;
|
||||
height: 280rpx;
|
||||
background: linear-gradient(180deg,#ff6a4a 0%, #e93b2d 100%);
|
||||
border-bottom-left-radius: 40rpx;
|
||||
border-bottom-right-radius: 40rpx;
|
||||
.banner-text{
|
||||
position: absolute;
|
||||
left: 48rpx;
|
||||
top: 120rpx;
|
||||
color: #fff;
|
||||
.line1{font-size: 44rpx;font-weight: 600;}
|
||||
.line2{font-size: 36rpx;margin-top: 12rpx;}
|
||||
}
|
||||
.help-btn{
|
||||
position: absolute;
|
||||
right: 40rpx;
|
||||
top: 90rpx;
|
||||
background: rgba(255,255,255,.95);
|
||||
color: #e04835;
|
||||
border-radius: 999rpx;
|
||||
padding: 10rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
.tips{
|
||||
margin: 48rpx;
|
||||
color: #333;
|
||||
font-size: 32rpx;
|
||||
.paste-action{
|
||||
color: #007aff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.code-inputs{
|
||||
display: flex;
|
||||
gap: 30rpx;
|
||||
padding: 0 48rpx;
|
||||
.code-box{
|
||||
flex: 1;
|
||||
height: 96rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
}
|
||||
.btn-wrapper{
|
||||
padding: 80rpx 48rpx 0;
|
||||
.submit-btn{
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
background: linear-gradient(90deg,#ff4d2e,#e93b2d);
|
||||
border-radius: 60rpx;
|
||||
color: #fff;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
.submit-btn[disabled]{
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 帮助弹层样式 */
|
||||
.center-help{padding: 0 32rpx;display:flex;flex-direction:column;align-items:center;gap: 16rpx;color:#333;font-size:28rpx;line-height:1.6;}
|
||||
|
||||
/* 自定义通用模态框 */
|
||||
.center-modal{position: fixed;left:0;right:0;top:0;bottom:0;background: rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index: 9999;}
|
||||
.center-modal-content{width: 640rpx;background:#fff;border-radius:24rpx;overflow:hidden;}
|
||||
.center-title{font-size: 32rpx;color:#333;text-align:center;padding:28rpx 24rpx 12rpx;font-weight:600;}
|
||||
.center-body{padding:0 32rpx 12rpx;color:#333;font-size:28rpx;line-height:1.7;text-align:center;}
|
||||
.center-actions{padding: 24rpx 24rpx 28rpx;}
|
||||
.center-btn{width:100%;height:88rpx;border-radius:999rpx;background:#e93b2d;color:#fff;font-size:30rpx;}
|
||||
|
||||
</style>
|
||||
@ -11,100 +11,121 @@
|
||||
></uni-nav-bar>
|
||||
|
||||
<view class="benefits-page">
|
||||
|
||||
<!-- 顶部红色横幅 -->
|
||||
<view class="top-banner">
|
||||
<view class="banner-text">
|
||||
<view class="line1">已兑换{{ cardInfo.length }}张</view>
|
||||
<view class="line2" @click="goMyWelfare">查看现有权益</view>
|
||||
</view>
|
||||
<view class="help-btn" @click="showRules">帮助说明</view>
|
||||
</view>
|
||||
|
||||
<!-- 头部导航栏 -->
|
||||
|
||||
|
||||
<!-- 帮助说明模态框(与兑换页一致的居中弹层) -->
|
||||
<view v-if="centerVisible" class="center-modal" @click.self="closeCenter">
|
||||
<view class="center-modal-content">
|
||||
<view class="center-title">帮助说明</view>
|
||||
<view class="center-help">
|
||||
<text>1、点击“兑换福利卡”,输入密码即可兑换相应权益</text>
|
||||
<text>2、每张福利卡仅限兑换一次,兑换后权益可在“我的福利-使用福利”中查看</text>
|
||||
<text>3、福利卡长期有效,福利卡不能退换或者折现</text>
|
||||
<text>4、查找文献权益不限文献类型,如指南共识、论文、电子书、课件或者视频</text>
|
||||
</view>
|
||||
<view class="center-actions">
|
||||
<button class="center-btn" @click="closeCenter">知道了</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 福利卡片列表 -->
|
||||
<view class="scrollbox">
|
||||
<scroll-view class="benefits-list" scroll-y="true" :show-scrollbar="false">
|
||||
<view
|
||||
class="benefit-card"
|
||||
v-for="(benefit, index) in benefitsList"
|
||||
:key="index"
|
||||
:class="benefit.type"
|
||||
@click="claimBenefit(benefit)"
|
||||
>
|
||||
<view class="card-title">{{ benefit.title }}</view>
|
||||
<!-- <view class="card-bg">
|
||||
|
||||
</view> -->
|
||||
<view class="card-content">
|
||||
|
||||
<view class="card-details">
|
||||
<view class="left-section">
|
||||
<text class="condition">{{ benefit.condition }}</text>
|
||||
<text class="requirement">{{ benefit.requirement }}</text>
|
||||
</view>
|
||||
<view class="right-section">
|
||||
<text class="reward-type">{{ benefit.rewardType }}</text>
|
||||
<text class="reward-value">{{ benefit.rewardValue }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 有卡展示(多张) -->
|
||||
<view v-if="hasCard" >
|
||||
<view class="card-wrapper" v-for="(card, cIdx) in cardInfo" :key="card.id">
|
||||
<view class="card-header">
|
||||
<text>卡号:{{ card.idcard }}</text>
|
||||
<text class="time">兑换时间:{{ card.exchange_date }}</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="benefit-line" v-for="(w, idx) in card.welfare_list" :key="idx">
|
||||
<text class="index">{{ idx + 1 }}、</text>
|
||||
<text class="text">{{ w.type_name }}{{ w.num }}{{ w.type_unit }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!-- 加载提示 -->
|
||||
<view class="loadmore-tip" v-if="isLoading || isLastPage">
|
||||
<text>{{ isLoading ? '加载中...' : (isLastPage ? '没有更多了' : '') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 兑换福利卡 -->
|
||||
<view class="emptybox">
|
||||
<up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image>
|
||||
|
||||
<!-- 无卡空状态 -->
|
||||
<view v-else class="emptybox">
|
||||
<up-image :src="emptyImg" width="176rpx" height="204rpx"></up-image>
|
||||
<view class="empty_desc">暂无福利卡</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<view class="bottom-nav">
|
||||
<view class="nav-item" @click="goPointsDetail">
|
||||
<up-image :src="jifenImg" width="34rpx" height="34rpx" ></up-image>
|
||||
<up-image :src="jifenImg" width="34rpx" height="34rpx"></up-image>
|
||||
<text class="nav-text">兑换福利卡</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import jifenImg from "@/static/duihuan.png"
|
||||
import emptyImg from "@/static/icon_empty.png"
|
||||
// 当前选中的标签页
|
||||
const activeTab = ref(0);
|
||||
import api from '@/api/api';
|
||||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
// 是否有福利卡以及示例卡信息
|
||||
const hasCard = ref(true)
|
||||
const cardInfo = ref([]);
|
||||
// 分页状态
|
||||
const pageNum = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const isLastPage = ref(false)
|
||||
const isLoading = ref(false)
|
||||
|
||||
const getMyWelfareCard = (opts = { isRefresh: false }) => {
|
||||
if (isLoading.value) return
|
||||
isLoading.value = true
|
||||
api.myWelfareCard({page: pageNum.value}).then(res => {
|
||||
console.log(res)
|
||||
if (res.code == 200) {
|
||||
const list = Array.isArray(res.data.list) ? res.data.list : []
|
||||
if (opts.isRefresh) {
|
||||
cardInfo.value = list
|
||||
} else {
|
||||
cardInfo.value = pageNum.value === 1 ? list : cardInfo.value.concat(list)
|
||||
}
|
||||
hasCard.value = list.length > 0 || cardInfo.value.length > 0
|
||||
isLastPage.value = !!res.data.isLastPage
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getMyWelfareCard()
|
||||
})
|
||||
|
||||
// 福利列表数据
|
||||
const benefitsList = ref([
|
||||
{
|
||||
type: 'points',
|
||||
title: '肝胆积分 (5个新随访)',
|
||||
condition: '赠送积分',
|
||||
requirement: '200积分',
|
||||
rewardType: '立即领取',
|
||||
rewardValue: ''
|
||||
},
|
||||
{
|
||||
type: 'video',
|
||||
title: '肝胆视频',
|
||||
condition: '再新增随访',
|
||||
requirement: '1个',
|
||||
rewardType: '赠送下载',
|
||||
rewardValue: '2集'
|
||||
},
|
||||
{
|
||||
type: 'courseware',
|
||||
title: '肝胆课件',
|
||||
condition: '再新增随访',
|
||||
requirement: '6个',
|
||||
rewardType: '赠送下载',
|
||||
rewardValue: '1篇'
|
||||
},
|
||||
{
|
||||
type: 'usb',
|
||||
title: '知识U盘',
|
||||
condition: '再新增随访 (年度计算)',
|
||||
requirement: '96个',
|
||||
rewardType: '赠送U盘',
|
||||
rewardValue: '1个'
|
||||
}
|
||||
]);
|
||||
// 下拉刷新
|
||||
onPullDownRefresh(() => {
|
||||
pageNum.value = 1
|
||||
isLastPage.value = false
|
||||
getMyWelfareCard({ isRefresh: true })
|
||||
})
|
||||
|
||||
// 上拉加载更多
|
||||
onReachBottom(() => {
|
||||
if (isLastPage.value || isLoading.value) return
|
||||
pageNum.value += 1
|
||||
getMyWelfareCard()
|
||||
})
|
||||
|
||||
// 方法
|
||||
const goBack = () => {
|
||||
@ -117,49 +138,21 @@
|
||||
});
|
||||
};
|
||||
|
||||
const centerVisible = ref(false)
|
||||
const showRules = () => {
|
||||
uni.showToast({
|
||||
title: '福利规则',
|
||||
icon: 'none'
|
||||
});
|
||||
centerVisible.value = true
|
||||
};
|
||||
|
||||
const switchTab = (index) => {
|
||||
activeTab.value = index;
|
||||
// 这里可以根据标签页切换加载不同的数据
|
||||
uni.showToast({
|
||||
title: `切换到${['领取福利', '使用福利', '兑福利卡'][index]}`,
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const claimBenefit = (benefit) => {
|
||||
uni.showToast({
|
||||
title: `领取${benefit.title}`,
|
||||
icon: 'none'
|
||||
});
|
||||
const closeCenter = () => {
|
||||
centerVisible.value = false
|
||||
};
|
||||
|
||||
const goPointsDetail = () => {
|
||||
uni.showToast({
|
||||
title: '积分详情',
|
||||
icon: 'none'
|
||||
});
|
||||
uni.navigateTo({ url: '/pages_app/myWelfareCard/exchange' })
|
||||
};
|
||||
|
||||
const goBenefitDetail = () => {
|
||||
uni.showToast({
|
||||
title: '福利详情',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const addPatient = () => {
|
||||
uni.showToast({
|
||||
title: '添加患者',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
const goMyWelfare = () => {
|
||||
uni.navigateTo({ url: '/pages_app/myWelfare/myWelfare' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -196,7 +189,6 @@
|
||||
.benefits-page {
|
||||
min-height: 100vh;
|
||||
background-color: $bg-color;
|
||||
padding-top: $nav-height; // 为固定导航栏预留空间
|
||||
.emptybox{
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
@ -330,7 +322,7 @@
|
||||
}
|
||||
.benefits-list {
|
||||
|
||||
|
||||
|
||||
.benefit-card {
|
||||
background: $white;
|
||||
border-radius: 20rpx;
|
||||
@ -340,22 +332,6 @@
|
||||
overflow: hidden;
|
||||
border:2rpx solid #fff;
|
||||
|
||||
&.points {
|
||||
|
||||
}
|
||||
|
||||
&.video {
|
||||
|
||||
}
|
||||
|
||||
&.courseware {
|
||||
|
||||
}
|
||||
|
||||
&.usb {
|
||||
|
||||
}
|
||||
|
||||
.card-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -364,7 +340,6 @@
|
||||
z-index:0;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.card-content {
|
||||
@ -388,7 +363,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding:0 40rpx;
|
||||
height: 220rpx;
|
||||
height: 220rpx;
|
||||
.left-section,
|
||||
.right-section {
|
||||
display: flex;
|
||||
@ -420,6 +395,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部横幅与卡片样式 */
|
||||
.top-banner{
|
||||
position: relative;
|
||||
height: 280rpx;
|
||||
background: linear-gradient(180deg,#ff6a4a 0%, #e93b2d 100%);
|
||||
border-bottom-left-radius: 40rpx;
|
||||
border-bottom-right-radius: 40rpx;
|
||||
@include shadow;
|
||||
.banner-text{
|
||||
position: absolute;
|
||||
left: 48rpx;
|
||||
top: 120rpx;
|
||||
color: #fff;
|
||||
.line1{font-size: 44rpx;font-weight: 600;}
|
||||
.line2{font-size: 36rpx;margin-top: 12rpx;}
|
||||
}
|
||||
.help-btn{
|
||||
position: absolute;
|
||||
right: 40rpx;
|
||||
top: 90rpx;
|
||||
background: rgba(255,255,255,.95);
|
||||
color: #e04835;
|
||||
border-radius: 999rpx;
|
||||
padding: 10rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.card-wrapper{
|
||||
margin: 24rpx 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
@include shadow;
|
||||
overflow: hidden;
|
||||
.card-header{
|
||||
background: linear-gradient(90deg,#ff7e4a,#ff4d2e);
|
||||
color: #fff;
|
||||
padding: 24rpx 28rpx;
|
||||
font-size: 26rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.time{opacity:.95}
|
||||
}
|
||||
.card-body{
|
||||
padding: 34rpx 28rpx 40rpx;
|
||||
.benefit-line{
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
line-height: 56rpx;
|
||||
.index{color:#333}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
@ -449,4 +478,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部加载提示 */
|
||||
.loadmore-tip{
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
padding: 20rpx 0 120rpx; // 给底部按钮留出空间
|
||||
}
|
||||
|
||||
/* 居中模态框样式(复用兑换页风格) */
|
||||
.center-modal{position: fixed;left:0;right:0;top:0;bottom:0;background: rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index: 9999;}
|
||||
.center-modal-content{width: 640rpx;background:#fff;border-radius:24rpx;overflow:hidden;}
|
||||
.center-title{font-size: 32rpx;color:#333;text-align:center;padding:28rpx 24rpx 12rpx;font-weight:600;}
|
||||
.center-help{padding: 0 32rpx;display:flex;flex-direction:column;align-items:center;gap: 16rpx;color:#333;font-size:28rpx;line-height:1.6;}
|
||||
.center-actions{padding: 24rpx 24rpx 28rpx;}
|
||||
.center-btn{width:100%;height:88rpx;border-radius:999rpx;background:#e93b2d;color:#fff;font-size:30rpx;}
|
||||
</style>
|
||||
@ -130,7 +130,7 @@
|
||||
// 跳转到修改密码页面
|
||||
const goToChangePassword = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_app/pwdLogin/pwdLogin'
|
||||
url: '/pages_app/changePassword/index'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
99
pages_goods/README.md
Normal file
99
pages_goods/README.md
Normal file
@ -0,0 +1,99 @@
|
||||
# 积分商城页面
|
||||
|
||||
## 页面结构
|
||||
|
||||
### 1. 积分商城首页 (`pointMall/pointMall.vue`)
|
||||
- **状态栏**: 显示时间、网络状态、电池等信息
|
||||
- **顶部导航栏**: 包含返回按钮、标题"积分商城"、搜索按钮
|
||||
- **轮播图横幅**: 显示活动信息,包含渐变背景和文字覆盖
|
||||
- **筛选排序栏**: 提供筛选和排序功能
|
||||
- **商品网格**: 两列布局显示商品列表
|
||||
- **底部导航栏**: 包含"我的兑换"和"购买积分"两个选项
|
||||
|
||||
### 2. 商品详情页 (`productDetail/productDetail.vue`)
|
||||
- **状态栏**: 与首页相同的状态栏
|
||||
- **顶部导航栏**: 包含返回按钮、标题"商品详情"、分享按钮
|
||||
- **商品信息**: 显示商品图片、标题、价格和描述
|
||||
- **兑换按钮**: 提供商品兑换功能
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 积分商城首页
|
||||
- ✅ 100%还原设计稿的UI布局
|
||||
- ✅ 响应式设计,适配不同屏幕尺寸
|
||||
- ✅ 轮播图自动播放
|
||||
- ✅ 商品网格布局
|
||||
- ✅ 点击商品跳转到详情页
|
||||
- ✅ 筛选和排序功能(待实现具体逻辑)
|
||||
- ✅ 底部导航栏交互
|
||||
|
||||
### 商品详情页
|
||||
- ✅ 商品信息展示
|
||||
- ✅ 兑换确认功能
|
||||
- ✅ 返回导航
|
||||
|
||||
## 路由配置
|
||||
|
||||
页面已添加到 `pages.json` 中:
|
||||
|
||||
```json
|
||||
{
|
||||
"root": "pages_goods",
|
||||
"pages": [
|
||||
{
|
||||
"path": "pointMall/pointMall",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "积分商城"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "productDetail/productDetail",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "商品详情"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 访问积分商城首页
|
||||
```javascript
|
||||
uni.navigateTo({
|
||||
url: '/pages_goods/pointMall/pointMall'
|
||||
})
|
||||
```
|
||||
|
||||
### 访问商品详情页
|
||||
```javascript
|
||||
uni.navigateTo({
|
||||
url: '/pages_goods/productDetail/productDetail?id=1'
|
||||
})
|
||||
```
|
||||
|
||||
## 样式特点
|
||||
|
||||
- 使用自定义状态栏和导航栏
|
||||
- 渐变色背景的轮播图
|
||||
- 卡片式商品布局
|
||||
- 固定底部导航栏
|
||||
- 响应式网格布局
|
||||
- 统一的颜色主题(红色 #e74c3c,青色 #20b2aa)
|
||||
|
||||
## 待完善功能
|
||||
|
||||
1. 筛选和排序的具体实现
|
||||
2. 商品数据的动态加载
|
||||
3. 搜索功能的具体实现
|
||||
4. 我的兑换页面
|
||||
5. 购买积分页面
|
||||
6. 商品图片的实际加载
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 页面使用了自定义导航栏,确保在 `pages.json` 中设置了 `"navigationStyle": "custom"`
|
||||
- 商品图片路径需要根据实际情况调整
|
||||
- 可以根据需要添加更多的商品数据和功能
|
||||
230
pages_goods/exchange/address.vue
Normal file
230
pages_goods/exchange/address.vue
Normal file
@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<view class="address-page">
|
||||
<uni-nav-bar left-icon="left" title="收货地址" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#ffffff" @clickLeft="goBack" />
|
||||
|
||||
<scroll-view scroll-y class="content">
|
||||
<view class="form-item">
|
||||
<view class="label">收件人</view>
|
||||
<input class="input" v-model="receiver" placeholder="请输入收货人名字" />
|
||||
</view>
|
||||
<view class="divider-line"></view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label">手机号</view>
|
||||
<input class="input" v-model="mobile" type="number" placeholder="收货人的电话,方便联系" />
|
||||
</view>
|
||||
<view class="divider-line"></view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label">邮箱</view>
|
||||
<input class="input" v-model="email" type="text" placeholder="用于接收电子卡等信息(可选)" />
|
||||
</view>
|
||||
<view class="divider-line"></view>
|
||||
|
||||
<view class="form-item select-item" @click="openAreaPicker">
|
||||
<view class="label">地址</view>
|
||||
<view class="input placeholder" v-if="!regionText">请选择地址</view>
|
||||
<view class="input" v-else>{{ regionText }}</view>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="divider-line"></view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label">详细地址</view>
|
||||
<input class="input" v-model="detail" placeholder="请输入街道、门牌等详细地址信息" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="footer-bar">
|
||||
<view class="confirm-btn" @click="submit">确定</view>
|
||||
</view>
|
||||
|
||||
<!-- 省市区选择器 -->
|
||||
<view v-if="showAreaPicker" class="picker-mask" @click="closeAreaPicker"></view>
|
||||
<view v-if="showAreaPicker" class="picker-panel">
|
||||
<view class="picker-header">
|
||||
<text class="picker-btn" @click="closeAreaPicker">取消</text>
|
||||
<text class="picker-title">选择地区</text>
|
||||
<text class="picker-btn ok" @click="confirmArea">确定</text>
|
||||
</view>
|
||||
<picker-view v-if="provinces.length && cities.length && areas.length" class="picker-view" :indicator-style="indicatorStyle" :value="pickerIndex" @change="onAreaChange">
|
||||
<picker-view-column>
|
||||
<view v-for="(p,pi) in provinces" :key="pi" class="picker-item">{{ p.name }}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view v-for="(c,ci) in cities" :key="ci" class="picker-item">{{ c.name }}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view v-for="(a,ai) in areas" :key="ai" class="picker-item">{{ a.name }}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<view v-else class="picker-empty">地区数据加载中...</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import areaList from '@/utils/areaList.js'
|
||||
|
||||
const receiver = ref('')
|
||||
const mobile = ref('')
|
||||
const regionText = ref('')
|
||||
const detail = ref('')
|
||||
const email = ref('')
|
||||
const editingId = ref(null)
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
// 省市区数据(适配树形数组:[{code,label,value,children:[...] }...])
|
||||
const provinces = ref([])
|
||||
const cities = ref([])
|
||||
const areas = ref([])
|
||||
const pickerIndex = ref([0,0,0])
|
||||
const showAreaPicker = ref(false)
|
||||
const indicatorStyle = `height: 80rpx;`
|
||||
|
||||
const normalizeNode = (node) => ({
|
||||
code: node?.code || '',
|
||||
name: node?.label || node?.value || node?.name || '',
|
||||
children: Array.isArray(node?.children) ? node.children : []
|
||||
})
|
||||
|
||||
const getAreaTree = () => {
|
||||
const raw = areaList && (areaList.default || areaList)
|
||||
return Array.isArray(raw) ? raw.map(normalizeNode) : []
|
||||
}
|
||||
|
||||
const buildData = () => {
|
||||
const tree = getAreaTree()
|
||||
provinces.value = tree.length ? tree : [{ code: '', name: '', children: [] }]
|
||||
const pIdx = Math.min(pickerIndex.value[0], Math.max(provinces.value.length - 1, 0))
|
||||
const pNode = provinces.value[pIdx]
|
||||
cities.value = (pNode?.children || []).map(normalizeNode)
|
||||
if (!cities.value.length) cities.value = [{ code: '', name: '', children: [] }]
|
||||
const cIdx = Math.min(pickerIndex.value[1], Math.max(cities.value.length - 1, 0))
|
||||
const cNode = cities.value[cIdx]
|
||||
areas.value = (cNode?.children || []).map(normalizeNode)
|
||||
if (!areas.value.length) areas.value = [{ code: '', name: '' }]
|
||||
}
|
||||
|
||||
const openAreaPicker = () => {
|
||||
showAreaPicker.value = true
|
||||
pickerIndex.value = [0,0,0]
|
||||
buildData()
|
||||
}
|
||||
const closeAreaPicker = () => { showAreaPicker.value = false }
|
||||
|
||||
const onAreaChange = (e) => {
|
||||
const val = (e && e.detail && e.detail.value) ? e.detail.value : [0,0,0]
|
||||
const [pi, ci, ai] = val
|
||||
if (pi !== pickerIndex.value[0]) {
|
||||
pickerIndex.value = [pi, 0, 0]
|
||||
buildData()
|
||||
return
|
||||
}
|
||||
if (ci !== pickerIndex.value[1]) {
|
||||
pickerIndex.value = [pi, ci, 0]
|
||||
buildData()
|
||||
return
|
||||
}
|
||||
pickerIndex.value = [pi, ci, ai]
|
||||
}
|
||||
|
||||
const confirmArea = () => {
|
||||
const p = provinces.value[pickerIndex.value[0]]
|
||||
const c = cities.value[pickerIndex.value[1]]
|
||||
const a = areas.value[pickerIndex.value[2]]
|
||||
regionText.value = [p?.name, c?.name, a?.name].filter(Boolean).join(' ')
|
||||
closeAreaPicker()
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
if (opts && opts.id) {
|
||||
// 回填编辑
|
||||
editingId.value = Number(opts.id)
|
||||
try {
|
||||
const list = uni.getStorageSync('goods_addresses') || []
|
||||
const target = Array.isArray(list) ? list.find(a => a.id === editingId.value) : null
|
||||
if (target) {
|
||||
receiver.value = target.receiver || ''
|
||||
mobile.value = target.mobile || ''
|
||||
regionText.value = target.region || ''
|
||||
detail.value = target.detail || ''
|
||||
email.value = target.email || ''
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
if (!receiver.value) return uni.showToast({ title: '请输入收件人', icon: 'none' })
|
||||
if (!/^1\d{10}$/.test(mobile.value)) return uni.showToast({ title: '请输入正确手机号', icon: 'none' })
|
||||
if (!regionText.value) return uni.showToast({ title: '请选择地址', icon: 'none' })
|
||||
if (!detail.value) return uni.showToast({ title: '请输入详细地址', icon: 'none' })
|
||||
if (email.value && !/^([a-zA-Z0-9_\.-]+)@([a-zA-Z0-9\.-]+)\.([a-zA-Z]{2,})$/.test(email.value)) return uni.showToast({ title: '邮箱格式不正确', icon: 'none' })
|
||||
|
||||
const STORAGE_KEY = 'goods_addresses'
|
||||
let list = []
|
||||
try {
|
||||
const cached = uni.getStorageSync(STORAGE_KEY)
|
||||
list = Array.isArray(cached) ? cached : []
|
||||
} catch (e) { list = [] }
|
||||
|
||||
if (editingId.value) {
|
||||
// 更新
|
||||
list = list.map(a => a.id === editingId.value ? {
|
||||
...a,
|
||||
receiver: receiver.value,
|
||||
mobile: mobile.value,
|
||||
region: regionText.value,
|
||||
detail: detail.value,
|
||||
fullAddress: `${regionText.value} ${detail.value}`,
|
||||
email: email.value
|
||||
} : a)
|
||||
} else {
|
||||
// 新增
|
||||
const address = {
|
||||
id: Date.now(),
|
||||
receiver: receiver.value,
|
||||
mobile: mobile.value,
|
||||
region: regionText.value,
|
||||
detail: detail.value,
|
||||
fullAddress: `${regionText.value} ${detail.value}`,
|
||||
email: email.value,
|
||||
createdAt: Date.now()
|
||||
}
|
||||
list.unshift(address)
|
||||
}
|
||||
uni.setStorageSync(STORAGE_KEY, list)
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
setTimeout(() => { uni.navigateBack() }, 500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.address-page { min-height: 100vh; background: #fff; }
|
||||
.content { position: absolute; top: 140rpx; bottom: 120rpx; left: 0; right: 0; background: #fff; }
|
||||
.form-item { display: flex; align-items: center; padding: 24rpx; }
|
||||
.label { width: 160rpx; color: #333; font-size: 30rpx; }
|
||||
.input { flex: 1; color: #333; font-size: 30rpx; }
|
||||
.placeholder { color: #bbb; }
|
||||
.divider-line { height: 2rpx; background: #eee; margin: 0 24rpx; }
|
||||
.select-item { position: relative; }
|
||||
.arrow { position: absolute; right: 24rpx; color: #999; font-size: 48rpx; }
|
||||
.footer-bar { position: fixed; left: 0; right: 0; bottom: 0; height: 120rpx; background: #27c5b8; display: flex; align-items: center; justify-content: center; }
|
||||
.confirm-btn { color: #fff; font-size: 34rpx; font-weight: 700; }
|
||||
|
||||
/* 地区选择器 */
|
||||
.picker-mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0,0,0,0.4); }
|
||||
.picker-panel { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; }
|
||||
.picker-header { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border-bottom: 1rpx solid #eee; }
|
||||
.picker-title { font-size: 30rpx; color: #333; }
|
||||
.picker-btn { color: #666; font-size: 28rpx; }
|
||||
.picker-btn.ok { color: #38c1b1; }
|
||||
.picker-view { height: 480rpx; }
|
||||
.picker-item { height: 80rpx; line-height: 80rpx; text-align: center; color: #333; }
|
||||
.picker-empty { padding: 40rpx; text-align: center; color: #999; }
|
||||
</style>
|
||||
|
||||
91
pages_goods/exchange/address_list.vue
Normal file
91
pages_goods/exchange/address_list.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<view class="addr-list-page">
|
||||
<uni-nav-bar left-icon="left" title="地址管理" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#ffffff" @clickLeft="goBack" />
|
||||
|
||||
<scroll-view scroll-y class="list-wrap">
|
||||
<view v-if="!list.length" class="empty">暂无收货地址</view>
|
||||
<view v-for="item in list" :key="item.id" class="addr-item" @click="select(item)">
|
||||
<view class="addr-main">
|
||||
<view class="row">
|
||||
<text class="name">{{ item.receiver }}</text>
|
||||
<text class="mobile">{{ item.mobile }}</text>
|
||||
</view>
|
||||
<view class="row small">{{ item.fullAddress || (item.region + ' ' + item.detail) }}</view>
|
||||
</view>
|
||||
<view class="addr-actions">
|
||||
<text class="act" @click.stop="edit(item)">编辑</text>
|
||||
<text class="act danger" @click.stop="remove(item)">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="footer-bar">
|
||||
<view class="add-btn" @click="addNew">新增地址</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
|
||||
const STORAGE_KEY = 'goods_addresses'
|
||||
const SELECTED_KEY = 'goods_selected_address'
|
||||
const list = ref([])
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const load = () => {
|
||||
try {
|
||||
const cached = uni.getStorageSync(STORAGE_KEY)
|
||||
list.value = Array.isArray(cached) ? cached : []
|
||||
} catch (e) {
|
||||
list.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onShow(() => load())
|
||||
|
||||
const addNew = () => uni.navigateTo({ url: '/pages_goods/exchange/address' })
|
||||
|
||||
const edit = (item) => {
|
||||
uni.navigateTo({ url: `/pages_goods/exchange/address?id=${item.id}` })
|
||||
}
|
||||
|
||||
const remove = (item) => {
|
||||
uni.showModal({
|
||||
title: '删除地址',
|
||||
content: '确定删除该地址吗?',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
list.value = list.value.filter(a => a.id !== item.id)
|
||||
uni.setStorageSync(STORAGE_KEY, list.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const select = (item) => {
|
||||
uni.setStorageSync(SELECTED_KEY, item)
|
||||
uni.showToast({ title: '已选择该地址', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 300)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.addr-list-page { min-height: 100vh; background: #fff; }
|
||||
.list-wrap { position: absolute; top: 140rpx; bottom: 120rpx; left: 0; right: 0; }
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 24rpx; }
|
||||
.addr-item { display: flex; align-items: center; justify-content: space-between; padding: 24rpx; border-bottom: 1rpx solid #f0f0f0; }
|
||||
.addr-main { flex: 1; }
|
||||
.row { display: flex; align-items: center; gap: 20rpx; margin-bottom: 8rpx; }
|
||||
.row.small { color: #666; font-size: 26rpx; }
|
||||
.name { color: #333; font-size: 30rpx; }
|
||||
.mobile { color: #333; font-size: 30rpx; }
|
||||
.addr-actions { display: flex; align-items: center; gap: 24rpx; margin-left: 24rpx; }
|
||||
.act { color: #38c1b1; font-size: 28rpx; }
|
||||
.act.danger { color: #ff4d4f; }
|
||||
.footer-bar { position: fixed; left: 0; right: 0; bottom: 0; height: 120rpx; background: #27c5b8; display: flex; align-items: center; justify-content: center; }
|
||||
.add-btn { color: #fff; font-size: 34rpx; font-weight: 700; }
|
||||
</style>
|
||||
|
||||
192
pages_goods/exchange/index.vue
Normal file
192
pages_goods/exchange/index.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<view class="exchange-page">
|
||||
<uni-nav-bar left-icon="left" title="在线兑换" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#ffffff" @clickLeft="goBack" />
|
||||
|
||||
<scroll-view scroll-y class="content">
|
||||
<!-- 收货地址 -->
|
||||
<view class="addr-card" @click="changeAddress">
|
||||
<view class="addr-header">
|
||||
<text class="addr-title">收货信息</text>
|
||||
<text class="addr-change">{{ selectedAddress ? '更换' : '添加' }}</text>
|
||||
</view>
|
||||
<view v-if="selectedAddress" class="addr-info">
|
||||
<text class="addr-row">{{ selectedAddress.receiver }} {{ selectedAddress.mobile }}</text>
|
||||
<text class="addr-row small">{{ selectedAddress.fullAddress || (selectedAddress.region + ' ' + selectedAddress.detail) }}</text>
|
||||
</view>
|
||||
<view v-else class="addr-empty">请添加收货地址</view>
|
||||
</view>
|
||||
|
||||
<view class="goods-title">{{ title }}</view>
|
||||
<view class="price">{{ price }}积分</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-label">兑换数量</view>
|
||||
<view class="qty-row">
|
||||
<view class="qty-btn" :class="{ active: qty === 1 }" @click="setQty(1)">1</view>
|
||||
<view class="qty-btn" :class="{ active: qty === 2 }" @click="setQty(2)">2</view>
|
||||
<view class="qty-btn" :class="{ active: qty === 3 }" @click="setQty(3)">3</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-label">所需积分</view>
|
||||
<view class="need-points">{{ needPoints }}积分</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="footer-bar">
|
||||
<view class="confirm-btn" @click="goAddress">我要兑换</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import goods_api from '@/api/goods_api'
|
||||
const title = ref('')
|
||||
const price = ref(0)
|
||||
const qty = ref(1)
|
||||
const needPoints = ref(0)
|
||||
const totalPoints = ref(0)
|
||||
const selectedAddress = ref(null)
|
||||
const id = ref('')
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const setQty = (n) => {
|
||||
qty.value = n
|
||||
needPoints.value = n * Number(price.value || 0)
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
if (opts) {
|
||||
title.value = decodeURIComponent(opts.title || '')
|
||||
price.value = Number(opts.price || 0)
|
||||
id.value = opts.id || ''
|
||||
setQty(1)
|
||||
getTotalPoints()
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
loadAddress()
|
||||
})
|
||||
|
||||
const STORAGE_KEY = 'goods_addresses'
|
||||
const SELECTED_KEY = 'goods_selected_address'
|
||||
const loadAddress = () => {
|
||||
try {
|
||||
const sel = uni.getStorageSync(SELECTED_KEY)
|
||||
if (sel && sel.id) {
|
||||
selectedAddress.value = sel
|
||||
return
|
||||
}
|
||||
const list = uni.getStorageSync(STORAGE_KEY)
|
||||
if (Array.isArray(list) && list.length) {
|
||||
selectedAddress.value = list[0]
|
||||
} else {
|
||||
selectedAddress.value = null
|
||||
}
|
||||
} catch (e) {
|
||||
selectedAddress.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const changeAddress = () => {
|
||||
let list = []
|
||||
try {
|
||||
const cached = uni.getStorageSync(STORAGE_KEY)
|
||||
list = Array.isArray(cached) ? cached : []
|
||||
} catch (e) { list = [] }
|
||||
if (!list.length) {
|
||||
uni.navigateTo({ url: '/pages_goods/exchange/address' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages_goods/exchange/address_list' })
|
||||
}
|
||||
|
||||
const goAddress = () => {
|
||||
if (needPoints.value > totalPoints.value) {
|
||||
uni.showToast({ title: '积分不足', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!selectedAddress.value) {
|
||||
changeAddress()
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
goodsUuid: id.value,
|
||||
address: selectedAddress.value.fullAddress,
|
||||
user_name: selectedAddress.value.receiver,
|
||||
mobile: selectedAddress.value.mobile,
|
||||
goodsNum: qty.value,
|
||||
email: selectedAddress.value.email
|
||||
}
|
||||
console.log(data)
|
||||
|
||||
|
||||
//{"goodsUuid":"e6ca84edc8e64242a379dd0b895ea812",
|
||||
// "address":"北京市东城区你一下",
|
||||
// "user_name":"噢噢",
|
||||
// "mobile":"13825244552",
|
||||
// "goodsNum":"1",
|
||||
// "email":""}
|
||||
// 模拟下单:此处可调用后端接口完成兑换
|
||||
uni.showModal({
|
||||
title: '确认兑换',
|
||||
content: `兑换“${title.value}”x${qty.value},共需 ${needPoints.value} 积分`,
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
// 扣减本地积分(演示)
|
||||
totalPoints.value = Math.max(0, totalPoints.value - needPoints.value)
|
||||
uni.removeStorageSync(SELECTED_KEY)
|
||||
goods_api.createGoodsOrder(data).then(res => {
|
||||
console.log(res)
|
||||
if (res.code == 200) {
|
||||
uni.showToast({ title: '兑换成功', icon: 'success' })
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.showToast({ title: res.msg, icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const getTotalPoints = () => {
|
||||
goods_api.getTotalPoints && goods_api.getTotalPoints().then(res => {
|
||||
if (res.code == 200) {
|
||||
totalPoints.value = res.data || 0
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.exchange-page { min-height: 100vh; background: #fff; }
|
||||
.content { position: absolute; top: 140rpx; bottom: 120rpx; left: 0; right: 0; background: #fff; }
|
||||
.addr-card { background: #fff; padding: 24rpx; border-bottom: 1rpx solid #f0f0f0; }
|
||||
.addr-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
|
||||
.addr-title { color: #333; font-size: 30rpx; }
|
||||
.addr-change { color: #38c1b1; font-size: 28rpx; }
|
||||
.addr-info { display: flex; flex-direction: column; gap: 8rpx; }
|
||||
.addr-row { color: #333; font-size: 28rpx; }
|
||||
.addr-row.small { color: #666; font-size: 26rpx; }
|
||||
.addr-empty { color: #bbb; font-size: 28rpx; }
|
||||
.goods-title { padding: 24rpx; font-size: 32rpx; color: #333; line-height: 1.6; }
|
||||
.price { padding: 0 24rpx 24rpx; color: #e74c3c; font-size: 30rpx; font-weight: 700; }
|
||||
.divider { height: 16rpx; background: #f5f5f5; }
|
||||
.section { padding: 24rpx; }
|
||||
.section-label { color: #333; font-size: 30rpx; margin-bottom: 20rpx; }
|
||||
.qty-row { display: flex; gap: 24rpx; }
|
||||
.qty-btn { flex: 1; text-align: center; height: 96rpx; line-height: 96rpx; border: 2rpx solid #38c1b1; color: #38c1b1; border-radius: 12rpx; font-size: 36rpx; }
|
||||
.qty-btn.active { background: #38c1b1; color: #fff; }
|
||||
.need-points { color: #333; font-size: 30rpx; text-align: right; }
|
||||
.footer-bar { position: fixed; left: 0; right: 0; bottom: 0; height: 120rpx; background: #27c5b8; display: flex; align-items: center; justify-content: center; }
|
||||
.confirm-btn { color: #fff; font-size: 34rpx; font-weight: 700; }
|
||||
</style>
|
||||
|
||||
113
pages_goods/myRedemption/myRedemption.vue
Normal file
113
pages_goods/myRedemption/myRedemption.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<uni-nav-bar left-icon="left" title="我的兑换" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#ffffff" @clickLeft="goBack" />
|
||||
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="content"
|
||||
refresher-enabled
|
||||
:refresher-triggered="refresherTriggered"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="onReachBottom"
|
||||
:lower-threshold="80"
|
||||
>
|
||||
<view v-if="!orderList.length" class="empty">暂无兑换记录</view>
|
||||
<view v-for="item in orderList" :key="item.uuid" class="card">
|
||||
<view class="row">
|
||||
<text class="label">兑换物品:</text>
|
||||
<text class="value">{{ item.goods_name }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">兑换时间:</text>
|
||||
<text class="value">{{ formatDate(item.create_date) }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">状态:</text>
|
||||
<text class="status" :class="statusClass(item.status)">{{ statusText(item.status) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-footer" v-if="isLoading">加载中...</view>
|
||||
<view class="list-footer" v-else-if="noMore && orderList.length">没有更多了</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import goods_api from '@/api/goods_api'
|
||||
import { ref, onMounted } from 'vue'
|
||||
const orderList = ref([])
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const isLoading = ref(false)
|
||||
const noMore = ref(false)
|
||||
const refresherTriggered = ref(false)
|
||||
|
||||
const fetchList = () => {
|
||||
if (isLoading.value || noMore.value) return
|
||||
isLoading.value = true
|
||||
goods_api.getGoodsOrderList({ page: page.value, pageSize: pageSize.value }).then(res => {
|
||||
if (res.code == 200 && res.data) {
|
||||
const list = Array.isArray(res.data.list) ? res.data.list : []
|
||||
orderList.value = page.value === 1 ? list : orderList.value.concat(list)
|
||||
noMore.value = !!res.data.isLastPage
|
||||
page.value = (res.data.pageNum || page.value) + 1
|
||||
}
|
||||
}).finally(() => {
|
||||
isLoading.value = false
|
||||
refresherTriggered.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const onRefresh = () => {
|
||||
refresherTriggered.value = true
|
||||
noMore.value = false
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
const onReachBottom = () => {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
const formatDate = (sec) => {
|
||||
if (!sec) return ''
|
||||
const d = new Date(Number(sec) * 1000)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
const statusText = (s) => {
|
||||
if (s === 1 || s === 2) return '已支付'
|
||||
if (s === 3) return '待支付'
|
||||
return ''
|
||||
}
|
||||
|
||||
const statusClass = (s) => {
|
||||
return (s === 1 || s === 2) ? 'paid' : 'unpaid'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
page.value = 1
|
||||
noMore.value = false
|
||||
orderList.value = []
|
||||
fetchList()
|
||||
})
|
||||
const goBack = () => uni.navigateBack()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { min-height: 100vh; background: #fff; }
|
||||
.content { position: absolute; top: 140rpx; bottom: 0; left: 0; right: 0; background: #fff; }
|
||||
.card { background: #fff; padding: 24rpx; border-bottom: 16rpx solid #f5f5f5; }
|
||||
.row { display: flex; align-items: center; padding: 10rpx 0; }
|
||||
.label { color: #333; font-size: 30rpx; width: 180rpx; }
|
||||
.value { color: #333; font-size: 30rpx; }
|
||||
.status { font-size: 30rpx; }
|
||||
.status.paid { color: #333; }
|
||||
.status.unpaid { color: #333; }
|
||||
.list-footer { text-align: center; color: #999; padding: 24rpx 0; }
|
||||
.empty { text-align: center; color: #999; padding: 40rpx 0; }
|
||||
</style>
|
||||
|
||||
555
pages_goods/pointMall/pointMall.vue
Normal file
555
pages_goods/pointMall/pointMall.vue
Normal file
@ -0,0 +1,555 @@
|
||||
<template>
|
||||
<view class="point-mall-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="积分商城"
|
||||
@clickLeft="goBack"
|
||||
fixed
|
||||
color="#e74c3c"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#ffffff"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<text class="search-btn" @click="goToSearch">搜索</text>
|
||||
</template>
|
||||
</uni-nav-bar>
|
||||
|
||||
<!-- 轮播图横幅 -->
|
||||
<view class="banner-section">
|
||||
<swiper class="banner-swiper" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="500">
|
||||
<swiper-item v-for="(banner, index) in bannerList" :key="banner.uuid">
|
||||
<view class="banner-item" @click="goToBannerDetail(banner)">
|
||||
<image class="banner-image" :src="banner.headImg" mode="aspectFill"></image>
|
||||
<view class="banner-content">
|
||||
<view class="banner-text green">{{ banner.title }}</view>
|
||||
<view class="banner-text orange">{{ banner.create_date }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
<!-- 如果没有数据,显示默认轮播图 -->
|
||||
<swiper-item v-if="bannerList.length === 0">
|
||||
<view class="banner-item">
|
||||
<image class="banner-image" src="/static/banner-bg.jpg" mode="aspectFill"></image>
|
||||
<view class="banner-content">
|
||||
<view class="banner-text green">第二届京津冀感染肝病高峰论坛&</view>
|
||||
<view class="banner-text green">第九届河北省感染科医师培训班</view>
|
||||
<view class="banner-text orange">2016.10.14-2016.10.16</view>
|
||||
<view class="banner-text orange">河北•石家庄</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<!-- 筛选排序栏 -->
|
||||
<view class="filter-bar">
|
||||
<view class="filter-item" @click="showFilter">
|
||||
<text class="filter-text">{{ selectedTagName || '筛选' }}</text>
|
||||
<text class="filter-icon">▼</text>
|
||||
</view>
|
||||
<view class="filter-divider"></view>
|
||||
<view class="filter-item" @click="showSort">
|
||||
<text class="filter-text">{{ selectedSortLabel || '排序' }}</text>
|
||||
<text class="filter-icon">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 筛选下拉 -->
|
||||
<view v-if="showFilterDropdown" class="dropdown">
|
||||
<view
|
||||
class="dropdown-item"
|
||||
:class="{ active: selectedTagId === null }"
|
||||
@click="onTagSelect({ id: null, name: '全部' })">
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
v-for="tag in tagList"
|
||||
:key="tag.id"
|
||||
class="dropdown-item"
|
||||
:class="{ active: selectedTagId === tag.id }"
|
||||
@click="onTagSelect(tag)">
|
||||
{{ tag.name }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 排序下拉 -->
|
||||
<view v-if="showSortDropdown" class="dropdown">
|
||||
<view
|
||||
v-for="opt in sortOptions"
|
||||
:key="opt.value"
|
||||
class="dropdown-item"
|
||||
:class="{ active: selectedSortValue === opt.value }"
|
||||
@click="onSortSelect(opt)">
|
||||
{{ opt.label }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品网格(上拉加载) -->
|
||||
<scroll-view class="product-grid" scroll-y @scrolltolower="getGoodsList" :lower-threshold="100">
|
||||
<view class="grid-wrap">
|
||||
<view class="product-item" v-for="(product, index) in products" :key="product.uuid || index" @click="goToProductDetail(product)">
|
||||
<view class="product-image-container">
|
||||
<image class="product-image" :src="product.image" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="product-info">
|
||||
<text class="product-title">{{ product.title }}</text>
|
||||
<text class="product-price">{{ product.price }}积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 加载与无更多提示 -->
|
||||
<view class="list-footer" v-if="isLoadingMore">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view class="list-footer" v-else-if="noMore && products.length > 0">
|
||||
<text>没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<view class="bottom-nav">
|
||||
<view class="nav-item active" @click="goToMyRedemption">
|
||||
<view class="nav-icon">💰</view>
|
||||
<text class="nav-text">我的兑换</text>
|
||||
</view>
|
||||
<view class="nav-divider"></view>
|
||||
<view class="nav-item" @click="goToBuyPoints">
|
||||
<view class="nav-icon">🛒</view>
|
||||
<text class="nav-text">购买积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import goods_api from '@/api/goods_api'
|
||||
import docUrl from '@/utils/docUrl'
|
||||
|
||||
// 轮播图数据
|
||||
const bannerList = ref([])
|
||||
|
||||
// 商品数据
|
||||
const products = ref([])
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const isLoadingMore = ref(false)
|
||||
const noMore = ref(false)
|
||||
|
||||
// 筛选相关
|
||||
const tagList = ref([])
|
||||
const selectedTagId = ref(null)
|
||||
const selectedTagName = ref('')
|
||||
const showFilterDropdown = ref(false)
|
||||
|
||||
// 排序相关
|
||||
const showSortDropdown = ref(false)
|
||||
const selectedSortValue = ref(null)
|
||||
const selectedSortLabel = ref('')
|
||||
const sortOptions = ref([
|
||||
{ label: '积分从小到大', value: 4 },
|
||||
{ label: '积分从大到小', value: 3 },
|
||||
{ label: '兑换从多到少', value: 5 },
|
||||
{ label: '兑换从少到多', value: 6 }
|
||||
])
|
||||
|
||||
// 方法
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const goToSearch = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_app/search/search'
|
||||
})
|
||||
}
|
||||
|
||||
const showFilter = () => {
|
||||
showFilterDropdown.value = !showFilterDropdown.value
|
||||
}
|
||||
|
||||
const showSort = () => {
|
||||
showSortDropdown.value = !showSortDropdown.value
|
||||
}
|
||||
|
||||
const onTagSelect = (tag) => {
|
||||
selectedTagId.value = tag.id
|
||||
selectedTagName.value = tag.name
|
||||
showFilterDropdown.value = false
|
||||
// 重置并拉取
|
||||
products.value = []
|
||||
page.value = 1
|
||||
noMore.value = false
|
||||
getGoodsList()
|
||||
}
|
||||
|
||||
const onSortSelect = (opt) => {
|
||||
selectedSortValue.value = opt.value
|
||||
selectedSortLabel.value = opt.label
|
||||
showSortDropdown.value = false
|
||||
products.value = []
|
||||
page.value = 1
|
||||
noMore.value = false
|
||||
getGoodsList()
|
||||
}
|
||||
|
||||
const goToProductDetail = (product) => {
|
||||
const id = product.uuid || product.id
|
||||
uni.navigateTo({
|
||||
url: `/pages_goods/productDetail/productDetail?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
const goToMyRedemption = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_goods/myRedemption/myRedemption'
|
||||
})
|
||||
}
|
||||
|
||||
const goToBuyPoints = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_app/buyPoint/buyPoint'
|
||||
})
|
||||
}
|
||||
|
||||
const goToBannerDetail = (banner) => {
|
||||
// 跳转到轮播图详情页面
|
||||
uni.navigateTo({
|
||||
url: `/pages_app/webview/webview?url=${encodeURIComponent(banner.path)}&title=${encodeURIComponent(banner.title)}`
|
||||
})
|
||||
}
|
||||
|
||||
const getGoodsNewsList = () => {
|
||||
goods_api.goodsNewsList().then(res => {
|
||||
console.log('轮播图数据:', res)
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
// 处理轮播图数据
|
||||
bannerList.value = res.data.map(item => ({
|
||||
uuid: item.uuid,
|
||||
title: item.title,
|
||||
headImg: docUrl + item.headImg,
|
||||
create_date: item.create_date,
|
||||
path: item.path,
|
||||
agreenum: item.agreenum,
|
||||
readnum: item.readnum
|
||||
}))
|
||||
} else {
|
||||
console.log('轮播图数据为空或请求失败')
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取轮播图数据失败:', err)
|
||||
uni.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getGoodsList = () => {
|
||||
if (noMore.value || isLoadingMore.value) return
|
||||
isLoadingMore.value = true
|
||||
const tagParam = selectedTagId.value ? selectedTagId.value : ""
|
||||
const sortParam = selectedSortValue.value ? selectedSortValue.value : 1
|
||||
goods_api.goodsList({ name: '', page: page.value, sort: sortParam, tag_type: tagParam }).then(res => {
|
||||
console.log('商品数据:', res)
|
||||
if ((res.code === 200 || res.code === '200') && res.data && res.data.list) {
|
||||
const list = res.data.list.map(item => ({
|
||||
uuid: item.uuid,
|
||||
title: item.name,
|
||||
price: item.bonuspoints,
|
||||
image: docUrl + item.img,
|
||||
times: item.times,
|
||||
type: item.type,
|
||||
upan: item.upan
|
||||
}))
|
||||
products.value = products.value.concat(list)
|
||||
const { isLastPage, pageNum } = res.data
|
||||
page.value = pageNum + 1
|
||||
noMore.value = !!isLastPage
|
||||
} else {
|
||||
console.log('商品列表为空或请求失败')
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取商品列表失败:', err)
|
||||
uni.showToast({ title: '获取商品失败', icon: 'none' })
|
||||
}).finally(() => {
|
||||
isLoadingMore.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const getGoodsTagList = () => {
|
||||
goods_api.goodsTagList({}).then(res => {
|
||||
console.log('商品类型数据:', res)
|
||||
if (res.code === 200 && res.data) {
|
||||
tagList.value = res.data.map(i => ({ id: i.id, name: i.name }))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getGoodsNewsList()
|
||||
// 初始化分页并加载第一页
|
||||
page.value = 1
|
||||
noMore.value = false
|
||||
products.value = []
|
||||
getGoodsList()
|
||||
getGoodsTagList()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.point-mall-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
/* 搜索按钮样式 */
|
||||
.search-btn {
|
||||
color: #e74c3c;
|
||||
font-size: 28rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
/* 轮播图样式 */
|
||||
.banner-section {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.banner-swiper {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.banner-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #87CEEB 0%, #98FB98 50%, #F0E68C 100%);
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
padding: 6px 12px;
|
||||
margin: 3px 0;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.banner-text.green {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.banner-text.orange {
|
||||
color: #e67e22;
|
||||
}
|
||||
|
||||
/* 轮播图指示器样式 */
|
||||
.banner-swiper ::v-deep .uni-swiper-dots {
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.banner-swiper ::v-deep .uni-swiper-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.banner-swiper ::v-deep .uni-swiper-dot-active {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 筛选排序栏样式 */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
background: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dropdown-item.active {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
background: #fff7f7;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.filter-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.filter-divider {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* 商品网格样式 */
|
||||
.product-grid {
|
||||
height: calc(100vh - 320rpx); /* 预留顶部导航/筛选及底部栏 */
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.grid-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-footer {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 12px 0 80px; /* 避开底部吸底栏 */
|
||||
}
|
||||
|
||||
.product-item {
|
||||
width: calc(50% - 8px);
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.product-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.product-image-container {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.product-title {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 14px;
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 底部导航栏样式 */
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60px;
|
||||
background-color: #20b2aa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 8px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-divider {
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
162
pages_goods/productDetail/productDetail.vue
Normal file
162
pages_goods/productDetail/productDetail.vue
Normal file
@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<view class="product-detail-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
:title="navTitle"
|
||||
@clickLeft="goBack"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#ffffff"
|
||||
/>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<scroll-view scroll-y class="detail-scroll">
|
||||
<!-- 商品主图轮播 -->
|
||||
<view class="swiper-box">
|
||||
<swiper class="detail-swiper" :indicator-dots="true" :autoplay="false" :interval="3000" :duration="400">
|
||||
<swiper-item v-for="(img, idx) in images" :key="idx">
|
||||
<image class="swiper-image" :src="img" mode="aspectFit"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<!-- 标题与价格、销量 -->
|
||||
<view class="summary">
|
||||
<view class="title">{{ product.title }}</view>
|
||||
<view class="price-row">
|
||||
<text class="price">{{ product.price }}积分</text>
|
||||
<text class="exchanged">已兑换{{ product.times }}件</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<view class="tips-section">
|
||||
<view class="tips-title">温馨提示</view>
|
||||
<view class="tip-item"><text class="num">1</text><text class="tip-text">商品一经兑换,消耗的积分恕不退还,请各位用户兑换前仔细阅读商品描述详情。</text></view>
|
||||
<view class="tip-item"><text class="num">2</text><text class="tip-text">商品兑换后不接受退换货申请,虚拟商品请在商品有效期内及时兑换。</text></view>
|
||||
<view class="tip-item"><text class="num">3</text><text class="tip-text">所有实物商品包邮,虚拟商品将发送相关信息到联系邮箱或手机。</text></view>
|
||||
<view class="tip-item"><text class="num">4</text><text class="tip-text">实物商品兑换后2-4工作日内发放,虚拟商品兑换后1-3工作日内发送。</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 物品展示 -->
|
||||
<view class="section-title">物品展示</view>
|
||||
<view class="rich-wrapper">
|
||||
<rich-text :nodes="product.content"></rich-text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部兑换按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="redeem-btn" @click="goExchange">在线兑换</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import goods_api from '@/api/goods_api'
|
||||
import docUrl from '@/utils/docUrl'
|
||||
|
||||
const product = ref({ id: '', title: '', price: 0, times: 0, content: '' })
|
||||
const images = ref([])
|
||||
const navTitle = ref('商品详情')
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const fetchDetail = async (id) => {
|
||||
try {
|
||||
const res = await goods_api.getGoodsDetail({ uuid: id })
|
||||
if ((res.code === 200 || res.code === '200') && res.data) {
|
||||
product.value = {
|
||||
id,
|
||||
title: res.data.name || '',
|
||||
price: res.data.bonuspoints || 0,
|
||||
times: res.data.times || 0,
|
||||
content: res.data.content || ''
|
||||
}
|
||||
navTitle.value = (product.value.title || '').slice(0, 18)
|
||||
const list = []
|
||||
if (res.data.detial_imgpath) {
|
||||
res.data.detial_imgpath.split(',').forEach(u => u && list.push(docUrl + u))
|
||||
}
|
||||
// if (res.data.img) list.push(docUrl + res.data.img)
|
||||
images.value = list.length ? list : ['/static/product2.jpg']
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '获取详情失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
uni.showToast({ title: '网络错误', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
if (opts && opts.id) fetchDetail(opts.id)
|
||||
})
|
||||
|
||||
const goExchange = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages_goods/exchange/index?id=${product.value.id}&title=${encodeURIComponent(product.value.title)}&price=${product.value.price}`
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.product-detail-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.detail-scroll {
|
||||
position: absolute;
|
||||
top: 140rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 100rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 主图轮播 */
|
||||
.swiper-box { background: #fff; }
|
||||
.detail-swiper { height: 480rpx; }
|
||||
.swiper-image { width: 100%; height: 100%; }
|
||||
|
||||
/* 概要 */
|
||||
.summary { background: #fff; padding: 24rpx; }
|
||||
.title { font-size: 34rpx; color: #333; line-height: 1.6; }
|
||||
.price-row { display: flex; justify-content: space-between; align-items: center; margin-top: 16rpx; }
|
||||
.price { color: #e74c3c; font-size: 34rpx; font-weight: 700; }
|
||||
.exchanged { color: #999; font-size: 24rpx; }
|
||||
.divider { height: 16rpx; background: #f5f5f5; }
|
||||
|
||||
/* 温馨提示 */
|
||||
.tips-section { background: #fff; padding: 24rpx; }
|
||||
.tips-title { text-align: center; font-size: 36rpx; font-weight: 700; color: #333; margin-bottom: 16rpx; }
|
||||
.tip-item { display: flex; align-items: flex-start; background: #f7f7f7; border-radius: 8rpx; padding: 20rpx; margin: 12rpx 0; }
|
||||
.num { width: 36rpx; height: 36rpx; line-height: 36rpx; text-align: center; color: #888; background: #fff; border-radius: 50%; margin-right: 16rpx; border: 1rpx solid #ddd; }
|
||||
.tip-text { color: #666; font-size: 26rpx; line-height: 1.6; flex: 1; }
|
||||
|
||||
.section-title { background: #fff; padding: 24rpx; font-size: 34rpx; font-weight: 700; color: #333; text-align: center; }
|
||||
.rich-wrapper { background: #fff; padding: 24rpx; }
|
||||
|
||||
/* 底部按钮 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #27c5b8;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.redeem-btn { color: #fff; font-size: 32rpx; font-weight: 700; }
|
||||
</style>
|
||||
24998
utils/areaList.js
Normal file
24998
utils/areaList.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user