Compare commits

...

113 Commits

Author SHA1 Message Date
1054425342@qq.com 5ea0848759 状态跳转 3 weeks ago
1054425342@qq.com 09904980a2 fix:更改状态 3 weeks ago
1054425342@qq.com 5020a8c59e fix:运费不通过的时候提示 3 weeks ago
zhangminghao 1a8c18092c Merge branch 'dev_des' of http://123.60.98.226:3000/jiazhipeng/EpicSoul into dev_des 4 weeks ago
zhangminghao fd9e25d41a iconup图片 4 weeks ago
1054425342@qq.com c1b591d205 Agent 4 weeks ago
1054425342@qq.com 3fd3fde8e6 clog 4 weeks ago
1054425342@qq.com a804917469 智能体 4 weeks ago
1054425342@qq.com 45d50826b9 分享 4 weeks ago
1054425342@qq.com ad1a308d47 笔记审核&&修改 4 weeks ago
1054425342@qq.com 09af5062f8 智能体替换 4 weeks ago
1054425342@qq.com 138b3bc47d 评论 4 weeks ago
1054425342@qq.com 72e6338cbe 评论 4 weeks ago
1054425342@qq.com 1951e9da29 点赞 4 weeks ago
1054425342@qq.com 9e4a60001e 点赞取消功能 4 weeks ago
1054425342@qq.com 03fefae930 修改头像 4 weeks ago
1054425342@qq.com 460743dd48 域名替换 4 weeks ago
1054425342@qq.com a665d4e9ea fix:订单 4 weeks ago
1054425342@qq.com b9d1d0100a Merge branch 'dev_des' of http://123.60.98.226:3000/jiazhipeng/EpicSoul into dev_des 4 weeks ago
1054425342@qq.com 12a4244135 fix订单 4 weeks ago
zhangminghao d6b67b3ef8 Merge branch 'dev_des' of http://123.60.98.226:3000/jiazhipeng/EpicSoul into dev_des 4 weeks ago
zhangminghao bcad6e02b6 小说文字修改 4 weeks ago
1054425342@qq.com cfd0d1432b 状态 4 weeks ago
1054425342@qq.com d765a35a34 订单 4 weeks ago
zhangminghao 11cee468e7 繁体功能 4 weeks ago
zhangminghao 7da46b930a 组件导入 4 weeks ago
1054425342@qq.com 0bfede96e3 配置 4 weeks ago
zhangminghao 28e279af9e jswenjian删除 4 weeks ago
zhangminghao d855284c2c 播放音频 1 month ago
zhangminghao 973f48eb2e 播放音频 1 month ago
1054425342@qq.com 8e5eea7ada base:地址替换 1 month ago
1054425342@qq.com 1a5b3f5042 支付 1 month ago
1054425342@qq.com 53264cb6e4 订单扭转 1 month ago
1054425342@qq.com a7fdf12ff1 feat:订单列表 1 month ago
1054425342@qq.com 7dfdd2b122 字段修改 1 month ago
1054425342@qq.com ce787c5f92 feat:购物车全选功能 1 month ago
zhangminghao a5173c37a5 Merge branch 'dev_des' of http://123.60.98.226:3000/jiazhipeng/EpicSoul into dev_des 1 month ago
zhangminghao a09e24ddcd 阅读小说功能 1 month ago
1054425342@qq.com 09290d8d06 购物车 1 month ago
1054425342@qq.com e68764777f 购物车 1 month ago
1054425342@qq.com 42149b1d87 预下单 1 month ago
1054425342@qq.com a5f6bf336e 新增标签 1 month ago
1054425342@qq.com 9a49278cde 购物车 1 month ago
1054425342@qq.com 9b45b1f6c5 feat:购物车 1 month ago
1054425342@qq.com 1c97afb5c0 积分兑换 1 month ago
1054425342@qq.com 5a8a05811b 积分 1 month ago
1054425342@qq.com 407b3114d0 段落 1 month ago
1054425342@qq.com e24a43ffc2 style 1 month ago
1054425342@qq.com 95bff6ce59 agent 1 month ago
1054425342@qq.com 3cc7deb10d 替换agent 1 month ago
1054425342@qq.com e8bf953b6b 只能听对接 1 month ago
1054425342@qq.com ff20f2b935 实物发货 1 month ago
1054425342@qq.com 78d226bcc6 海报 1 month ago
1054425342@qq.com d66e080a60 跳转退思园 1 month ago
1054425342@qq.com b068045b87 返回按钮 1 month ago
1054425342@qq.com 9514e33cca agent文件替换 1 month ago
1054425342@qq.com 8cd5aedce3 替换智能体组件 1 month ago
1054425342@qq.com 40b2f2bd55 删除 1 month ago
1054425342@qq.com 5706972ee0 合并代码 1 month ago
1054425342@qq.com e062173183 Merge branch 'master' into dev_des 1 month ago
1054425342@qq.com 6add555b45 IP合作体 1 month ago
1054425342@qq.com 908fce5bf3 到店核销 2 months ago
1054425342@qq.com 3b8d309468 style 字号调整 2 months ago
1054425342@qq.com 2416e88962 fix 2 months ago
1054425342@qq.com e65ac2b6a0 fix:isoul 2 months ago
1054425342@qq.com c3ebf719d3 style 2 months ago
1054425342@qq.com 3e370b580f 阅读体 2 months ago
1054425342@qq.com d418613a1c 阅读体 2 months ago
1054425342@qq.com 12dc7404cd 跳转agent 2 months ago
1054425342@qq.com ca86d667d5 关注列表 2 months ago
1054425342@qq.com fda0c0e377 首页 2 months ago
1054425342@qq.com 340708bbdd 首页gif替换 2 months ago
1054425342@qq.com 6cf9d9acdf 样式修改 2 months ago
1054425342@qq.com 5de45a14db 阅读体新增返回 2 months ago
1054425342@qq.com 4bcf2dd472 Merge branch 'master' into dev_des 2 months ago
1054425342@qq.com 707486b390 页面处理 2 months ago
1054425342@qq.com c35152900a 样式 2 months ago
1054425342@qq.com f889055866 假数据对接 2 months ago
1054425342@qq.com d3249c5098 样式修改 2 months ago
1054425342@qq.com 940af4ba24 时间银行 2 months ago
1054425342@qq.com 28e63d21de 时间银行 2 months ago
1054425342@qq.com 90766478be 首页 2 months ago
1054425342@qq.com 3d984d15af 首页 2 months ago
1054425342@qq.com 94e34bd3e8 数据替换 2 months ago
1054425342@qq.com 0d170b8380 灵动岛 2 months ago
1054425342@qq.com a9748db0c6 在线时间 2 months ago
1054425342@qq.com 68677fa7e5 停留时间 2 months ago
1054425342@qq.com b0e4b1b602 动态配置智能体 2 months ago
1054425342@qq.com 8e8ab6802e 跳转详情 2 months ago
1054425342@qq.com c9d860d375 合并阅读体 2 months ago
1054425342@qq.com 56b77a075a 订单逻辑判断 2 months ago
1054425342@qq.com a148b283c0 sku显示 2 months ago
1054425342@qq.com 96c4555066 订单详情 2 months ago
1054425342@qq.com fcb33a7163 订单 2 months ago
1054425342@qq.com 6bb401b366 feat:权益包兑换 2 months ago
1054425342@qq.com 4426281ca6 feat:详情字段对接 2 months ago
1054425342@qq.com 2767b35236 style 2 months ago
1054425342@qq.com d4cae0e13c 静态 2 months ago
1054425342@qq.com 79849ceeb8 Merge branch 'dev_pig' into dev_des 2 months ago
1054425342@qq.com 2dbbda432f fix 2 months ago
1054425342@qq.com 23fd3a9a8c fix 2 months ago
1054425342@qq.com 9d661fa8ca feat:订单样式 2 months ago
1054425342@qq.com 7f3c089597 路径 2 months ago
1054425342@qq.com 6544b2537f delete 3 months ago
1054425342@qq.com 5a5380710e Merge branch 'dev_pig' into dev_des 3 months ago
1054425342@qq.com 6d233d6a8d style样式优化 3 months ago
1054425342@qq.com 0098af14d3 CI:appid更改 3 months ago
1054425342@qq.com 4489fc5874 Merge branch 'master' into dev_des 3 months ago
1054425342@qq.com f5cf6a1eea style: 静态页面 3 months ago
1054425342@qq.com 7c5e7c19df feat: 时间银行 3 months ago
1054425342@qq.com 0863897eca feat: 时间银行页面静态 3 months ago
1054425342@qq.com 402de12fb7 feat:首页静态 3 months ago
1054425342@qq.com 1b67138962 feat:首页一些静态 3 months ago
  1. 1
      .vercel/project.json
  2. 296
      App.vue
  3. 253
      README_AreaPicker.md
  4. 107
      audioBook/components/AudioPlayer.vue
  5. 6905
      audioBook/components/msg copy 2.json
  6. 10575
      audioBook/components/msg copy.json
  7. 10115
      audioBook/components/msg.json
  8. 739
      audioBook/pages/index copy.vue
  9. 1070
      audioBook/pages/index.vue
  10. 18
      bmzm/chapter1/index.vue
  11. 4
      bmzm/chapter2/index.vue
  12. 4
      bmzm/chapter3/index.vue
  13. 8
      bmzm/chapter4/index.vue
  14. 16
      bmzm/chapter5/index.vue
  15. 26
      bmzm/chapter6/index.vue
  16. 22
      bmzm/chapter7/index.vue
  17. 2
      bmzm/components/NavMenu.vue
  18. 84
      bmzm/home/home.vue
  19. 55
      common/index.js
  20. 222
      components/ActivateAgentPopup.vue
  21. 429
      components/AreaPicker.vue
  22. 155
      components/AudioControl使用文档.md
  23. 97
      components/BackButton.vue
  24. 320
      components/Book.vue
  25. 2
      components/BuyPeaches.vue
  26. 121
      components/CustomTabBar.vue
  27. 931
      components/DynamicIsland.vue
  28. BIN
      components/GPT/.DS_Store
  29. 414
      components/GPT/components/client-chat.vue
  30. 232
      components/GPT/components/pending.vue
  31. 1047
      components/GPT/index.vue
  32. BIN
      components/GPT/utils/.DS_Store
  33. 347
      components/GPT/utils/ClientData.js
  34. 50
      components/GPT/utils/EventHub.js
  35. 225
      components/GPT/utils/audio.js
  36. 247
      components/GPT/utils/audio2.js
  37. 12
      components/GPT/utils/chat_constant.js
  38. 8
      components/GPT/utils/global.js
  39. 56
      components/GPT/utils/message.js
  40. 45
      components/GPT/utils/observer.js
  41. 168
      components/GPT/utils/pcm-player.js
  42. 322
      components/GPT/utils/socket.js
  43. 132
      components/GPT/utils/util.js
  44. 364
      components/IPComponents.vue
  45. 4
      components/MusicControl.vue
  46. 407
      components/ProductSection.vue
  47. 2
      components/ShareGuide.vue
  48. 293
      components/SwipeToNext使用文档.md
  49. 508
      components/WaterfallLayout.vue
  50. 288
      components/header.vue
  51. 2
      components/messagePop.vue
  52. 52
      components/nmr-icon/nmr-icon.vue
  53. 225
      components/跨页面音频控制解决方案.md
  54. 144
      components/音频背景音乐交互说明.md
  55. 1122
      libs/qqmap-wx-jssdk1.2/qqmap-wx-jssdk.js
  56. 1
      libs/qqmap-wx-jssdk1.2/qqmap-wx-jssdk.min.js
  57. 7
      main.js
  58. 12
      manifest.json
  59. 2
      mixins/myMixins.js
  60. 23
      package-lock.json
  61. 3
      package.json
  62. 280
      pages.json
  63. 47
      pages/agent/index.vue
  64. 748
      pages/index/components/FollowTab.vue
  65. 662
      pages/index/iSoul - 副本.vue
  66. 2155
      pages/index/iSoul.vue
  67. 328
      pages/index/index.vue
  68. 9
      pages/index/intelligentAgent.vue
  69. 442
      pages/index/readingBody.vue
  70. 262
      pages/index/sensoryStore - 副本.vue
  71. 136
      pages/index/sensoryStore.vue
  72. 599
      pages/index/timeShopBank.vue
  73. 347
      pages/login/login.vue
  74. 1440
      pages/notes/detail.vue
  75. 623
      pages/notes/publish.vue
  76. 19
      pages/stratIndex.vue
  77. 2
      pig/chapter1/chapter1.vue
  78. 2
      pig/chapter2/chapter2.vue
  79. 2
      pig/chapter3/art.vue
  80. 2
      pig/chapter3/brave.vue
  81. 2
      pig/chapter3/chapter3.vue
  82. 2
      pig/chapter3/humble.vue
  83. 2
      pig/chapter3/sacred.vue
  84. 2
      pig/chapter3/smart.vue
  85. 2
      pig/chapter3/stupid.vue
  86. 6
      pig/chapter4/chapter4.vue
  87. 2
      pig/chapter4/first.vue
  88. 2
      pig/chapter4/fourth.vue
  89. 2
      pig/chapter4/secBuy.vue
  90. 2
      pig/chapter4/second.vue
  91. 2
      pig/chapter4/third.vue
  92. 2
      pig/components/SideNav.vue
  93. 480
      pig/home/home.vue
  94. 2
      project.config.json
  95. 22
      static/css/icon.scss
  96. 3
      static/image/empty-cart.png
  97. 3
      static/image/heart.png
  98. 2
      static/image/search.png
  99. BIN
      static/image/tabbar/agent.png
  100. BIN
      static/image/tabbar/agent_select.png

1
.vercel/project.json

@ -0,0 +1 @@
{"projectName":"trae_l237eu51"}

296
App.vue

@ -1,41 +1,245 @@
<script>
export default {
import store from "./store";
export default {
globalData: {
mainSliderIndex: 0,
randomImages: [],
bgMusic: null,
isMusicPlaying: false,
musicSrc: 'https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3',
currentAudio: null //
musicSrc: "https://des.dayunyuanjian.cn/epicSoul/EpicSouls.mp3",
initMusicSrc: "https://des.dayunyuanjian.cn/epicSoul/EpicSouls.mp3",
// 使
userSessionId: null,
networkStartTime: null, //
networkEndTime: null, //
currentAudio: null, //
},
onLaunch: function() {
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
console.log('App Launch')
onLaunch: function () {
// 使
this.initUserUsageStats();
// 使
this.retryReportLocalStats();
//
// this.initBackgroundMusic();
//
this.Post({id: 10217},'/api/article/getArticleById').then(res => {
this.Post({ id: 10217 }, "/api/article/getArticleById").then((res) => {
try {
let SHFlag = res.data.title
let SHFlag = res.data.title;
// let SHFlag = res.data.subtitle
uni.setStorageSync('SHFlag', SHFlag)
} catch(e) {}
uni.setStorageSync("SHFlag", SHFlag);
} catch (e) {}
});
},
onShow: function() {
console.log('App Show')
onShow: function () {
//
this.recordAppShow();
this.getUserInfo();
},
onHide: function() {
console.log('App Hide')
onHide: function () {
// 退
this.recordAppHide();
},
methods: {
getUserInfo() {
if (!this.getUserId()) return;
this.Post({}, "/framework/user/getInfo", "DES").then((res) => {
let token = JSON.parse(uni.getStorageSync("userInfo")).token;
res.data.token = token;
uni.setStorageSync("userInfo", JSON.stringify(res.data));
});
},
// 使
initUserUsageStats() {
// ID
this.globalData.userSessionId = this.generateSessionId();
},
// ID
generateSessionId() {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 15);
return `session_${timestamp}_${random}`;
},
// -
recordAppShow() {
//
this.getNetworkTime()
.then((networkTime) => {
this.globalData.networkStartTime = networkTime;
})
.catch((err) => {
//
});
},
// -
recordAppHide() {
//
this.getNetworkTime()
.then((networkTime) => {
this.globalData.networkEndTime = networkTime;
// 使1
this.reportUserUsageStats();
})
.catch((err) => {
//
});
},
getUserId() {
const userInfoFromStorage = uni.getStorageSync("userInfo");
if (userInfoFromStorage) {
const userInfo = JSON.parse(userInfoFromStorage);
if (userInfo.id) {
return userInfo.id;
}
}
return store.state.user.userInfo.id;
},
// 使
reportUserUsageStats() {
if (
!this.globalData.networkStartTime ||
!this.globalData.networkEndTime
) {
return;
}
let userId = this.getUserId();
const usageData = {
sessionId: this.globalData.userSessionId,
startTime: this.globalData.networkStartTime.toString(),
endTime: this.globalData.networkEndTime.toString(),
userId: userId,
method: "POST",
};
if (!userId) {
this.saveUsageStatsToLocal(usageData);
return;
}
//
this.Post(usageData, "/api/visit/end", "DES")
.then((res) => {
//
this.clearUsageStats();
})
.catch((err) => {
//
this.saveUsageStatsToLocal(usageData);
});
},
//
getPlatform() {
// #ifdef MP-WEIXIN
return "weixin";
// #endif
// #ifdef H5
return "h5";
// #endif
// #ifdef APP-PLUS
return "app";
// #endif
return "unknown";
},
//
getNetworkTime() {
return new Promise((resolve, reject) => {
//
this.Post({}, "/api/visit/currentTime", "DES")
.then((res) => {
if (res.code == 1 || res.code == 200) {
// serverTime
const networkTime = res.data;
resolve(networkTime);
} else {
reject(new Error(res.msg || "获取网络时间失败"));
}
})
.catch((err) => {
reject(err);
});
});
},
//
getDeviceInfo() {
try {
const systemInfo = uni.getSystemInfoSync();
return {
model: systemInfo.model || "",
system: systemInfo.system || "",
platform: systemInfo.platform || "",
version: systemInfo.version || "",
};
} catch (e) {
return {};
}
},
// 使
clearUsageStats() {
this.globalData.networkStartTime = null;
this.globalData.networkEndTime = null;
this.globalData.userSessionId = null;
},
// 使
saveUsageStatsToLocal(usageData) {
try {
const localStats = uni.getStorageSync("pendingUsageStats") || [];
localStats.push(usageData);
uni.setStorageSync("pendingUsageStats", localStats);
} catch (e) {
//
}
},
// 使
retryReportLocalStats() {
try {
const localStats = uni.getStorageSync("pendingUsageStats") || [];
if (localStats.length === 0) {
return;
}
let userId = this.getUserId();
if (!userId) {
return;
}
//
localStats.forEach((stats, index) => {
stats.userId = userId;
this.Post(stats, "/api/visit/end", "DES")
.then((res) => {
//
localStats.splice(index, 1);
uni.setStorageSync("pendingUsageStats", localStats);
})
.catch((err) => {
//
});
});
} catch (e) {
//
}
},
initBackgroundMusic() {
try {
console.log('bgMusic',this.globalData.bgMusic)
console.log("bgMusic", this.globalData.bgMusic);
//
if (this.globalData.bgMusic) {
console.log("销毁bgMusic");
this.globalData.bgMusic.stop();
this.globalData.bgMusic.destroy()
this.globalData.bgMusic.destroy();
this.globalData.bgMusic = null;
}
@ -62,7 +266,7 @@
//
bgMusic.src = this.globalData.musicSrc;
console.log(bgMusic.src)
console.log(bgMusic.src);
bgMusic.loop = true; //
// 使
@ -132,10 +336,10 @@
}
return this.globalData.isMusicPlaying;
},
isPlaying: () => this.globalData.isMusicPlaying
isPlaying: () => this.globalData.isMusicPlaying,
};
} catch (err) {
console.error('初始化背景音乐失败:', err);
console.error("初始化背景音乐失败:", err);
}
},
updateMusicSrc(newSrc) {
@ -143,36 +347,40 @@
if (this.globalData.bgMusic) {
this.globalData.bgMusic.src = newSrc;
}
}
}
}
},
},
};
</script>
<style lang="scss">
/*每个页面公共css */
@import '@/uni_modules/uni-scss/index.scss';
@import "@/static/css/base.css";
@import "@/static/css/icon.scss";
@font-face {
font-family: "Futura";
src: url(https://des.dayunyuanjian.cn/epicSoul/taozi/fonts/Futura.ttc);
}
/*每个页面公共css */
@import "@/uni_modules/uni-scss/index.scss";
@import "@/static/css/base.css";
/* #ifndef APP-NVUE */
//
page {
/* #ifndef APP-NVUE */
//
page {
background-color: #f5f5f5;
}
}
/* #endif */
.example-info {
/* #endif */
.example-info {
font-size: 14px;
color: #333;
padding: 10px;
}
}
/* 清除按钮默认样式 */
button::after {
/* 清除按钮默认样式 */
button::after {
border: none;
}
@keyframes bounce {
}
@keyframes bounce {
0%,
20%,
50%,
@ -188,10 +396,10 @@
60% {
transform: translateY(-10rpx);
}
}
}
/* 音乐控制按钮动画 */
@keyframes rotate {
/* 音乐控制按钮动画 */
@keyframes rotate {
from {
transform: rotate(0deg);
}
@ -199,10 +407,10 @@
to {
transform: rotate(360deg);
}
}
}
/* 隐藏微信小程序默认音频组件 */
#mp-audio {
/* 隐藏微信小程序默认音频组件 */
#mp-audio {
display: none;
}
}
</style>

253
README_AreaPicker.md

@ -0,0 +1,253 @@
# 省市区选择组件使用说明
## 概述
`AreaPicker.vue` 是一个功能完整的省市区三级联动选择组件,支持自定义样式插槽,可以灵活地集成到各种页面中。
## 功能特性
- ✅ 省市区三级联动选择
- ✅ 支持默认值设置
- ✅ 支持插槽自定义显示样式
- ✅ 完整的事件回调
- ✅ 灵活的数据请求方式
- ✅ 错误处理和容错机制
## Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| placeholder | String | '请选择' | 占位符文本 |
| defaultValue | Object | null | 默认选中值,格式:`{ provinceId, cityId, areaId }` |
| disabled | Boolean | false | 是否禁用 |
## Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| change | data | 选择改变时触发,返回选中的省市区信息 |
### change 事件返回数据格式
```javascript
{
provinceId: '110000', // 省份ID
cityId: '110100', // 城市ID
areaId: '110101', // 区域ID
fullText: '北京市北京市东城区' // 完整地址文本
}
```
## Methods
| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| getValue | - | Object | 获取当前选中的值 |
| reset | - | - | 重置选择 |
## 插槽使用
组件提供了默认插槽,允许完全自定义显示样式。插槽提供以下数据:
| 插槽参数 | 类型 | 说明 |
|----------|------|------|
| selectedText | String | 当前选中的完整文本 |
| placeholder | String | 占位符文本 |
| provinceData | Array | 省份数据列表 |
| cityData | Array | 城市数据列表 |
| areaData | Array | 区域数据列表 |
| multiIndex | Array | 当前选中的索引数组 |
| currentSelection | Object | 当前选中的详细信息 |
### currentSelection 对象结构
```javascript
{
province: { id: '110000', name: '北京市' },
city: { id: '110100', name: '北京市' },
area: { id: '110101', name: '东城区' },
fullText: '北京市北京市东城区'
}
```
## 使用示例
### 基本使用
```vue
<template>
<view>
<!-- 使用默认样式 -->
<AreaPicker
ref="areaPicker"
placeholder="请选择省市区"
:defaultValue="{ provinceId: '110000', cityId: '110100', areaId: '110101' }"
@change="onAreaChange"
/>
</view>
</template>
<script>
export default {
methods: {
onAreaChange(data) {
console.log('选中的地区:', data)
}
}
}
</script>
```
### 自定义样式(插槽)
```vue
<template>
<view>
<!-- 自定义样式1:简单自定义 -->
<AreaPicker
placeholder="请选择省市区"
@change="onAreaChange"
>
<template v-slot="{ selectedText, placeholder }">
<view class="custom-display">
<text class="icon">📍</text>
<text class="text">{{ selectedText || placeholder }}</text>
<text class="arrow"></text>
</view>
</template>
</AreaPicker>
<!-- 自定义样式2:显示详细信息 -->
<AreaPicker
placeholder="请选择省市区"
@change="onAreaChange"
>
<template v-slot="{ selectedText, placeholder, currentSelection }">
<view class="detail-display">
<view v-if="currentSelection.province" class="detail-info">
<text>省:{{ currentSelection.province.name }}</text>
<text>市:{{ currentSelection.city.name }}</text>
<text>区:{{ currentSelection.area.name }}</text>
</view>
<text v-else class="placeholder">{{ placeholder }}</text>
</view>
</template>
</AreaPicker>
</view>
</template>
<script>
export default {
methods: {
onAreaChange(data) {
console.log('选中的地区:', data)
}
}
}
</script>
<style>
.custom-display {
display: flex;
align-items: center;
padding: 12px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 25px;
color: white;
}
.icon {
margin-right: 10px;
}
.text {
flex: 1;
}
.arrow {
margin-left: 10px;
}
.detail-display {
padding: 15px;
background-color: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 8px;
}
.detail-info {
display: flex;
flex-direction: column;
gap: 5px;
}
.placeholder {
color: #adb5bd;
font-style: italic;
}
</style>
```
### 在 header 组件中使用
```vue
<template>
<view>
<header
:isSearch="true"
:isAreaPicker="true"
areaPlaceholder="请选择省市区"
:defaultAreaValue="{ provinceId: '110000', cityId: '110100', areaId: '110101' }"
@areaChange="onAreaChange"
/>
</view>
</template>
<script>
export default {
methods: {
onAreaChange(data) {
console.log('选中的地区:', data)
}
}
}
</script>
```
## 数据接口要求
组件需要后端提供省市区数据接口,接口返回格式如下:
```javascript
// 省份数据
[
{ id: '110000', name: '北京市' },
{ id: '120000', name: '天津市' },
// ...
]
// 城市数据(根据省份ID获取)
[
{ id: '110100', name: '北京市', pid: '110000' },
// ...
]
// 区域数据(根据城市ID获取)
[
{ id: '110101', name: '东城区', pid: '110100' },
{ id: '110102', name: '西城区', pid: '110100' },
// ...
]
```
## 注意事项
1. 组件会自动处理数据请求的多种方式(父组件 Post 方法、全局 Post 方法、uni.request)
2. 建议在使用前确保数据接口正常可用
3. 插槽内容会完全替换默认的显示样式
4. 可以通过 ref 调用组件的 getValue() 和 reset() 方法
5. 组件支持设置默认值,会自动回显对应的省市区信息
## 完整示例
查看 `AreaPickerSlotExample.vue` 文件获取完整的使用示例,包含多种自定义样式的演示。

107
audioBook/components/AudioPlayer.vue

@ -0,0 +1,107 @@
<template>
<div></div>
</template>
<script>
export default {
name: 'AudioPlayer',
props: ['src'],
data() {
return {
backgroundAudioManager: null,
currentTime: 0,
duration: 0,
timeUpdateTimer: null
}
},
mounted() {
this.backgroundAudioManager = wx.getBackgroundAudioManager()
//
this.backgroundAudioManager.title = '有声电子书'
this.backgroundAudioManager.epname = '章节播放'
this.backgroundAudioManager.singer = 'EpicSoul'
this.backgroundAudioManager.coverImgUrl = ''
//
this.backgroundAudioManager.onPlay(() => {
this.startTimeUpdate()
this.$emit('play')
})
//
this.backgroundAudioManager.onPause(() => {
this.stopTimeUpdate()
this.$emit('pause')
})
//
this.backgroundAudioManager.onStop(() => {
this.stopTimeUpdate()
this.$emit('pause')
})
//
this.backgroundAudioManager.onEnded(() => {
this.stopTimeUpdate()
this.$emit('ended')
})
//
this.backgroundAudioManager.onError((err) => {
console.error('背景音频播放错误:', err)
this.stopTimeUpdate()
})
//
if (this.src) {
this.backgroundAudioManager.src = this.src
}
},
methods: {
play() {
if (this.backgroundAudioManager.src) {
this.backgroundAudioManager.play()
}
},
pause() {
this.backgroundAudioManager.pause()
},
seek(time) {
this.backgroundAudioManager.seek(time)
},
startTimeUpdate() {
this.stopTimeUpdate()
this.timeUpdateTimer = setInterval(() => {
this.currentTime = this.backgroundAudioManager.currentTime || 0
this.duration = this.backgroundAudioManager.duration || 0
this.$emit('timeupdate', {
currentTime: this.currentTime,
duration: this.duration
})
}, 100)
},
stopTimeUpdate() {
if (this.timeUpdateTimer) {
clearInterval(this.timeUpdateTimer)
this.timeUpdateTimer = null
}
}
},
watch: {
src(newSrc) {
if (newSrc) {
this.backgroundAudioManager.src = newSrc
}
}
},
beforeDestroy() {
this.stopTimeUpdate()
// backgroundAudioManager
}
}
</script>
<style scoped>
/* AudioPlayer组件样式 */
</style>

6905
audioBook/components/msg copy 2.json

File diff suppressed because one or more lines are too long

10575
audioBook/components/msg copy.json

File diff suppressed because one or more lines are too long

10115
audioBook/components/msg.json

File diff suppressed because one or more lines are too long

739
audioBook/pages/index copy.vue

@ -0,0 +1,739 @@
<template>
<view class="container">
<!-- 顶部标题栏 -->
<!-- 主要内容区域 -->
<!-- 文本显示区域 -->
<scroll-view
class="content"
:show-scrollbar="false"
enhanced
scroll-y
:scroll-top="scrollTop"
scroll-with-animation
:style="{ height: scrollViewHeight + 'px' }"
>
<view class="content-wrapper">
<view class="article-content">
<text
v-for="(char, index) in currentChapter.characters"
:key="index"
class="character"
:class="{ active: index === activeCharIndex }"
>
{{ char.Text }}
</text>
</view>
</view>
</scroll-view>
<!-- 底部控制栏 -->
<view class="controls">
<!-- 章节信息栏 -->
<view class="chapter-info-bar">
<text class="chapter-title">{{ currentChapter.title }}</text>
<text class="chapter-count">
{{ currentChapterIndex + 1 }}/{{ chapters.length }}
</text>
</view>
<!-- 进度条区域 -->
<view class="progress-section">
<text class="current-time">{{ formatTime(currentTime) }}</text>
<view class="progress-container">
<view class="progress-bar" @click="seek">
<view class="progress" :style="{ width: progress + '%' }"></view>
<view
class="progress-thumb"
:style="{ left: progress + '%' }"
></view>
</view>
</view>
<text class="duration">{{ formatTime(duration) }}</text>
</view>
<!-- 控制按钮区域 -->
<view class="control-buttons">
<!-- 上一章按钮 -->
<button
class="control-btn prev-btn"
@click="prevChapter"
:disabled="currentChapterIndex === 0"
>
<text class="control-icon"></text>
</button>
<!-- 播放/暂停按钮 -->
<button class="play-btn" @click="togglePlay">
<text class="play-icon">{{ playing ? "⏸️" : "▶️" }}</text>
</button>
<!-- 下一章按钮 -->
<button
class="control-btn next-btn"
@click="nextChapter"
:disabled="currentChapterIndex === chapters.length - 1"
>
<text class="control-icon"></text>
</button>
</view>
</view>
<AudioPlayer
ref="audioPlayer"
:src="currentChapter.audioUrl"
@timeupdate="onTimeUpdate"
@ended="onEnded"
></AudioPlayer>
</view>
</template>
<script>
// uniapp使audio使innerAudioContext
import AudioPlayer from "../components/AudioPlayer.vue";
export default {
components: {
AudioPlayer,
},
data() {
return {
chapters: [
{
title: "第一章:人工智能的崛起",
audioUrl: "https://des.dayunyuanjian.cn/epicSoul/EpicSouls.mp3",
//
characters: [
{"BeginIndex": 0, "BeginTime": 0, "EndIndex": 1, "EndTime": 500, "Phoneme": "ren2", "Text": "人"},
{"BeginIndex": 1, "BeginTime": 500, "EndIndex": 2, "EndTime": 1000, "Phoneme": "gong1", "Text": "工"},
{"BeginIndex": 2, "BeginTime": 1000, "EndIndex": 3, "EndTime": 1500, "Phoneme": "zhi4", "Text": "智"},
{"BeginIndex": 3, "BeginTime": 1500, "EndIndex": 4, "EndTime": 2000, "Phoneme": "neng2", "Text": "能"},
{"BeginIndex": 4, "BeginTime": 2000, "EndIndex": 5, "EndTime": 2200, "Phoneme": "", "Text": "的"},
{"BeginIndex": 5, "BeginTime": 2200, "EndIndex": 6, "EndTime": 2700, "Phoneme": "fa1", "Text": "发"},
{"BeginIndex": 6, "BeginTime": 2700, "EndIndex": 7, "EndTime": 3200, "Phoneme": "zhan3", "Text": "展"},
{"BeginIndex": 7, "BeginTime": 3200, "EndIndex": 8, "EndTime": 3700, "Phoneme": "li4", "Text": "历"},
{"BeginIndex": 8, "BeginTime": 3700, "EndIndex": 9, "EndTime": 4200, "Phoneme": "cheng2", "Text": "程"},
{"BeginIndex": 9, "BeginTime": 4200, "EndIndex": 10, "EndTime": 4400, "Phoneme": "", "Text": "。"},
{"BeginIndex": 10, "BeginTime": 5500, "EndIndex": 11, "EndTime": 6000, "Phoneme": "cong2", "Text": "从"},
{"BeginIndex": 11, "BeginTime": 6000, "EndIndex": 12, "EndTime": 6500, "Phoneme": "yi1", "Text": "1"},
{"BeginIndex": 12, "BeginTime": 6500, "EndIndex": 13, "EndTime": 7000, "Phoneme": "jiu3", "Text": "9"},
{"BeginIndex": 13, "BeginTime": 7000, "EndIndex": 14, "EndTime": 7500, "Phoneme": "wu3", "Text": "5"},
{"BeginIndex": 14, "BeginTime": 7500, "EndIndex": 15, "EndTime": 8000, "Phoneme": "ling2", "Text": "0"},
{"BeginIndex": 15, "BeginTime": 8000, "EndIndex": 16, "EndTime": 8500, "Phoneme": "nian2", "Text": "年"},
{"BeginIndex": 16, "BeginTime": 8500, "EndIndex": 17, "EndTime": 9000, "Phoneme": "dai4", "Text": "代"},
{"BeginIndex": 17, "BeginTime": 9000, "EndIndex": 18, "EndTime": 9500, "Phoneme": "kai1", "Text": "开"},
{"BeginIndex": 18, "BeginTime": 9500, "EndIndex": 19, "EndTime": 10000, "Phoneme": "shi3", "Text": "始"},
{"BeginIndex": 19, "BeginTime": 10000, "EndIndex": 20, "EndTime": 10200, "Phoneme": "", "Text": ","},
{"BeginIndex": 20, "BeginTime": 10200, "EndIndex": 21, "EndTime": 10700, "Phoneme": "ren2", "Text": "人"},
{"BeginIndex": 21, "BeginTime": 10700, "EndIndex": 22, "EndTime": 11200, "Phoneme": "gong1", "Text": "工"},
{"BeginIndex": 22, "BeginTime": 11200, "EndIndex": 23, "EndTime": 11700, "Phoneme": "zhi4", "Text": "智"},
{"BeginIndex": 23, "BeginTime": 11700, "EndIndex": 24, "EndTime": 12200, "Phoneme": "neng2", "Text": "能"},
{"BeginIndex": 24, "BeginTime": 12200, "EndIndex": 25, "EndTime": 12700, "Phoneme": "zuo4", "Text": "作"},
{"BeginIndex": 25, "BeginTime": 12700, "EndIndex": 26, "EndTime": 13200, "Phoneme": "wei2", "Text": "为"},
{"BeginIndex": 26, "BeginTime": 13200, "EndIndex": 27, "EndTime": 13700, "Phoneme": "yi1", "Text": "一"},
{"BeginIndex": 27, "BeginTime": 13700, "EndIndex": 28, "EndTime": 14200, "Phoneme": "men2", "Text": "门"},
{"BeginIndex": 28, "BeginTime": 14200, "EndIndex": 29, "EndTime": 14700, "Phoneme": "xue2", "Text": "学"},
{"BeginIndex": 29, "BeginTime": 14700, "EndIndex": 30, "EndTime": 15200, "Phoneme": "ke1", "Text": "科"},
{"BeginIndex": 30, "BeginTime": 15200, "EndIndex": 31, "EndTime": 15700, "Phoneme": "zheng4", "Text": "正"},
{"BeginIndex": 31, "BeginTime": 15700, "EndIndex": 32, "EndTime": 16200, "Phoneme": "shi4", "Text": "式"},
{"BeginIndex": 32, "BeginTime": 16200, "EndIndex": 33, "EndTime": 16700, "Phoneme": "dan4", "Text": "诞"},
{"BeginIndex": 33, "BeginTime": 16700, "EndIndex": 34, "EndTime": 17200, "Phoneme": "sheng1", "Text": "生"},
{"BeginIndex": 34, "BeginTime": 17200, "EndIndex": 35, "EndTime": 17400, "Phoneme": "", "Text": "。"}
],
content: `人工智能的发展历程。从1950年代开始,人工智能作为一门学科正式诞生。`,
},
{
title: "第二章:科技与人文的融合",
audioUrl: "https://des.dayunyuanjian.cn/epicSoul/EpicSouls.mp3",
//
characters: [
{"BeginIndex": 0, "BeginTime": 0, "EndIndex": 1, "EndTime": 500, "Phoneme": "ke1", "Text": "科"},
{"BeginIndex": 1, "BeginTime": 500, "EndIndex": 2, "EndTime": 1000, "Phoneme": "ji4", "Text": "技"},
{"BeginIndex": 2, "BeginTime": 1000, "EndIndex": 3, "EndTime": 1500, "Phoneme": "fa1", "Text": "发"},
{"BeginIndex": 3, "BeginTime": 1500, "EndIndex": 4, "EndTime": 2000, "Phoneme": "zhan3", "Text": "展"},
{"BeginIndex": 4, "BeginTime": 2000, "EndIndex": 5, "EndTime": 2500, "Phoneme": "yu3", "Text": "与"},
{"BeginIndex": 5, "BeginTime": 2500, "EndIndex": 6, "EndTime": 3000, "Phoneme": "ren2", "Text": "人"},
{"BeginIndex": 6, "BeginTime": 3000, "EndIndex": 7, "EndTime": 3500, "Phoneme": "wen2", "Text": "文"},
{"BeginIndex": 7, "BeginTime": 3500, "EndIndex": 8, "EndTime": 4000, "Phoneme": "guan1", "Text": "关"},
{"BeginIndex": 8, "BeginTime": 4000, "EndIndex": 9, "EndTime": 4500, "Phoneme": "huai2", "Text": "怀"},
{"BeginIndex": 9, "BeginTime": 4500, "EndIndex": 10, "EndTime": 5000, "Phoneme": "de5", "Text": "的"},
{"BeginIndex": 10, "BeginTime": 5000, "EndIndex": 11, "EndTime": 5500, "Phoneme": "ping2", "Text": "平"},
{"BeginIndex": 11, "BeginTime": 5500, "EndIndex": 12, "EndTime": 6000, "Phoneme": "heng2", "Text": "衡"},
{"BeginIndex": 12, "BeginTime": 6000, "EndIndex": 13, "EndTime": 6200, "Phoneme": "", "Text": "。"}
],
content: `科技发展与人文关怀的平衡。`,
},
{
title: "第三章:未来社会的展望",
audioUrl: "https://des.dayunyuanjian.cn/epicSoul/EpicSouls.mp3",
//
characters: [
{"BeginIndex": 0, "BeginTime": 0, "EndIndex": 1, "EndTime": 500, "Phoneme": "zhan3", "Text": "展"},
{"BeginIndex": 1, "BeginTime": 500, "EndIndex": 2, "EndTime": 1000, "Phoneme": "wang4", "Text": "望"},
{"BeginIndex": 2, "BeginTime": 1000, "EndIndex": 3, "EndTime": 1500, "Phoneme": "wei4", "Text": "未"},
{"BeginIndex": 3, "BeginTime": 1500, "EndIndex": 4, "EndTime": 2000, "Phoneme": "lai2", "Text": "来"},
{"BeginIndex": 4, "BeginTime": 2000, "EndIndex": 5, "EndTime": 2500, "Phoneme": "she4", "Text": "社"},
{"BeginIndex": 5, "BeginTime": 2500, "EndIndex": 6, "EndTime": 3000, "Phoneme": "hui4", "Text": "会"},
{"BeginIndex": 6, "BeginTime": 3000, "EndIndex": 7, "EndTime": 3500, "Phoneme": "de5", "Text": "的"},
{"BeginIndex": 7, "BeginTime": 3500, "EndIndex": 8, "EndTime": 4000, "Phoneme": "fa1", "Text": "发"},
{"BeginIndex": 8, "BeginTime": 4000, "EndIndex": 9, "EndTime": 4500, "Phoneme": "zhan3", "Text": "展"},
{"BeginIndex": 9, "BeginTime": 4500, "EndIndex": 10, "EndTime": 5000, "Phoneme": "qu1", "Text": "趋"},
{"BeginIndex": 10, "BeginTime": 5000, "EndIndex": 11, "EndTime": 5500, "Phoneme": "shi4", "Text": "势"},
{"BeginIndex": 11, "BeginTime": 5500, "EndIndex": 12, "EndTime": 5700, "Phoneme": "", "Text": "。"}
],
content: `展望未来社会的发展趋势。`,
},
],
currentChapterIndex: 0,
playing: false,
currentTime: 0,
duration: 0,
activeCharIndex: -1,
scrollTop: 0,
scrollViewHeight: 0,
};
},
mounted() {
this.syncAudioState();
this.calculateScrollViewHeight();
},
onShow() {
//
this.syncAudioState();
},
onHide() {
//
//
},
computed: {
currentChapter() {
const chapter = this.chapters[this.currentChapterIndex];
// 使
const characters = chapter.characters.map((char) => {
return {
...char,
timeInSeconds: char.BeginTime / 1000, //
};
});
return {
...chapter,
characters: characters,
};
},
progress() {
return (this.currentTime / this.duration) * 100 || 0;
},
currentTimeFormatted() {
return this.formatTime(this.currentTime);
},
durationFormatted() {
return this.formatTime(this.duration);
},
},
methods: {
togglePlay() {
if (this.playing) {
this.$refs.audioPlayer.pause();
this.playing = false;
} else {
this.$refs.audioPlayer.play();
this.playing = true;
}
},
prevChapter() {
if (this.currentChapterIndex > 0) {
this.currentChapterIndex--;
this.resetPlayback();
}
},
nextChapter() {
if (this.currentChapterIndex < this.chapters.length - 1) {
this.currentChapterIndex++;
this.resetPlayback();
}
},
resetPlayback() {
this.playing = false;
this.currentTime = 0;
this.duration = 0;
this.activeCharIndex = -1;
this.scrollTop = 0;
//
const currentChapter = this.chapters[this.currentChapterIndex];
if (
this.$refs.audioPlayer &&
this.$refs.audioPlayer.backgroundAudioManager
) {
const bgAudio = this.$refs.audioPlayer.backgroundAudioManager;
bgAudio.title = currentChapter.title;
bgAudio.epname = `${this.currentChapterIndex + 1}`;
bgAudio.src = currentChapter.audioUrl;
//
bgAudio.currentTime = 0;
// seek
if (this.$refs.audioPlayer.seek) {
this.$refs.audioPlayer.seek(0);
}
}
},
onTimeUpdate(e) {
this.currentTime = e.currentTime;
this.duration = e.duration;
//
this.playing = !e.paused;
//
const characters = this.currentChapter.characters;
const currentTimeMs = this.currentTime * 1000; //
for (let i = characters.length - 1; i >= 0; i--) {
if (currentTimeMs >= characters[i].BeginTime && currentTimeMs <= characters[i].EndTime) {
if (this.activeCharIndex !== i) {
this.activeCharIndex = i;
// 30rpx
this.scrollTop = Math.floor(i / 20) * 30; // 20
}
break;
}
}
},
onEnded() {
this.playing = false;
//
if (this.currentChapterIndex < this.chapters.length - 1) {
this.nextChapter();
setTimeout(() => {
this.$refs.audioPlayer.play();
this.playing = true;
}, 500);
}
},
seek(e) {
// 使uni.createSelectorQuery
const query = uni.createSelectorQuery().in(this);
query
.select(".progress-bar")
.boundingClientRect((rect) => {
if (rect) {
const clickX = e.detail.x - rect.left;
const progressPercent = clickX / rect.width;
const seekTime = progressPercent * this.duration;
if (seekTime >= 0 && seekTime <= this.duration) {
this.$refs.audioPlayer.seek(seekTime);
}
}
})
.exec();
},
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, "0")}:${secs
.toString()
.padStart(2, "0")}`;
},
syncAudioState() {
//
if (
this.$refs.audioPlayer &&
this.$refs.audioPlayer.backgroundAudioManager
) {
const bgAudio = this.$refs.audioPlayer.backgroundAudioManager;
//
if (bgAudio.src) {
// - pausedtruefalse
this.playing = !bgAudio.paused;
//
this.currentTime = bgAudio.currentTime || 0;
this.duration = bgAudio.duration || 0;
//
if (this.playing) {
this.$refs.audioPlayer.startTimeUpdate();
}
} else {
//
this.playing = false;
this.currentTime = 0;
this.duration = 0;
}
}
},
calculateScrollViewHeight() {
//
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
// 300rpxpx
const controlsHeight = 300 * (systemInfo.windowWidth / 750);
//
this.scrollViewHeight = windowHeight - controlsHeight - 60; // 60px
},
},
};
</script>
<style>
.container {
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
padding: 30rpx;
position: relative;
}
/* 顶部标题栏样式 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 48rpx;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-bottom: 2rpx solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4rpx 40rpx rgba(0, 0, 0, 0.1);
}
.header-content {
flex: 1;
}
.title {
font-size: 48rpx;
font-weight: 700;
color: #2d3748;
margin-bottom: 8rpx;
}
.subtitle {
font-size: 32rpx;
color: #718096;
font-weight: 500;
}
.chapter-info {
background: linear-gradient(135deg, #667eea, #764ba2);
padding: 16rpx 32rpx;
border-radius: 40rpx;
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
}
.chapter-count {
color: white;
font-size: 28rpx;
font-weight: 600;
}
.content {
width: 690rpx;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20rpx);
border-radius: 32rpx;
margin: 0 auto;
margin-bottom: 20rpx;
}
.content-wrapper {
padding: 48rpx;
}
.article-content {
line-height: 1.8;
font-size: 32rpx;
color: #2d3748;
text-align: justify;
}
.character {
transition: all 0.3s ease;
padding: 2rpx 4rpx;
border-radius: 6rpx;
display: inline;
margin: 0 1rpx;
}
.character.active {
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.25),
rgba(118, 75, 162, 0.25)
);
color: #667eea;
font-weight: 700;
box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.3);
transform: scale(1.1);
}
/* 底部控制栏样式 */
.controls {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(40rpx);
border-top: 2rpx solid rgba(255, 255, 255, 0.3);
box-shadow: 0 -16rpx 64rpx rgba(0, 0, 0, 0.12),
0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
border-radius: 48rpx 48rpx 0 0;
padding: 32rpx 40rpx 40rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
}
/* 章节信息栏 */
.chapter-info-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 8rpx;
}
.chapter-title {
font-size: 28rpx;
font-weight: 600;
color: #2d3748;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 16rpx;
}
.chapter-count {
font-size: 24rpx;
color: #718096;
font-weight: 500;
background: rgba(139, 92, 246, 0.1);
padding: 8rpx 16rpx;
border-radius: 20rpx;
backdrop-filter: blur(10rpx);
}
/* 进度条区域 */
.progress-section {
display: flex;
align-items: center;
gap: 20rpx;
}
.current-time,
.duration {
font-size: 24rpx;
color: #718096;
font-weight: 500;
font-family: "Courier New", monospace;
min-width: 80rpx;
text-align: center;
}
/* 控制按钮区域 */
.control-buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 32rpx;
}
/* 播放按钮 */
.play-btn {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
border: none;
background: linear-gradient(135deg, #8b5cf6, #a855f7);
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 12rpx 40rpx rgba(139, 92, 246, 0.4),
0 6rpx 20rpx rgba(0, 0, 0, 0.15);
position: relative;
overflow: hidden;
z-index: 2;
}
/* 控制按钮(上一章/下一章) */
.control-btn {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
border: none;
background: rgba(139, 92, 246, 0.08);
color: #8b5cf6;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4rpx 16rpx rgba(139, 92, 246, 0.15);
backdrop-filter: blur(20rpx);
position: relative;
overflow: hidden;
}
.play-btn::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.2),
rgba(255, 255, 255, 0.05)
);
border-radius: 50%;
opacity: 0;
transition: opacity 0.3s ease;
}
.play-btn:hover {
transform: scale(1.08);
box-shadow: 0 18rpx 56rpx rgba(139, 92, 246, 0.5),
0 9rpx 28rpx rgba(0, 0, 0, 0.2);
}
.play-btn:hover::before {
opacity: 1;
}
.play-btn:active {
transform: scale(0.96);
transition: transform 0.1s ease;
}
/* 进度条容器 */
.progress-container {
flex: 1;
position: relative;
}
.progress-bar {
height: 12rpx;
background: rgba(139, 92, 246, 0.12);
border-radius: 6rpx;
position: relative;
cursor: pointer;
overflow: hidden;
backdrop-filter: blur(20rpx);
box-shadow: inset 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
.progress {
height: 100%;
background: linear-gradient(90deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
border-radius: 6rpx;
transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4rpx 16rpx rgba(139, 92, 246, 0.3);
position: relative;
}
.progress::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.3) 0%,
rgba(255, 255, 255, 0.1) 100%
);
border-radius: 6rpx;
}
.progress-thumb {
position: absolute;
top: 50%;
width: 28rpx;
height: 28rpx;
background: white;
border: 4rpx solid #8b5cf6;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 6rpx 20rpx rgba(139, 92, 246, 0.4),
0 3rpx 6rpx rgba(0, 0, 0, 0.1);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.progress-thumb:hover {
transform: translate(-50%, -50%) scale(1.2);
box-shadow: 0 8rpx 24rpx rgba(139, 92, 246, 0.5),
0 4rpx 8rpx rgba(0, 0, 0, 0.15);
}
/* 控制按钮交互效果 */
.control-btn::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(139, 92, 246, 0.1),
rgba(168, 85, 247, 0.05)
);
border-radius: 50%;
opacity: 0;
transition: opacity 0.3s ease;
}
.control-btn:hover {
background: rgba(139, 92, 246, 0.15);
transform: scale(1.1);
box-shadow: 0 8rpx 32rpx rgba(139, 92, 246, 0.25);
}
.control-btn:hover::before {
opacity: 1;
}
.control-btn:active {
transform: scale(0.95);
transition: transform 0.1s ease;
}
.control-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
transform: none;
box-shadow: 0 2rpx 6rpx rgba(139, 92, 246, 0.1);
}
.control-btn:disabled:hover {
background: rgba(139, 92, 246, 0.08);
transform: none;
box-shadow: 0 2rpx 6rpx rgba(139, 92, 246, 0.1);
}
.control-btn:disabled::before {
opacity: 0;
}
/* 控制图标样式 */
.control-icon {
font-size: 32rpx;
line-height: 1;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.1));
}
.play-icon {
font-size: 42rpx;
line-height: 1;
margin-left: 3rpx;
filter: drop-shadow(0 3rpx 6rpx rgba(0, 0, 0, 0.2));
}
</style>

1070
audioBook/pages/index.vue

File diff suppressed because it is too large

18
bmzm/chapter1/index.vue

@ -13,13 +13,13 @@
</template>
<!-- 视频 -->
<template v-if="index == 3">
<video src="https://static.ticket.sz-trip.com/epicSoul/bmzm.mp4" @play="handleVideoPlay"
<video src="https://des.dayunyuanjian.cn/epicSoul/bmzm.mp4" @play="handleVideoPlay"
@pause="handleVideoPause" @ended="handleVideoEnded" style="width: 100vw;height: 30vh;"
objectFit="cover"></video>
</template>
<!-- 动图 -->
<template v-if="index == 8">
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img7s.gif" mode="widthFix"
<image src="https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter1/img7s.gif" mode="widthFix"
style="width: 100vw;"></image>
</template>
</view>
@ -27,7 +27,7 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
@ -55,23 +55,23 @@
},
// swiper-item
swiperItems: [{
imageUrl: `https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index1.png`
imageUrl: `https://des.dayunyuanjian.cn/epicSoul/bmzm/index/index1.png`
},
{
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index5.png'
imageUrl: 'https://des.dayunyuanjian.cn/epicSoul/bmzm/index/index5.png'
},
//
...Array.from({
length: 9
}, (_, i) => ({
imageUrl: `https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img${i + 1}.png`
imageUrl: `https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter1/img${i + 1}.png`
})),
{
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img10.png',
imageUrl: 'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter1/img10.png',
images: Array.from({
length: 4
}, (_, i) => ({
src: `https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img10-${i + 1}.png`
src: `https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter1/img10-${i + 1}.png`
})),
link: '/bmzm/chapter2/index?index={index}',
linkIndex: 11
@ -84,7 +84,7 @@
this.initialIndex = option.index;
// swiper-item
this.swiperItems[0].imageUrl =
`https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index${this.initialIndex}.png`;
`https://des.dayunyuanjian.cn/epicSoul/bmzm/index/index${this.initialIndex}.png`;
},
methods: {
touchmove() {

4
bmzm/chapter2/index.vue

@ -10,7 +10,7 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
@ -68,7 +68,7 @@ export default {
if (typeof path === 'object') {
path = path.url;
}
return `https://static.ticket.sz-trip.com/epicSoul/bmzm/${path}`;
return `https://des.dayunyuanjian.cn/epicSoul/bmzm/${path}`;
}
}
};

4
bmzm/chapter3/index.vue

@ -29,7 +29,7 @@
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
@ -122,7 +122,7 @@ export default {
if (typeof path === 'object') {
path = path.url;
}
return `https://static.ticket.sz-trip.com/epicSoul/bmzm/${path}`;
return `https://des.dayunyuanjian.cn/epicSoul/bmzm/${path}`;
}
}
};

8
bmzm/chapter4/index.vue

@ -2,24 +2,24 @@
<view>
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange">
<swiper-item>
<view class="swiper-item" :style="{backgroundImage: `url(https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter2/img${initialIndex}.png)`}">
<view class="swiper-item" :style="{backgroundImage: `url(https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter2/img${initialIndex}.png)`}">
</view>
</swiper-item>
<swiper-item>
<view class="swiper-item" :style="{backgroundImage: 'url(https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter2/img15.png)'}">
<view class="swiper-item" :style="{backgroundImage: 'url(https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter2/img15.png)'}">
</view>
</swiper-item>
<!-- 第三章 -->
<swiper-item>
<view class="swiper-item" :style="{backgroundImage: 'url(https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter3/img1.png)'}"
<view class="swiper-item" :style="{backgroundImage: 'url(https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter3/img1.png)'}"
@click="gotoPath('/bmzm/chapter5/index')">
</view>
</swiper-item>
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>

16
bmzm/chapter5/index.vue

@ -8,7 +8,7 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
@ -27,13 +27,13 @@ export default {
currentIndex: 0,
popupIndex: 1,
swiperImages: [
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter3/img2.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter3/img3.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter3/img4.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter3/img5.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter3/img6.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter3/img7.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img1.png'
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter3/img2.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter3/img3.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter3/img4.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter3/img5.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter3/img6.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter3/img7.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img1.png'
]
};
},

26
bmzm/chapter6/index.vue

@ -4,14 +4,14 @@
<swiper-item v-for="(imageUrl, index) in imageUrls" :key="index">
<view class="swiper-item" :style="{ backgroundImage: `url(${imageUrl})` }"
@click="index == 8 ? gotoPath('/bmzm/chapter7/index') : null">
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/btn-img.png" mode="aspectFill"
<image src="https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/btn-img.png" mode="aspectFill"
:class="['btn-img', {'btn-imgs': index == 6}]" @click="gotoPath('/subPackages/techan/detail?id=37')" v-if="index == 2 || index == 4"></image>
</view>
</swiper-item>
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
@ -32,18 +32,18 @@ export default {
popupIndex: 1,
// URL
imageUrls: [
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img2.png",
// "https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img3.png",
// "https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img4.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img5.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img6.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img7.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img8.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img9.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img10.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter4/img11.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img2.png",
// "https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img3.png",
// "https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img4.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img5.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img6.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img7.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img8.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img9.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img10.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter4/img11.png",
//
"https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img1.png"
"https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img1.png"
]
};
},

22
bmzm/chapter7/index.vue

@ -7,12 +7,12 @@
<view class="swiper-item" :style="{ backgroundImage: `url(${imageUrl})` }">
<!-- 根据索引添加特定元素 -->
<template v-if="index === 2">
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img4-text.png" mode="aspectFill" class="img4-text"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img4-btn.png" mode="aspectFill" class="img4-btn" @click="showCustomModal"></image>
<image src="https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img4-text.png" mode="aspectFill" class="img4-text"></image>
<image src="https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img4-btn.png" mode="aspectFill" class="img4-btn" @click="showCustomModal"></image>
</template>
<template v-if="index === 3">
<view class="img5-text">{{ inputValue }}</view>
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img5-btn.png" mode="aspectFill" class="img5-btn"></image>
<image src="https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img5-btn.png" mode="aspectFill" class="img5-btn"></image>
</template>
<template v-if="index === 1">
<!-- 添加动态类名 -->
@ -24,7 +24,7 @@
</template>
<!-- 二维码 -->
<template v-if="index === 5">
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/qrcode.png" mode="widthFix" class="qrcode" :show-menu-by-longpress="true"></image>
<image src="https://des.dayunyuanjian.cn/epicSoul/bmzm/qrcode.png" mode="widthFix" class="qrcode" :show-menu-by-longpress="true"></image>
</template>
</view>
</swiper-item>
@ -51,7 +51,7 @@
</uni-popup>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
@ -75,12 +75,12 @@ export default {
maxLength: 25,
// swiper URL
swiperImages: [
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img2.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img3.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img4.gif',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img5.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img6.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img7.png'
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img2.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img3.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img4.gif',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img5.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img6.png',
'https://des.dayunyuanjian.cn/epicSoul/bmzm/chapter5/img7.png'
],
answerObj: {},
showAnswerBox: false

2
bmzm/components/NavMenu.vue

@ -31,7 +31,7 @@
//
navIconSrc: {
type: String,
default: 'https://static.ticket.sz-trip.com/epicSoul/taozi/nav-icon.png'
default: 'https://des.dayunyuanjian.cn/epicSoul/taozi/nav-icon.png'
}
},
data() {

84
bmzm/home/home.vue

@ -1,17 +1,28 @@
<template>
<view>
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange">
<BackButton />
<swiper
class="swiper"
:current="currentIndex"
:vertical="true"
@change="handleSwiperChange"
>
<swiper-item v-for="(image, index) in swiperImages" :key="index">
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }">
<!-- 仅在第四张图片添加子模块 -->
<template v-if="index === 3">
<image
:src="`https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home4-${i + 1}.png`"
:src="`https://des.dayunyuanjian.cn/epicSoul/bmzm/home/home4-${
i + 1
}.png`"
v-for="i in 4"
:key="i"
:class="'module'+(i+1)"
:class="'module' + (i + 1)"
:style="{ animationDelay: `${i * animationConfig.delay}s` }"
@click="setStorage(i);gotoPath(`/bmzm/chapter1/index?index=${i + 1}`)"
@click="
setStorage(i);
gotoPath(`/bmzm/chapter1/index?index=${i + 1}`);
"
></image>
</template>
</view>
@ -19,28 +30,32 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
<AudioControl
audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3"
/>
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
import AudioControl from "@/components/AudioControl.vue";
import MusicControl from "@/components/MusicControl.vue";
import NavMenu from "../components/NavMenu.vue";
import BackButton from "@/components/BackButton.vue";
export default {
components: {
MusicControl,
NavMenu,
AudioControl
AudioControl,
BackButton
},
data() {
return {
currentIndex: 0,
swiperImages: [
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home1s.gif',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home2.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home3.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home4.png'
"https://des.dayunyuanjian.cn/epicSoul/bmzm/home/home1s.gif",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/home/home2.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/home/home3.png",
"https://des.dayunyuanjian.cn/epicSoul/bmzm/home/home4.png",
],
animationConfig: {
delay: 0.5,
@ -50,15 +65,16 @@ export default {
first: 0.8,
second: 1.2,
third: 0.9,
end: 1.1
}
}
end: 1.1,
},
},
};
},
onShow() {
uni.removeStorageSync('answerObj');
uni.removeStorageSync("answerObj");
const app = getApp();
app.updateMusicSrc('https://static.ticket.sz-trip.com/epicSoul/bmzm.mp3');
app.updateMusicSrc("https://des.dayunyuanjian.cn/epicSoul/bmzm.mp3");
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
},
@ -68,43 +84,43 @@ export default {
},
// 使
setStorage(i) {
let text = ''
switch (i){
let text = "";
switch (i) {
case 0:
text = '月光白'
text = "月光白";
break;
case 1:
text = '黎明青'
text = "黎明青";
break;
case 2:
text = '玄天黑'
text = "玄天黑";
break;
case 3:
text = '胭脂红'
text = "胭脂红";
break;
default:
break;
}
this.appendToStorage('answerObj', { answer1: text });
}
this.appendToStorage("answerObj", { answer1: text });
},
},
//
// #ifdef MP-WEIXIN
onShareAppMessage() {
return {
title: '不眠之夜·Endless Dream|「Epic Soul」阅读体 issue03',
mpId: 'wx8954209bb3ad489e',
path: '/bmzm/home/home',
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/share.jpg'
title: "不眠之夜·Endless Dream|「Epic Soul」阅读体 issue03",
mpId: "wx9660f8c5776663e0",
path: "/bmzm/home/home",
imageUrl: "https://des.dayunyuanjian.cn/epicSoul/bmzm/share.jpg",
};
},
onShareTimeline() {
return {
title: '不眠之夜·Endless Dream|「Epic Soul」阅读体 issue03',
query: '',
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/share.jpg'
title: "不眠之夜·Endless Dream|「Epic Soul」阅读体 issue03",
query: "",
imageUrl: "https://des.dayunyuanjian.cn/epicSoul/bmzm/share.jpg",
};
}
},
// #endif
};
</script>

55
common/index.js

@ -0,0 +1,55 @@
import QQMapWX from '@/libs/qqmap-wx-jssdk1.2/qqmap-wx-jssdk.js';
//获取位置信息
async function getLocationInfo() {
return new Promise((resolve) => {
//位置信息默认数据
let location = {
longitude: 0,
latitude: 0,
province: '',
city: '',
area: '',
street: '',
address: '',
};
uni.getLocation({
type: 'gcj02',
success(res) {
location.longitude = res.longitude;
location.latitude = res.latitude;
// 腾讯地图Api
const qqmapsdk = new QQMapWX({
key: 'X5YBZ-ES6K3-Q6E3P-RUVXH-2R5ZQ-ERBFG', //这里填写自己申请的key
});
qqmapsdk.reverseGeocoder({
location,
success(response) {
let info = response.result;
let _c = info.ad_info.adcode.slice(0,3)
location.cityId = info.ad_info.city_code.slice(3);
location.provinceId = _c.padEnd(6,'0');
location.areaId = info.ad_info.adcode;
location.province = info.address_component.province;
location.city = info.address_component.city;
location.area = info.address_component.district;
location.street = info.address_component.street;
location.address = info.address;
resolve(location);
},
fail(e){
console.log(e,'地址信息报错')
}
});
},
fail(err) {
console.log(err);
resolve(location);
},
});
});
}
//导出
module.exports = {
getLocationInfo,
};

222
components/ActivateAgentPopup.vue

@ -0,0 +1,222 @@
<template>
<uni-popup ref="popup" type="bottom" :mask-click="false" :z-index="999">
<view class="activate-agent-popup">
<!-- 弹窗头部 -->
<view class="popup-header">
<text class="popup-title">激活AGENT</text>
<text class="popup-close" @click="closePopup">×</text>
</view>
<!-- AGENT列表 -->
<view class="agent-list">
<view
class="agent-item"
v-for="(agent, index) in agentList"
:key="index"
@click="handleAgentClick(agent)"
>
<!-- 头像区域 -->
<view class="agent-avatar">
<view class="avatar-bubble">
<image
:src="agent.headImage"
mode="aspectFill"
class="avatar-img"
></image>
</view>
</view>
<!-- 信息区域 -->
<view class="agent-info">
<view class="agent-name">
<text class="name-chinese">{{ agent.name }}</text>
<!-- <text class="name-pinyin">{{ agent.namePinyin }}</text> -->
</view>
</view>
<!-- 状态按钮 -->
<view class="agent-status">
<view
class="status-btn"
:class="
agent.status == '0' ? 'status-inactive' : 'status-active'
"
>
<text class="status-text">{{ agent.status==1?'去使用':'去激活' }}</text>
</view>
</view>
</view>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
name: "ActivateAgentPopup",
props:["agentList"],
data() {
return {
};
},
methods: {
//
openPopup() {
this.$refs.popup.open();
},
//
closePopup() {
this.$refs.popup.close();
},
// AGENT
handleAgentClick(agent) {
if (agent.status ==1) {
//
uni.navigateTo({
url: "/subPackages/other/evita?id="+agent.agentId
});
} else {
uni.navigateTo({
url:"/subPackages/equityGoods/detail?id="+agent.vos[0].benefitPackageId
})
}
},
},
};
</script>
<style lang="scss" scoped>
.activate-agent-popup {
background: #ffffff;
border-radius: 24rpx 24rpx 0 0;
padding: 40rpx 30rpx;
max-height: 80vh;
overflow-y: auto;
}
//
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
padding-bottom: 20rpx;
}
.popup-title {
font-size: 36rpx;
font-weight: bold;
color: #000000;
margin: 0 auto;
}
.popup-close {
font-size: 48rpx;
color: #999999;
padding: 10rpx;
line-height: 1;
}
// AGENT
.agent-list {
display: flex;
flex-direction: column;
gap: 30rpx;
}
.agent-item {
display: flex;
align-items: center;
padding: 20rpx;
background: #F1F1F1;
border-radius: 20rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
background: #f5f5f5;
}
}
//
.agent-avatar {
margin-right: 24rpx;
}
.avatar-bubble {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
backdrop-filter: blur(10rpx);
border: 2rpx solid rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
}
.avatar-img {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
//
.agent-info {
flex: 1;
}
.agent-name {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.name-chinese {
font-size: 32rpx;
font-weight: bold;
color: #333333;
line-height: 1.2;
}
.name-pinyin {
font-size: 24rpx;
color: #77f3f9;
font-weight: 500;
line-height: 1.2;
}
//
.agent-status {
margin-left: 20rpx;
}
.status-btn {
padding: 12rpx 24rpx;
border-radius: 20rpx;
min-width: 120rpx;
text-align: center;
transition: all 0.3s ease;
}
.status-inactive {
background: #354242;
color: #ffffff;
}
.status-active {
background: linear-gradient(135deg, #fffdb7 0%, #97fffa 100%);
color: #000000;
}
.status-text {
font-size: 26rpx;
font-weight: 500;
}
</style>

429
components/AreaPicker.vue

@ -0,0 +1,429 @@
<template>
<view class="area-picker">
<picker
mode="multiSelector"
:range="newProvinceDataList"
range-key="name"
@change="changeArea"
@columnchange="pickerColumnchange"
:value="multiIndex"
>
<!-- 使用插槽允许父组件自定义显示样式 -->
<slot
:selectedText="selectedText"
:placeholder="placeholder"
:provinceData="newProvinceDataList[0]"
:cityData="newProvinceDataList[1]"
:areaData="newProvinceDataList[2]"
:multiIndex="multiIndex"
:currentSelection="getCurrentSelection()"
>
<!-- 默认显示样式 -->
<view class="picker-display">
<text class="picker-text" :class="{ 'placeholder': !selectedText }">{{ selectedText || placeholder }}</text>
<image
class="dropdown-icon"
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEyIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0xIDFMNiA2TDExIDEiIHN0cm9rZT0iIzk5OTk5OSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+"
mode="heightFix"
></image>
</view>
</slot>
</picker>
</view>
</template>
<script>
export default {
name: 'AreaPicker',
props: {
//
placeholder: {
type: String,
default: '请选择地区'
},
// ID
defaultValue: {
type: Object,
default: () => ({
provinceId: null,
cityId: null,
areaId: null
})
},
//
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
//
columns: [],
newProvinceDataList: [[], [], []],
multiIndex: [0, 0, 0],
provinceId: null,
cityId: null,
areaId: null,
ready: false,
selectedText: '',
}
},
mounted() {
this.getSeldCityList()
},
watch: {
defaultValue: {
handler(newVal) {
console.log('----有值回显')
if (newVal && newVal.provinceId && this.ready) {
this.setDefaultValue(newVal)
}
},
deep: true,
immediate: true
}
},
methods: {
//
getSeldCityList() {
//
let requestMethod = null;
// 1. 使 Post
if (this.$parent && this.$parent.Post) {
requestMethod = this.$parent.Post;
}
// 2. 使 Post mixin
else if (this.Post) {
requestMethod = this.Post;
}
// 3. 使 uni.request
else {
this.requestWithUni();
return;
}
requestMethod({}, '/api/areas/getAll').then(res => {
if (res) {
this.processAreaData(res.data)
}
}).catch(err => {
console.warn('省市区数据获取失败:', err)
//
})
},
// 使 uni.request
requestWithUni() {
uni.request({
url: '/api/areas/getAll', // URL
method: 'POST',
data: {},
success: (res) => {
if (res.data) {
this.processAreaData(res.data.data || res.data)
}
},
fail: (err) => {
console.warn('省市区数据获取失败:', err)
//
uni.showToast({
title: '地区数据加载失败',
icon: 'none'
})
}
})
},
//
processAreaData(data) {
var result = {}
for (var i = 0; i < data.length; i++) {
var item = data[i]
if (item.parent_id == 0) {
continue
}
//
if (item.parent_id == "1") {
result[item.id.toString()] = {}
result[item.id.toString()].children = []
result[item.id.toString()].name = item.name
result[item.id.toString()].id = item.id
} else if (result[item.parent_id.toString()]) {
//
var t = {
id: item.id,
name: item.name,
children: []
}
result[item.parent_id.toString()].children.push(t)
} else {
//
var k = {
id: item.id,
name: item.name
}
for (var j = 0; j < result[item.parent_id.toString().substr(0, 2) + "0000"].children.length; j++) {
if (result[item.parent_id.toString().substr(0, 2) + "0000"].children[j].id == item.parent_id) {
result[item.parent_id.toString().substr(0, 2) + "0000"].children[j].children.push(k)
}
}
}
}
var r = []
// ObjectArray
for (var i in result) {
r.push(result[i])
}
this.columns = r
this.initPickerData()
this.ready = true
//
if (this.defaultValue && this.defaultValue.provinceId) {
this.setDefaultValue(this.defaultValue)
}
},
// picker
initPickerData() {
if (this.columns.length === 0) return
//
this.newProvinceDataList[0] = this.columns.map(item => ({
name: item.name,
id: item.id
}))
//
if (this.columns[0] && this.columns[0].children) {
this.newProvinceDataList[1] = this.columns[0].children.map(item => ({
name: item.name,
id: item.id
}))
}
//
if (this.columns[0] && this.columns[0].children[0] && this.columns[0].children[0].children) {
this.newProvinceDataList[2] = this.columns[0].children[0].children.map(item => ({
name: item.name,
id: item.id
}))
}
},
//
setDefaultValue(defaultValue) {
if (!this.ready || !defaultValue) return
//
const provinceIndex = this.newProvinceDataList[0].findIndex(item => item.id == defaultValue.provinceId)
if (provinceIndex === -1) return
this.multiIndex[0] = provinceIndex
//
this.newProvinceDataList[1] = this.columns[provinceIndex].children.map(item => ({
name: item.name,
id: item.id
}))
//
const cityIndex = this.newProvinceDataList[1].findIndex(item => item.id == defaultValue.cityId)
if (cityIndex !== -1) {
this.multiIndex[1] = cityIndex
//
this.newProvinceDataList[2] = this.columns[provinceIndex].children[cityIndex].children.map(item => ({
name: item.name,
id: item.id
}))
//
const areaIndex = this.newProvinceDataList[2].findIndex(item => item.id == defaultValue.areaId)
if (areaIndex !== -1) {
this.multiIndex[2] = areaIndex
}
}
this.updateSelectedText()
},
//
changeArea(e) {
this.multiIndex = e.detail.value
this.updateSelectedText()
this.emitChange()
},
//
pickerColumnchange(e) {
const column = e.detail.column
const value = e.detail.value
if (column === 0) {
//
this.multiIndex[0] = value
//
this.newProvinceDataList[1] = this.columns[this.multiIndex[0]].children.map(item => ({
name: item.name,
id: item.id
}))
//
if (this.columns[this.multiIndex[0]].children.length === 1) {
this.newProvinceDataList[2] = this.columns[this.multiIndex[0]].children[0].children.map(item => ({
name: item.name,
id: item.id
}))
} else {
this.newProvinceDataList[2] = this.columns[this.multiIndex[0]].children[this.multiIndex[1]].children.map(item => ({
name: item.name,
id: item.id
}))
}
//
this.multiIndex.splice(1, 1, 0)
this.multiIndex.splice(2, 1, 0)
} else if (column === 1) {
//
this.multiIndex[1] = value
//
this.newProvinceDataList[2] = this.columns[this.multiIndex[0]].children[this.multiIndex[1]].children.map(item => ({
name: item.name,
id: item.id
}))
//
this.multiIndex.splice(2, 1, 0)
} else if (column === 2) {
//
this.multiIndex[2] = value
}
},
// ID
updateSelectedText() {
if (this.newProvinceDataList[0][this.multiIndex[0]] &&
this.newProvinceDataList[1][this.multiIndex[1]] &&
this.newProvinceDataList[2][this.multiIndex[2]]) {
this.selectedText =
this.newProvinceDataList[1][this.multiIndex[1]].name
this.provinceId = this.newProvinceDataList[0][this.multiIndex[0]].id
this.cityId = this.newProvinceDataList[1][this.multiIndex[1]].id
this.areaId = this.newProvinceDataList[2][this.multiIndex[2]].id
}
},
// change
emitChange() {
const selectedText = this.newProvinceDataList[0][this.multiIndex[0]].name +
this.newProvinceDataList[1][this.multiIndex[1]].name +
this.newProvinceDataList[2][this.multiIndex[2]].name
const result = {
provinceId: this.provinceId,
cityId: this.cityId,
areaId: this.areaId,
province: this.newProvinceDataList[0][this.multiIndex[0]]?.name || '',
city: this.newProvinceDataList[1][this.multiIndex[1]]?.name || '',
area: this.newProvinceDataList[2][this.multiIndex[2]]?.name || '',
fullText: selectedText
}
this.$emit('change', result)
},
//
getValue() {
const selectedText = this.newProvinceDataList[0][this.multiIndex[0]]?.name +
this.newProvinceDataList[1][this.multiIndex[1]]?.name +
this.newProvinceDataList[2][this.multiIndex[2]]?.name
return {
provinceId: this.provinceId,
cityId: this.cityId,
areaId: this.areaId,
provinceName: this.newProvinceDataList[0][this.multiIndex[0]]?.name || '',
cityName: this.newProvinceDataList[1][this.multiIndex[1]]?.name || '',
areaName: this.newProvinceDataList[2][this.multiIndex[2]]?.name || '',
fullText: selectedText
}
},
//
reset() {
this.multiIndex = [0, 0, 0]
this.selectedText = ''
this.provinceId = null
this.cityId = null
this.areaId = null
this.initPickerData()
},
// 使
getCurrentSelection() {
if (!this.newProvinceDataList[0][this.multiIndex[0]] ||
!this.newProvinceDataList[1][this.multiIndex[1]] ||
!this.newProvinceDataList[2][this.multiIndex[2]]) {
return {
province: null,
city: null,
area: null,
fullText: ''
}
}
return {
province: this.newProvinceDataList[0][this.multiIndex[0]],
city: this.newProvinceDataList[1][this.multiIndex[1]],
area: this.newProvinceDataList[2][this.multiIndex[2]],
fullText: this.newProvinceDataList[0][this.multiIndex[0]].name +
this.newProvinceDataList[1][this.multiIndex[1]].name +
this.newProvinceDataList[2][this.multiIndex[2]].name
}
}
}
}
</script>
<style scoped lang="scss">
.area-picker {
width: 100%;
}
.picker-display {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8rpx 16rpx;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
background-color: #fff;
min-height: 60rpx;
}
.picker-text {
font-size: 30rpx;
color: #333;
flex: 1;
&.placeholder {
color: #999;
}
}
.dropdown-icon {
width: 24rpx;
height: 16rpx;
margin-left: 8rpx;
}
</style>

155
components/AudioControl使用文档.md

@ -1,155 +0,0 @@
# AudioControl 音频控制组件使用文档
## 组件功能
- 在父组件右上角显示音频控制图标
- 点击图标播放指定音频,同时暂停背景音乐
- 再次点击暂停音频,恢复背景音乐
- 音频播放结束后自动恢复背景音乐
## 组件属性 (Props)
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| audioSrc | String | 是 | - | 音频文件路径 |
| visible | Boolean | 否 | true | 是否显示组件 |
## 使用方法
### 1. 在父组件中引入和注册组件
```vue
<template>
<view class="parent-container">
<!-- 父组件内容 -->
<view class="content">
<!-- 你的页面内容 -->
</view>
<!-- 音频控制组件 -->
<AudioControl
:audioSrc="audioUrl"
:visible="showAudio"
/>
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
export default {
components: {
AudioControl
},
data() {
return {
audioUrl: 'https://your-domain.com/audio/sample.mp3', // 替换为你的音频URL
showAudio: true
}
}
}
</script>
<style>
.parent-container {
position: relative; /* 重要:确保AudioControl能正确定位 */
width: 100vw;
height: 100vh;
}
</style>
```
### 2. 使用项目中的showImg方法(推荐)
如果你的音频文件也存储在项目服务器上,可以使用项目的showImg方法:
```vue
<script>
export default {
data() {
return {
audioUrl: this.showImg('/uploads/audio/your-audio-file.mp3'),
showAudio: true
}
}
}
</script>
```
### 3. 动态控制音频源
你可以根据不同的页面或条件播放不同的音频:
```vue
<script>
export default {
data() {
return {
audioUrl: '',
showAudio: true
}
},
mounted() {
// 根据页面设置不同的音频
this.setAudioForCurrentPage();
},
methods: {
setAudioForCurrentPage() {
const currentRoute = this.$route.path; // 假设使用vue-router
switch(currentRoute) {
case '/chapter1':
this.audioUrl = this.showImg('/uploads/audio/chapter1.mp3');
break;
case '/chapter2':
this.audioUrl = this.showImg('/uploads/audio/chapter2.mp3');
break;
default:
this.audioUrl = this.showImg('/uploads/audio/default.mp3');
}
}
}
}
</script>
```
## 样式说明
组件默认定位在父组件的右上角(top: 30rpx, right: 30rpx),如果需要调整位置,可以在父组件中覆盖样式:
```vue
<style>
/* 调整音频控制组件位置 */
.parent-container ::v-deep .audio-control {
top: 50rpx !important;
right: 50rpx !important;
}
</style>
```
## 注意事项
1. **父组件样式**:确保父组件设置了 `position: relative`,这样AudioControl组件才能正确定位
2. **音频格式**:建议使用 mp3 格式的音频文件,兼容性最好
3. **音频路径**:确保音频文件路径正确且可访问
4. **背景音乐**:组件会自动处理与MusicControl组件的交互,无需额外配置
## 图标说明
- 🔊:音频未播放状态
- 🎧:音频播放中状态,带有脉动动画效果
## 事件处理
组件内部已处理所有音频播放逻辑,包括:
- 播放音频时自动暂停背景音乐
- 暂停音频时自动恢复背景音乐
- 音频播放结束时自动恢复背景音乐
- 组件销毁时自动清理资源
## 示例场景
适用于以下场景:
- 章节页面播放对应的音频解说
- 展示页面播放介绍音频
- 互动页面播放提示音频
- 任何需要临时播放音频并暂停背景音乐的场景

97
components/BackButton.vue

@ -0,0 +1,97 @@
<template>
<view
class="back-button-container"
:style="{ paddingTop: statusBarHeight + 'px' }"
>
<view @click="handleBack" class="back-btn">
<image class="back-icon" :src="iconSrc" mode="aspectFill"></image>
</view>
</view>
</template>
<script>
export default {
name: "BackButton",
props: {
//
iconSrc: {
type: String,
default: "https://epic.js-dyyj.com/uploads/20250825/f7e4825867dbd90e2cd0721a49fad6eb.png",
},
//
customBack: {
type: Function,
default: null,
},
},
data() {
return {
statusBarHeight: 0,
};
},
mounted() {
this.setStatusBarHeight();
},
methods: {
setStatusBarHeight() {
try {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
} catch (e) {
// 使
this.statusBarHeight = 0;
}
},
handleBack() {
if (this.customBack) {
// 使
this.customBack();
} else {
//
uni.navigateBack({
delta: 1,
fail: () => {
//
uni.switchTab({
url: "/pages/index/index",
});
},
});
}
},
},
};
</script>
<style lang="scss" scoped>
.back-button-container {
position: fixed;
top: 0;
left: 0;
z-index: 999;
padding-left: 20rpx;
padding-bottom: 20rpx;
}
.back-btn {
width: 80rpx;
height: 80rpx;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(10rpx);
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
background-color: rgba(0, 0, 0, 0.5);
}
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
</style>

320
components/Book.vue

@ -0,0 +1,320 @@
<template>
<view class="product-section">
<view class="title-section">
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
"
@click="handleMoreClick"
>
<text class="title">EPIC SOUL阅读体</text>
<view class="more-btn"><image src="https://des.dayunyuanjian.cn/data/2025/08/31/affb21f0-fcc1-4746-9543-0d54369aa315.png" style="width:124.6rpx;height: 36rpx;"/></view>
</div>
</view>
<!-- 轮播容器 -->
<view class="carousel-container">
<!-- 左箭头 -->
<view
class="nav-arrow left-arrow"
@click="prevSlide"
v-if="list.length > 1 && currentIndex > 0"
>
<image style="width: 50rpx;height: 50rpx;" :src="showImg('/uploads/20250908/29beeddf1e45571d2c5a4187f2f1ae05.png')"></image>
</view>
<!-- 轮播内容 -->
<scroll-view
class="carousel-scroll"
scroll-x="true"
:show-scrollbar="false"
:enhanced="true"
:scroll-with-animation="true"
:scroll-left="scrollLeft"
@scroll="onScroll"
@scrollend="onScrollEnd"
>
<view class="carousel-content">
<view
class="carousel-item"
v-for="(item, index) in list"
:key="index"
@click="gotoUrlNew(item)"
v-if="item && item.image"
>
<view class="issue-card">
<!-- 背景图片 -->
<image
class="card-bg"
:src="showImg(item.image)"
mode="aspectFill"
></image>
</view>
</view>
<!-- 无数据时的提示 -->
<view v-if="!list || list.length === 0" class="no-data">
<text>暂无数据</text>
</view>
</view>
</scroll-view>
<!-- 右箭头 -->
<view
class="nav-arrow right-arrow"
@click="nextSlide"
v-if="list.length > 1 && currentIndex < list.length - 1"
>
<image style="width: 50rpx;height: 50rpx;" :src="showImg('/uploads/20250908/6622b3699518d6b559e1241d7addb7af.png')"></image>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
list: [],
currentIndex: 0,
scrollLeft: 0,
itemWidth: 224, // 207rpx + 30rpx margin
};
},
mounted() {
this.getList();
},
methods: {
handleMoreClick(){
uni.switchTab({
url:'/pages/index/readingBody'
})
},
getList() {
this.Post(
{
type_id: 3,
offset: 0,
limit: 10, //
},
"/api/article/getArticleByType"
).then((res) => {
if (res.data && res.data.length > 0) {
this.list = res.data;
//
this.currentIndex = 0;
this.scrollLeft = 0;
}
});
},
//
prevSlide() {
if (!this.validateData()) return;
if (this.list.length > 1) {
this.currentIndex =
this.currentIndex > 0 ? this.currentIndex - 1 : this.list.length - 1;
this.$nextTick(() => {
this.scrollToCurrentItem();
});
}
},
//
nextSlide() {
if (!this.validateData()) return;
if (this.list.length > 1) {
this.currentIndex =
this.currentIndex < this.list.length - 1 ? this.currentIndex + 1 : 0;
this.$nextTick(() => {
this.scrollToCurrentItem();
});
}
},
//
scrollToCurrentItem() {
//
if (this.currentIndex < 0) {
this.currentIndex = 0;
}
if (this.currentIndex >= this.list.length) {
this.currentIndex = this.list.length - 1;
}
const scrollPosition = this.currentIndex * this.itemWidth;
this.scrollLeft = scrollPosition;
},
//
onScroll(e) {
const scrollLeft = e.detail.scrollLeft;
const index = Math.round(scrollLeft / this.itemWidth);
if (
index !== this.currentIndex &&
index >= 0 &&
index < this.list.length
) {
this.currentIndex = index;
}
},
//
onScrollEnd(e) {
const scrollLeft = e.detail.scrollLeft;
const index = Math.round(scrollLeft / this.itemWidth);
//
this.currentIndex = Math.max(0, Math.min(index, this.list.length - 1));
this.$nextTick(() => {
this.scrollToCurrentItem();
});
},
//
handleItemClick(item) {
console.log("点击了卡片:", item);
//
},
//
validateData() {
if (!this.list || this.list.length === 0) {
this.currentIndex = 0;
this.scrollLeft = 0;
return false;
}
//
if (this.currentIndex < 0) {
this.currentIndex = 0;
}
if (this.currentIndex >= this.list.length) {
this.currentIndex = this.list.length - 1;
}
return true;
},
},
};
</script>
<style lang="scss" scoped>
.product-section {
width: 100%;
background-color: #fffdd6;
padding: 40rpx 25rpx;
margin: 30rpx 0;
border-radius: 40rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1),
0 0 0 1rpx rgba(255, 255, 255, 0.2) inset;
}
//
.carousel-container {
position: relative;
width: 100%;
height: 368rpx;
display: flex;
align-items: center;
justify-content: center;
}
//
.carousel-scroll {
width: 100%;
height: 100%;
white-space: nowrap;
}
//
.carousel-content {
display: flex;
align-items: center;
height: 100%;
min-width: max-content;
}
//
.carousel-item {
flex-shrink: 0;
width: 210rpx;
height: 368rpx;
margin: 0 7rpx;
will-change: transform;
}
//
.issue-card {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
//
.card-bg {
width: 100%;
height: 100%;
}
//
.nav-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 3;
transition: all 0.3s ease;
&.left-arrow {
left: 0rpx;
}
&.right-arrow {
right: 0rpx;
}
}
.arrow-icon {
font-size: 32rpx;
color: #ffffff;
font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
//
.no-data {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 368rpx;
color: #999;
font-size: 28rpx;
}
//
.title-section {
display: inline-block;
padding: 0rpx 0 30rpx;
width: 100%;
.title {
font-size: 34rpx;
font-weight: bold;
color: #000000;
}
.more-btn {
font-size: 26rpx;
color: #000000;
margin-left: 35rpx;
}
}
</style>

2
components/BuyPeaches.vue

@ -1,6 +1,6 @@
<template>
<view>
<image @click="buyTaozi" class="taozi" src="https://static.ticket.sz-trip.com/epicSoul/taozi/taozi.png" mode=""></image>
<image @click="buyTaozi" class="taozi" src="https://des.dayunyuanjian.cn/epicSoul/taozi/taozi.png" mode=""></image>
</view>
</template>

121
components/CustomTabBar.vue

@ -1,56 +1,96 @@
<template>
<view class="">
<view class="custom-tab-bar-placeholder"></view>
<view class="custom-tab-bar">
<view class="tab-item" v-for="(item,i) in tabBarList" :key="i" v-if="tabBarShowList[i]" @click="switchTab(i)">
<text :style="{ 'color': currentTab === i?item.selectColor:'#fff' }">{{ item.text }}</text>
<view
class="tab-item"
v-for="(item, i) in tabBarList"
:key="i"
v-if="tabBarShowList[i]"
@click="switchTab(i)"
>
<image
:src="currentTab === i ? item.select_img : item.img"
style="height: 80rpx;"
:style="{'width':item.width}"
></image>
<!-- <text :style="{ color: currentTab === i ? item.selectColor : '#fff' }">{{
item.text
}}</text> -->
</view>
</view>
</view>
</template>
<script>
export default {
export default {
props: {
currentTab: {
type: Number,
default: 0
}
default: 0,
},
},
data() {
return {
tabBarList: [{
"pagePath": "pages/index/index",
"selectColor": "#00FF00",
"text": "首页"
tabBarList: [
{
pagePath: "pages/index/index",
selectColor: "#00FF00",
img: require("@/static/image/tabbar/home.png"),
select_img: require("@/static/image/tabbar/home_select.png"),
text: "首页",
width:'53rpx'
},
{
"pagePath": "pages/index/readingBody",
"selectColor": "#00FF00",
"text": "阅读体"
pagePath: "pages/index/readingBody",
selectColor: "#00FF00",
text: "阅读体",
img: require("@/static/image/tabbar/book.png"),
select_img: require("@/static/image/tabbar/book_select.png"),
width:'58rpx'
},
// {
// pagePath: "pages/index/sensoryStore",
// selectColor: "#00FF00",
// text: "",
// },
{
"pagePath": "pages/index/sensoryStore",
"selectColor": "#00FF00",
"text": "有感商店"
pagePath: "pages/index/timeShopBank",
selectColor: "#00FF00",
text: "时间银行",
img: require("@/static/image/tabbar/time.png"),
select_img: require("@/static/image/tabbar/time_select.png"),
width:'58rpx'
},
{
"pagePath": "pages/index/intelligentAgent",
"selectColor": "#00FFFF",
"text": "智能体"
pagePath: "pages/index/intelligentAgent",
selectColor: "#00FFFF",
text: "智能体",
img: require("@/static/image/tabbar/agent.png"),
select_img: require("@/static/image/tabbar/agent_select.png"),
width:'58rpx'
},
{
"pagePath": "pages/index/iSoul",
"selectColor": "#00FF00",
"text": "iSoul"
}
pagePath: "pages/index/iSoul",
selectColor: "#00FF00",
text: "iSoul",
img: require("@/static/image/tabbar/isoul.png"),
select_img: require("@/static/image/tabbar/isoul_select.png"),
width:'47rpx'
},
],
tabBarShowList: []
tabBarShowList: [],
};
},
onLoad() {
this.getCurrentTab();
},
mounted() {
this.tabBarShowList = uni.getStorageSync('SHFlag').split(',').map(item => {
return item.trim().toLowerCase() === 'true';
this.tabBarShowList = uni
.getStorageSync("SHFlag")
.split(",")
.map((item) => {
return item.trim().toLowerCase() === "true";
});
},
methods: {
@ -67,15 +107,15 @@
switchTab(index) {
if (this.currentTab === index) return;
uni.switchTab({
url: '/' + this.tabBarList[index].pagePath
url: "/" + this.tabBarList[index].pagePath,
});
}
}
};
},
},
};
</script>
<style scoped>
.custom-tab-bar {
.custom-tab-bar {
position: fixed;
bottom: 0;
left: 0;
@ -83,22 +123,27 @@
display: flex;
justify-content: space-around;
align-items: center;
height: 123rpx;
z-index: 30;
background: #989898;
}
padding: 20rpx 0;
background: white;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
}
.tab-item {
.custom-tab-bar-placeholder {
height: calc(env(safe-area-inset-bottom) + 120rpx);
width: 100%;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
height: 100%;
}
}
.tab-item text {
.tab-item text {
font-size: 31rpx;
}
}
</style>

931
components/DynamicIsland.vue

@ -0,0 +1,931 @@
<template>
<!-- 灵动岛占位区域 - 始终存在但控制可见性 -->
<view
class="dynamic-island-placeholder"
:class="{ visible: isScrolled }"
:style="{ height: 216 + 'rpx' }"
>
<view
class="dynamic-island"
:class="{
compact: actualCompactState,
fixed: isFixed,
}"
:style="{ top: isFixed ? fixedTopPosition + 'px' : 0 }"
@click="handleToggle"
>
<!-- 展开状态 -->
<view v-if="!actualCompactState" class="expanded-content">
<template>
<template v-if="styleType != 'timeShop'">
<!-- 三栏布局 -->
<view class="three-column-layout">
<!-- 右侧头像和链接 -->
<view class="right-section">
<view class="avatar-container" @click="toWebView">
<image
class="avatar"
src="https://epic.js-dyyj.com/uploads/20250826/92b0a21e9125fc21ca294a408bf3508f.png"
mode="aspectFill"
></image>
<view class="ai-label">智能体</view>
</view>
<view
class=""
style="
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
"
>
<view class="profile-info">
<text class="profile-title">数字领航员</text>
<text class="profile-name">EVITA</text>
</view>
<view class="platform-link">
<view class="link-text" @click="toDesInfo"
>交响介绍 >>
</view>
<!-- <view class="link-text">DES广播 >></view> -->
</view>
</view>
</view>
<!-- 左侧分隔线 -->
<view class="column-divider"></view>
<!-- 左侧欢迎信息 -->
<view class="left-section">
<view class="welcome-message">
<view class="welcome-text"
>Hi!
{{
userInfo && userInfo.token ? userInfo.nickname : "用户"
}}欢迎回来~</view
>
</view>
<view
class=""
style="font-size: 24rpx; font-weight: bold; color: #000000"
>
查看您的交响数据文化资产行
</view>
<view
class=""
style="
display: flex;
align-items: flex-end;
justify-content: space-between;
"
>
<view class="" @click="toOrder">
<view class="stats-info">
<text class="stats-number">{{
(userInfo && userInfo.unUseOrderQuantity) || 0
}}</text>
<text class="stats-unit"></text>
</view>
<view class="stats-label">交响权益行</view>
</view>
<div @click="toTime">
<view class="stats-info">
<text class="stats-number">{{
(userInfo && userInfo.ipQuantity) || 0
}}</text>
<text class="stats-unit"></text>
</view>
<view class="stats-label">交响资产行</view>
</div>
<view class="middle-section">
<view class="time-reward-container" @click="toPoint">
<text
class="time-reward-title"
style="margin-bottom: 5rpx"
>时间奖励</text
>
<view class="time-reward-stats">
<text class="time-reward-number">{{
userInfo && userInfo.token
? userInfo.hourValue || 0
: 0
}}</text>
<text class="time-reward-unit"></text>
</view>
<text class="time-reward-label" style="font-weight: bold"
>交响时间行</text
>
</view>
</view>
</view>
</view>
</view>
</template>
<template v-if="styleType == 'timeShop'">
<view class="bottom-section">
<view class="" style="flex: 1">
<view class="" style="display: flex">
<view
class="time-reward-container"
style="width: 200rpx"
@click="toPoint"
>
<text class="time-reward-title">时间奖励</text>
<view class="time-reward-stats">
<text class="time-reward-number">{{
userInfo && userInfo.token ? userInfo.hourValue || 0 : 0
}}</text>
<text class="time-reward-unit"></text>
</view>
<text
class="time-reward-label"
style="font-size: 24rpx; font-weight: bold"
>交响时间行</text
>
</view>
<view class="">
<view class="time-reward-number" style="font-size: 34rpx">
{{
userInfo && userInfo.token ? userInfo.nickname : "用户"
}}
</view>
<view
class="time-reward-label"
@click="toPoint"
style="
margin-top: 15rpx;
font-weight: bold;
font-size: 26rpx;
"
>
积分:{{ totalPoints || 0
}}<text
@click.stop="pointDetail"
style="
color: #999999;
font-size: 22rpx;
margin-left: 10rpx;
"
>积分获取规则</text
>
</view>
</view>
</view>
<view
class=""
style="
display: flex;
align-items: center;
font-size: 26rpx;
font-weight: bold;
display: flex;
align-items: center;
margin-top: 20rpx;
"
>
<view class="" style="width: 200rpx">
时长:{{
userInfo && userInfo.token ? userInfo.hour || 0 : 0
}}h
</view>
<view class="">
<image
style="width: 22rpx; height: 22rpx; margin-right: 15rpx"
:src="
showImg(
'/uploads/20250822/c8ee7615823a1ffaba400a4d5746de9a.png'
)
"
>
</image>
点赞:{{ (userInfo && userInfo.likeCount) || 0 }}
</view>
<view class="">
<image
style="width: 22rpx; height: 22rpx; margin: 0 15rpx"
:src="
showImg(
'/uploads/20250822/84c49f78f1c86b7340aaaa391bd4b7cf.png'
)
"
>
</image>
留言:0
</view>
</view>
</view>
<image
class="avatar"
@click="toWebView"
src="https://epic.js-dyyj.com/uploads/20250826/92b0a21e9125fc21ca294a408bf3508f.png"
mode="aspectFill"
></image>
</view>
</template>
</template>
</view>
<!-- 紧凑状态 -->
<view v-else class="compact-content">
<text class="compact-name">{{ getCompactName() }}</text>
<image
class="compact-avatar"
src="https://epic.js-dyyj.com/uploads/20250826/92b0a21e9125fc21ca294a408bf3508f.png"
mode="aspectFill"
></image>
</view>
</view>
</view>
</template>
<script>
export default {
name: "DynamicIsland",
props: {
isCompact: {
type: Boolean,
default: false,
},
styleType: {
type: String,
default: "",
},
title: {
type: String,
default: "用户",
},
subtitle: {
type: String,
default: "周杰伦 - 青花瓷",
},
avatarUrl: {
type: String,
default: "https://picsum.photos/80/80",
},
actionText: {
type: String,
default: "暂停",
},
//
pageId: {
type: String,
default: "default_page",
},
},
data() {
return {
isExpanded: false,
statusBarHeight: 0,
isScrolled: false,
scrollThreshold: 160, // 160rpx
// props
currentTitle: "Hi!用户,欢迎回来~",
currentSubtitle: "2个权益 | 120时间银行",
currentAvatar: "https://picsum.photos/80/80",
currentAction: "激活你的Agent",
userInfo: {},
totalPoints: 0,
};
},
computed: {
// 使
actualCompactState() {
if (this.isScrolled) {
return !this.isExpanded;
}
return false; //
},
// top
fixedTopPosition() {
// + (40px) + (20px)
return this.statusBarHeight + 40 + 20;
},
// -
placeholderHeight() {
//
// 32rpx() + 160rpx() + 24rpx() = 216rpx
const islandHeightRpx = 160; //
const topMarginRpx = 32; //
const bottomMarginRpx = 24; //
return topMarginRpx + islandHeightRpx + bottomMarginRpx;
},
// 使props
title() {
return this.currentTitle;
},
subtitle() {
return this.currentSubtitle;
},
avatarUrl() {
return this.currentAvatar;
},
actionText() {
return this.currentAction;
},
//
isFixed() {
return this.isScrolled;
},
},
mounted() {
// uni-app
this.setStatusBarHeight();
//
this.addScrollListener();
//
this.getUserInfo();
},
beforeDestroy() {
//
this.removeScrollListener();
},
methods: {
toLogin() {
uni.navigateTo({
url: "/pages/login/login",
});
},
handleToggle() {
if (this.isScrolled) {
//
this.isExpanded = !this.isExpanded;
this.$emit("toggle", this.isExpanded);
} else {
//
this.$emit("toggle");
}
},
handleAction() {
this.$emit("action");
},
//
collapseIsland() {
if (this.isScrolled && this.isExpanded) {
this.isExpanded = false;
this.$emit("toggle", this.isExpanded);
}
},
//
addScrollListener() {
// ID
const eventName = `pageScroll_${this.pageId}`;
console.log("DynamicIsland 添加滚动监听:", eventName);
uni.$on(eventName, this.handlePageScroll);
},
//
removeScrollListener() {
// ID
const eventName = `pageScroll_${this.pageId}`;
uni.$off(eventName, this.handlePageScroll);
},
//
handlePageScroll(e) {
const scrollTop = e.scrollTop || e;
const shouldScroll = scrollTop > this.scrollThreshold;
if (this.isScrolled !== shouldScroll) {
this.isScrolled = shouldScroll;
//
}
//
if (this.isScrolled) {
this.collapseIsland();
}
},
toOrder() {
uni.switchTab({
url: "/pages/index/iSoul",
});
},
toTime() {
uni.switchTab({
url: "/pages/index/timeShopBank",
});
},
getCompactName() {
//
if (this.userInfo && this.userInfo.nickname) {
return this.userInfo.nickname;
}
return "用户";
},
toPoint() {
uni.navigateTo({
url: "/subPackages/points/index",
});
},
getStatNumber(type) {
//
if (this.subtitle) {
if (type === "权益") {
const match = this.subtitle.match(/(\d+)个权益/);
return match ? match[1] : "0";
} else if (type === "时间银行") {
const match = this.subtitle.match(/(\d+)时间银行/);
return match ? match[1] : "0";
}
}
return "0";
},
//
setStatusBarHeight() {
try {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
} catch (e) {
console.warn("获取系统信息失败:", e);
this.statusBarHeight = 0;
}
},
//
getUserInfo() {
try {
this.userInfo =
(uni.getStorageSync("userInfo") &&
JSON.parse(uni.getStorageSync("userInfo"))) ||
this.$store.state.user.userInfo ||
{};
console.log(this.userInfo, "this.userInfo");
//
if (this.userInfo && this.userInfo.nickname) {
this.currentTitle = `Hi!${this.userInfo.nickname},欢迎回来~`;
this.Post({}, "/framework/points/getLastBalance", "DES").then(
(res) => {
if (res.code === 200) {
this.totalPoints = res.data.balance || 0;
}
}
);
}
} catch (e) {
console.warn("获取用户信息失败:", e);
this.userInfo = {};
}
},
toWebView() {
// uni.navigateTo({
// url: "/subPackages/webPage/webPage?url=" +
// "https://des.dayunyuanjian.cn/dist/#/",
// });
uni.navigateTo({
url: "/subPackages/other/evita?id=0",
});
},
toDesInfo() {
uni.navigateTo({
url: "/subPackages/other/introduction",
});
},
pointDetail() {
uni.navigateTo({
url: "/subPackages/user/privacyInfo?id=10222",
});
},
},
};
</script>
<style scoped lang="scss">
/* 灵动岛占位区域样式 - 始终存在但控制可见性 */
.dynamic-island-placeholder {
width: 100%;
background: transparent;
position: relative;
opacity: 1;
transition: opacity 0.3s ease;
// padding: 24rpx 0;
margin-top: 24rpx;
}
.dynamic-island-placeholder.visible {
opacity: 1;
}
/* 当灵动岛不是固定状态时,确保它在占位符内正常显示 */
.dynamic-island-placeholder .dynamic-island:not(.fixed) {
position: relative;
z-index: 100;
}
.dynamic-island {
// margin: 24rpx auto 24rpx;
margin: 0 auto;
z-index: 100;
background: linear-gradient(180deg, #fffdb7 0%, #97fffa 100%);
backdrop-filter: blur(20rpx);
-webkit-backdrop-filter: blur(20rpx);
border-radius: 40rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1),
0 0 0 1rpx rgba(255, 255, 255, 0.2) inset;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
//
width: 710rpx;
height: 216rpx;
//
&.compact {
width: 300rpx;
height: 80rpx;
border-radius: 40rpx;
.expanded-content {
opacity: 0;
visibility: hidden;
transition: opacity 0.15s ease-out, visibility 0s linear 0.15s;
}
}
&:not(.compact) {
.compact-content {
opacity: 0;
visibility: hidden;
transition: opacity 0.15s ease-out, visibility 0s linear 0.15s;
}
}
//
&.fixed {
position: fixed;
left: 50%;
transform: translateX(-50%);
z-index: 998;
margin: 0;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
animation: slideInFromTop 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
}
.expanded-content {
display: flex;
flex-direction: column;
height: 100%;
padding: 24rpx 25rpx;
opacity: 1;
visibility: visible;
transition: opacity 0.2s ease-in 0.1s, visibility 0s linear 0s;
}
/* 三栏布局样式 */
.three-column-layout {
display: flex;
align-items: flex-end;
height: 100%;
width: 100%;
}
/* 列分隔线 */
.column-divider {
width: 2rpx;
height: 160rpx;
background: rgba(0, 0, 0, 0.1);
margin: 0rpx 25rpx;
flex-shrink: 0;
}
/* 左侧区域 */
.left-section {
color: #333;
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
height: 100%;
}
.welcome-message {
display: flex;
flex-direction: column;
}
.welcome-text {
font-size: 22rpx;
color: #000000;
line-height: 1.2;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.welcome-subtitle {
font-size: 24rpx;
color: #000000;
margin-top: 4rpx;
font-weight: bold;
}
.stats-info {
display: flex;
align-items: baseline;
margin-bottom: 8rpx;
}
.stats-number {
font-size: 40rpx;
color: #333;
font-weight: bold;
line-height: 1;
}
.stats-unit {
font-size: 24rpx;
color: #000000;
margin-left: 4rpx;
}
.stats-label {
font-size: 22rpx;
color: #000000;
font-weight: bold;
}
/* 中间区域 */
.middle-section {
display: flex;
justify-content: flex-start;
align-items: center;
}
.time-reward-container {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
}
.time-reward-title {
font-size: 20rpx;
color: #000000;
font-weight: 500;
margin-bottom: 12rpx;
}
.time-reward-stats {
display: flex;
align-items: baseline;
margin-bottom: 8rpx;
}
.time-reward-number {
font-size: 40rpx;
color: #000000;
font-weight: bold;
line-height: 1;
}
.time-reward-unit {
font-size: 24rpx;
color: #000000;
margin-left: 4rpx;
}
.time-reward-label {
font-size: 22rpx;
color: #000000;
}
/* 右侧区域 */
.right-section {
display: flex;
align-items: flex-end;
height: 100%;
}
.avatar-container {
position: relative;
margin-right: 10rpx;
width: 140rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.avatar {
width: 130rpx;
height: 130rpx;
border-radius: 50%;
}
.profile-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.profile-title {
font-size: 26rpx;
color: #000000;
margin-bottom: 4rpx;
font-weight: bold;
}
.profile-name {
font-size: 26rpx;
color: #000000;
font-weight: bold;
}
.platform-link {
cursor: pointer;
}
.link-text {
font-size: 22rpx;
color: #000000;
text-decoration: underline;
font-weight: 500;
}
/* 保留原有的底部区域样式用于timeShop模式 */
.bottom-section {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
color: #000000;
}
.stats-section {
display: flex;
gap: 32rpx;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
font-size: 28rpx;
font-weight: bold;
}
.stat-number {
font-size: 32rpx;
color: #000000;
font-weight: bold;
line-height: 1;
margin-bottom: 4rpx;
}
.stat-label {
font-size: 28rpx;
color: #000000;
line-height: 1;
font-weight: bold;
margin-top: 20rpx;
}
.divider {
width: 2rpx;
height: 60rpx;
background: rgba(255, 255, 255, 0.3);
margin: 0 24rpx;
}
.action-section {
display: flex;
align-items: center;
flex: 1;
justify-content: space-between;
}
.action-text {
font-size: 26rpx;
color: #ffffff;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
//
.dynamic-island:active {
transform: scale(0.98);
&.fixed {
transform: translateX(-50%) scale(0.98);
}
}
//
@keyframes slideInFromTop {
0% {
transform: translateX(-50%) translateY(-100%);
opacity: 0;
}
100% {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
}
.compact-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 24rpx;
opacity: 1;
visibility: visible;
transition: opacity 0.2s ease-in 0.1s, visibility 0s linear 0s;
}
.compact-name {
font-size: 27rpx;
color: #333;
font-weight: bold;
flex: 1;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
.compact-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
border: 2rpx solid rgba(0, 0, 0, 0.2);
flex-shrink: 0;
}
//
@keyframes pulse {
0% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
}
//
// @media (max-width: 750rpx) {
// .dynamic-island {
// &.is-expanded {
// width: 100vw;
// max-width: 750rpx;
// }
// &.is-compact {
// width: 280rpx;
// }
// }
// }
.action-text-box {
color: #000000;
.action-text-box-des {
font-size: 20rpx;
}
.action-text-box-msg {
font-size: 24rpx;
display: flex;
align-items: center;
margin-top: 20rpx;
}
.action-text-box-img {
width: 57rpx;
height: 46rpx;
margin-right: 10rpx;
}
}
.ai-label {
border: 1rpx solid;
padding: 0rpx 15rpx;
height: 40rpx;
line-height: 38rpx;
font-weight: bold;
font-size: 20rpx;
border-radius: 4rpx;
border-color: #333333;
color: #333333;
display: inline;
margin-top: 10rpx;
}
.ai-name {
font-size: 27rpx;
font-weight: bold;
color: #ffffff;
margin-left: 10rpx;
}
</style>

BIN
components/GPT/.DS_Store

Binary file not shown.

414
components/GPT/components/client-chat.vue

@ -0,0 +1,414 @@
<template>
<view class="client-chat" style="height: 100%">
<view ref="first" clazz="pop-demo" :use-arrow="true">
<view class="pop-demo-list" v-for="pop in popperList" :key="pop.id">
<v-button type="link" kind="primary" @click="openSearchUrl(pop, pop.id)">
{{ pop.id }}.{{ pop.name }}
<v-icon remote name="arrow_right_line" size="12" valign="-1"></v-icon>
</v-button>
</view>
</view>
<view class="qa-item" v-for="(item, index) in msgList" :key="index">
<!-- 时间戳 -->
<view class="timestamp"
v-if="index === 0 || (index !== 0 && item.timestamp && (Number(item.timestamp) - Number(msgList[index - 1].timestamp)) > timestampGap)">
{{ moment(new Date(String(item.timestamp).length === 10 ? item.timestamp * 1000 :
Number(item.timestamp))).format('MM-DD HH:mm') }}
</view>
<!-- 问题 -->
<view class="question-item" v-if="item.is_from_self">
<v-spinner status="default" class="qs-loading" v-if="item.is_loading"></v-spinner>
<!-- <VueMarkdown class="question-text" style="max-width: 352px" :source="item.content"
:anchorAttributes="{ target: '_blank' }" :linkify="false" /> -->
</view>
<!-- 答案 -->
<view class="answer-item" v-if="!item.is_from_self">
<!-- 头像 -->
<view class="answer-avatar">
<img class="robot-avatar" :src="item.from_avatar" />
</view>
<!-- 答案信息 -->
<view class="answer-info" :ref="item.record_id">
<view v-if="item.agent_thought && item.agent_thought.procedures && item.agent_thought.procedures.length > 0">
<!-- 思考部分 -->
<MsgThought
v-for="(thought, i) in item.agent_thought.procedures"
:key="i"
:content="thought.debugging.content"
:title="thought.title"
:titleIcon="thought.icon"
:nodeName="thought.name"
:status="thought.status"
:elapsed="thought.elapsed"
:detailVisible="thought.detailVisible"
/>
</view>
<view class="loading" v-if="item.loading_message">正在思考中</view>
<!-- 回复主体 -->
<MsgContent :showTags="true"
:recordId="item.record_id"
:isReplaceLinks="true"
:loadingMessage="item.loading_message"
:content="item.content"
:isFinal="item.is_final"
:isMdExpand="item.isMdExpand"
@littleTagClick="littleTagClick"
/>
<!-- 参考来源 -->
<Reference v-if="item.references && item.references.length>0" :references-list="item.references"/>
<!-- 运行状态 -->
<TokensBoardBfr class="tokens-board-class" :showDtl="true" :tokensData="item.tokens_msg"></TokensBoardBfr>
</view>
</view>
</view>
</view>
</template>
<script>
import clone from 'clone';
// import VueMarkdown from 'vue-markdown';
import elementResizeDetectorMaker from 'element-resize-detector';
import { scrollToBottom } from './../utils/util';
import { MESSAGE_TYPE, ACCESS_TYPE } from './../constants';
import TokensBoardBfr from './tokens-board-brif.vue';
import Reference from './reference-component.vue';
export default {
name: 'ClientChat',
components: {
// VueMarkdown,
Reference,
TokensBoardBfr
},
data () {
return {
popperList: [],
oldPopDemo: null,
loading: false,
historyLoading: false,
timestampGap: 5 * 60, // 5min
msgList: [], //
robotName: '', //
chatBoxHeight: document.body.clientHeight,
jsScrolling: false,
userScrolling: false
};
},
created () {
// /ws
this.listenClientAndManageEvent();
// ws
this.listenCommonEvent();
},
mounted () {
const erd = elementResizeDetectorMaker();
const bodyDom = document.body;
erd.listenTo(bodyDom, (element) => {
this.chatBoxHeight = element.clientHeight - 113; // 57+56
});
document.addEventListener('click', this.handleOutsideClick);
const sDom = document.querySelector('.client-chat');
sDom.addEventListener('scroll', () => {
if (this.msgList[this.msgList.length - 1].is_final === false && !this.jsScrolling) {
this.userScrolling = true;
} else {
this.jsScrolling = false;
}
});
},
beforeDestroy () {
//
document.removeEventListener('click', this.handleOutsideClick);
},
methods: {
openSearchUrl (refer, index) {
window.open(refer.url);
},
// /ws
listenClientAndManageEvent () {
//
let cachedConfig = null;
if (ACCESS_TYPE === 'ws') {
cachedConfig = this.$clientData.getConfigInfo();
} else {
cachedConfig = this.$SseCls.sseQueryConfigInfo();
}
if (cachedConfig) {
this.robotName = cachedConfig.name;
}
//
this.$eventHub.$on('client_msgContentChange', (res) => {
const { chatsContent, type } = res;
// PS $clientData ws
this.renderMsgList(chatsContent, type);
});
},
// ws
listenCommonEvent () {
this.$eventHub.$on('data_history', () => {
this.historyLoading = false;
});
this.$eventHub.$on('data_historyError', () => {
this.historyLoading = false;
});
},
//
renderMsgList (data, type) {
// ws
const noScrollEvt = [MESSAGE_TYPE.HISTORY, MESSAGE_TYPE.STOP, MESSAGE_TYPE.WORKBENCH_HISTORY, MESSAGE_TYPE.FEEDBACK];
const list = data.map(el => {
return { ...el, showPop: true };
});
this.msgList = clone(list);
// console.log('=======list========', clone(list));
// ws
this.$nextTick(() => {
const sDom = document.querySelector('.client-chat');
if (!sDom) return;
if (!this.userScrolling && (!noScrollEvt.includes(type))) {
this.jsScrolling = true;
scrollToBottom(sDom, sDom.scrollHeight);
}
if (this.msgList.length > 0 && this.msgList[this.msgList.length - 1].is_final === true) {
this.userScrolling = false;
}
});
},
handleOutsideClick (event) {
if (!this.oldPopDemo) { return; };
const firstElement = document.getElementsByClassName('pop-demo')[0];
if (this.oldPopDemo.contains(event.target) || firstElement.contains(event.target)) {
} else {
if (this.oldPopDemo) {
this.$refs['first'] && this.$refs['first'].unbindTrigger(this.oldPopDemo);
}
//
this.$refs['first'] && this.$refs['first'].hide();
this.oldPopDemo = null;
}
},
littleTagClick (e, r) {
const findMsg = this.$clientData.getMsgById(r);
let innerDome = e.querySelectorAll('.little-tags');
let outerTextArr = [];
if (innerDome && innerDome.length > 0) {
innerDome.forEach(dom => {
outerTextArr.push(dom.outerText);
});
}
this.popperList = findMsg.references.filter(e => outerTextArr.includes(e.id));
if (e) {
this.$refs['first'] && this.$refs['first'].bindTrigger(e, 'manual');
this.$refs['first'] && this.$refs['first'].update();
this.$refs['first'] && this.$refs['first'].show();
this.oldPopDemo = e;
}
}
}
};
</script>
<style lang="scss">
.client-chat::-webkit-scrollbar {
display: none;
}
.pop-demo{
// background-color: pink;
padding: 10px;
display: flex;
min-width: var(--size-l);
padding: var(--spacing-base);
flex-direction: column;
justify-content: center;
// align-items: center;
gap: var(--spacing-tight);
border-radius: var(--radius-normal);
border: 0.5px solid var(--color-border-normal);
background: var(--color-bg-2);
/* shadow/--shadow-medium */
box-shadow: var(--shadow-medium-x-1) var(--shadow-medium-y-2) var(--shadow-medium-blur-1) var(--shadow-medium-spread-1) var(--shadow-medium-color-1), var(--shadow-medium-x-2) var(--shadow-medium-y-2) var(--shadow-medium-blur-2) var(--shadow-medium-spread-2) var(--shadow-medium-color-2), var(--shadow-medium-x-3) var(--shadow-medium-y-3) var(--shadow-medium-blur-3) var(--shadow-medium-spread-3) var(--shadow-medium-color-3);
.v-popper__arrow{
display: block;
}
.pop-demo-list{
color: var(--color-link-normal);
/* caption/--caption-regular */
font-family: var(--font-family-normal);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
.v-button {
text-decoration: none;
text-align: left;
}
}
}
.client-chat {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: overlay;
padding: 0 12px;
.loading {
margin: 1em 0;
width: 150px;
&:after {
content: ".";
animation: ellipsis 1.5s steps(1, end) infinite;
}
}
@keyframes ellipsis {
0% {
content: ".";
}
33% {
content: "..";
}
66% {
content: "...";
}
100% {
content: ".";
}
}
.qa-item {
display: flex;
flex-direction: column;
margin-bottom: 16px;
font-weight: 400;
font-size: 14px;
color: var(--color-text-primary);
.timestamp {
font-weight: 400;
font-size: 12px;
line-height: 16px;
text-align: center;
color: var(--color-text-caption);
margin: 16px 0;
}
.question-item {
display: flex;
align-items: center;
width: fit-content;
text-align: center;
align-self: flex-end;
padding-left: 44px;
.qs-error {
min-width: 16px;
margin-right: 10px;
color: var(--color-error-normal);
}
.qs-loading {
margin-right: 10px;
}
.question-text {
background: #DBE8FF;
border-radius: 6px;
padding: 0 12px;
text-align: left;
word-break: break-all;
word-wrap: break-word;
code {
white-space: break-spaces;
}
img {
max-width: 80%;
}
}
}
.summary-item {
align-self: center;
margin: 12px 0;
}
.answer-item {
display: flex;
.contacter-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.answer-info {
position: relative;
display: flex;
flex-direction: column;
padding: 12px;
background: #F4F5F7;
border-radius: 6px;
width: calc(100% - 67px);
.answer-expand {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
width: 44px;
height: 24px;
margin-bottom: 12px;
background: var(--color-bg-2);
box-shadow: var(--shadow-small-light);
border-radius: 16px;
align-self: center;
}
.stop-ws {
color: var(--color-text-caption);
margin-left: 5px;
}
.answer-source {
margin: 12px 0;
font-size: 14px;
color: var(--color-text-caption);
text-align: left;
.v-button {
text-decoration: none;
text-align: left;
}
}
}
}
}
.qa-item:last-child {
padding-bottom: 120px;
}
}
</style>

232
components/GPT/components/pending.vue

@ -0,0 +1,232 @@
<template>
<view class="pending">
{{title}}
</view>
</template>
<script>
export default {
name: 'Pending',
data() {
return {
timeObj: null,
title: '正在思考中...'
};
},
mounted() {
this.init()
},
beforeDestroy() {
this.timeObj && clearInterval(this.timeObj);
},
methods: {
init() {
this.timeObj = setInterval(() => {
this.title = this.title == '正在思考中...' ? this.title === '正在思考中..' ? '正在思考中.' : '正在思考中..' : '正在思考中...'
}, 500)
}
}
};
</script>
<style lang="scss">
.client-chat::-webkit-scrollbar {
display: none;
}
.pop-demo {
// background-color: pink;
padding: 10px;
display: flex;
min-width: var(--size-l);
padding: var(--spacing-base);
flex-direction: column;
justify-content: center;
// align-items: center;
gap: var(--spacing-tight);
border-radius: var(--radius-normal);
border: 0.5px solid var(--color-border-normal);
background: var(--color-bg-2);
/* shadow/--shadow-medium */
box-shadow: var(--shadow-medium-x-1) var(--shadow-medium-y-2)
var(--shadow-medium-blur-1) var(--shadow-medium-spread-1)
var(--shadow-medium-color-1),
var(--shadow-medium-x-2) var(--shadow-medium-y-2)
var(--shadow-medium-blur-2) var(--shadow-medium-spread-2)
var(--shadow-medium-color-2),
var(--shadow-medium-x-3) var(--shadow-medium-y-3)
var(--shadow-medium-blur-3) var(--shadow-medium-spread-3)
var(--shadow-medium-color-3);
.v-popper__arrow {
display: block;
}
.pop-demo-list {
color: var(--color-link-normal);
/* caption/--caption-regular */
font-family: var(--font-family-normal);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
.v-button {
text-decoration: none;
text-align: left;
}
}
}
.client-chat {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: overlay;
padding: 0 12px;
.loading {
margin: 1em 0;
width: 150px;
&:after {
content: ".";
animation: ellipsis 1.5s steps(1, end) infinite;
}
}
@keyframes ellipsis {
0% {
content: ".";
}
33% {
content: "..";
}
66% {
content: "...";
}
100% {
content: ".";
}
}
.qa-item {
display: flex;
flex-direction: column;
margin-bottom: 16px;
font-weight: 400;
font-size: 14px;
color: var(--color-text-primary);
.timestamp {
font-weight: 400;
font-size: 12px;
line-height: 16px;
text-align: center;
color: var(--color-text-caption);
margin: 16px 0;
}
.question-item {
display: flex;
align-items: center;
width: fit-content;
text-align: center;
align-self: flex-end;
padding-left: 44px;
.qs-error {
min-width: 16px;
margin-right: 10px;
color: var(--color-error-normal);
}
.qs-loading {
margin-right: 10px;
}
.question-text {
background: #dbe8ff;
border-radius: 6px;
padding: 0 12px;
text-align: left;
word-break: break-all;
word-wrap: break-word;
code {
white-space: break-spaces;
}
img {
max-width: 80%;
}
}
}
.summary-item {
align-self: center;
margin: 12px 0;
}
.answer-item {
display: flex;
.contacter-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.answer-info {
position: relative;
display: flex;
flex-direction: column;
padding: 12px;
background: #f4f5f7;
border-radius: 6px;
width: calc(100% - 67px);
.answer-expand {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
width: 44px;
height: 24px;
margin-bottom: 12px;
background: var(--color-bg-2);
box-shadow: var(--shadow-small-light);
border-radius: 16px;
align-self: center;
}
.stop-ws {
color: var(--color-text-caption);
margin-left: 5px;
}
.answer-source {
margin: 12px 0;
font-size: 14px;
color: var(--color-text-caption);
text-align: left;
.v-button {
text-decoration: none;
text-align: left;
}
}
}
}
}
.qa-item:last-child {
padding-bottom: 120px;
}
}
</style>

1047
components/GPT/index.vue

File diff suppressed because it is too large

BIN
components/GPT/utils/.DS_Store

Binary file not shown.

347
components/GPT/utils/ClientData.js

@ -0,0 +1,347 @@
import Vue from "vue";
import { MESSAGE_TYPE } from "./chat_constant";
import { getQueryVariable, generateRequestId, arrayUnique } from "./util";
import { v4 as uuidv4 } from "uuid";
import GLOBAL from "./global";
import Observer from "./observer";
const $e = Observer;
// const $s = this.$s;
let cache = null; // 缓存
let timeoutTasks = {}; // 超时任务管理
const msgSendTimeout = 2 * 60 * 1000; // 发送消息超时ms,此处超时默认为2min
class ClientData {
constructor(option) {
cache = {
session_id: "", // 会话ID
configInfo: null, // 配置信息
chatsContent: [], // 会话聊天内容
systemEvents: [], // 系统事件栈
transferInfo: {
transferStatus: false,
transferAvatar: "",
}, // 当前转人工状态
};
this.$s = null
}
init() {
// 获取基础配置
this.queryConfigInfo();
}
setAttr(options) {
this.$s =options.socketObj
}
// 获取基础配置
async queryConfigInfo() {
try {
const seatBizId = ''// getQueryVariable("seat_biz_id");
console.log("seatBizId", seatBizId);
const sessionInfo = await this.createSession();
console.log("createsession, res", sessionInfo);
if (sessionInfo.code === 0) {
cache.seat_biz_id = seatBizId;
cache.session_id = sessionInfo.data.session_id;
} else {
uni.showModal({
title: "获取会话ID失败,请重试",
icon: "none",
});
}
// 接着获取机器人基础信息
const botInfo = {
code: 0,
data: {
name: "测试机器人",
avatar:
"https://qbot-1251316161.cos.ap-nanjing.myqcloud.com/avatar.png",
is_available: true,
bot_biz_id: "1664519736704069632",
},
};
if (botInfo.data) {
cache.configInfo = botInfo.data;
cache.configInfo.session_id = sessionInfo.data.session_id;
$e.$emit("client_configChange", cache.configInfo);
} else {
uni.showModal({
title: "获取机器人信息失败",
icon: "none",
});
}
} catch (e) {
console.log("获取机器人信息失败", e);
uni.showModal({
title: "获取会话信息失败,请刷新页面重试",
icon: "none",
});
}
}
async createSession() {
const session_id = uuidv4();
return { code: 0, data: { session_id: session_id } };
}
// 消息上行事件(用户端)
triggerSendMsg =async (msg, type='text', status= true) => {
console.log("[triggerSendMsg]", msg, wx);
if (!cache.configInfo || !cache.configInfo.session_id) {
await this.queryConfigInfo();
}
const requestId = generateRequestId();
const params = {
request_id: requestId,
session_id: cache.configInfo ? cache.configInfo.session_id : 0,
is_msg_status: status
};
if (type == "text") {
params.content = msg;
} else if (type == "img") {
params.content = `![](${msg})`;
params.realContent = msg;
}
this.$s.emit("send", params, type);
}
// 监听token用量和详情事件
listenTokenStat() {
this.$s.on("token_stat", (data) => {
$e.$emit("token_state_change", data);
if (data.session_id !== cache.session_id) return; // 若新消息不属于当前机器人时,则不做处理
let loadingMsg = cache.chatsContent.find((el) => el.loading_message);
let loadingText = "思考中";
if (loadingMsg) {
if (data.procedures && data.procedures.length > 0) {
loadingText =
data.procedures[data.procedures.length - 1].title || "思考中";
}
let currentList = cache.chatsContent;
currentList.forEach((el) => {
if (el.loading_message) {
el.text = loadingText;
el.record_id = data.record_id;
el.tokens_msg = data;
// 只有标准模式加这个
if (GLOBAL.webimToken[0].pattern === "standard") {
el.is_final = false;
}
}
});
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type: MESSAGE_TYPE.ANSWER,
});
} else {
let findedMsg = cache.chatsContent.find(
(el) => el.record_id === data.record_id
);
if (!findedMsg) return;
findedMsg.tokens_msg = data;
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type: MESSAGE_TYPE.ANSWER,
});
}
});
}
// 组装消息队列数据
// 问题确认消息:根据request_id关联覆盖(服务端收到问题后的确认消息)
// 答案消息:倒序遍历插入(服务端答案消息)
assembleMsgContent(msgList, type) {
console.log("assembleMsgContent", msgList, type);
let newMsg = msgList;
if (type === MESSAGE_TYPE.QUESTION) {
// 发送的问题消息由前端临时插入消息队列
cache.chatsContent.push(newMsg);
} else if (type === MESSAGE_TYPE.ANSWER) {
if (cache.chatsContent.length < 1) {
cache.chatsContent.push(newMsg);
} else {
let currentList = cache.chatsContent;
timeoutTasks[newMsg.request_id] &&
clearTimeout(timeoutTasks[newMsg.request_id]);
if (currentList.length === 2 && newMsg.can_rating) {
currentList[0].transferRobot = true;
}
if (newMsg.transfer && newMsg.loading_message) {
currentList.pop();
currentList[currentList.length - 1].loading_message = false;
currentList[currentList.length - 1] = {
...newMsg,
...currentList[currentList.length - 1],
transfer: true,
transferRobot: false,
};
} else {
for (let i = currentList.length - 1; i >= 0; i--) {
const { transfer, quit, transferRobot } = currentList[i];
let tmp = {
...newMsg,
transfer,
quit,
transferRobot,
};
// 保留tokens_msg,防止覆盖
if (currentList[i].tokens_msg) {
tmp = { ...tmp, tokens_msg: currentList[i].tokens_msg };
}
// 保留thought 放置被覆盖
if (currentList[i].agent_thought) {
tmp = { ...tmp, agent_thought: currentList[i].agent_thought };
}
// 保留reference
if (currentList[i].references) {
tmp = { ...tmp, references: currentList[i].references };
}
// 答案消息流式输出覆盖(record_id)
if (newMsg.record_id === currentList[i].record_id) {
currentList[i] = tmp;
break;
}
// 服务端问题消息确认数据,覆盖前端插入的临时问题消息数据(request_id匹配 & 自己发出的问题消息)
if (
newMsg.request_id &&
newMsg.request_id === currentList[i].request_id &&
newMsg.is_from_self
) {
newMsg.is_loading = false; // 服务端确认收到问题消息,则去除”发送中“状态
currentList[i] = tmp;
// 非人工状态时, 并且用户发送的不是敏感消息。插入临时[正在思考中...]消息
if (!newMsg.is_evil && !cache.transferInfo.transferStatus) {
currentList.push({
loading_message: true,
is_from_self: false,
content: "",
from_avatar: cache.configInfo.avatar,
timestamp: Number(currentList[i].timestamp), // 精确到秒
});
}
break;
}
// 插入最新答案消息
if (Number(newMsg.timestamp) >= Number(currentList[i].timestamp)) {
if (currentList[i].loading_message) {
// 删除原来的[正在思考中...]消息
currentList[currentList.length - 1] = newMsg;
} else {
currentList.splice(i + 1, 0, newMsg);
}
break;
}
if (
i === 0 &&
Number(newMsg.timestamp) < Number(currentList[i].timestamp)
) {
currentList.splice(0, 0, newMsg);
}
}
}
}
} else if (type === MESSAGE_TYPE.HISTORY) {
let currentList = cache.chatsContent;
// 历史数据打上标签,无需展示”重新生成“和”停止生成“操作
msgList = msgList.map((r) => {
return {
...r,
is_history: true,
is_final: true,
};
});
if (currentList.length === 0) {
// 若消息队列为空(用户端,初始拉取历史记录,用做判断欢迎页展示场景)
cache.chatsContent = [].concat(msgList);
} else {
// 若消息队列不为空
let oldMsgCurrent = currentList[0];
let newMsgHistory = msgList[msgList.length - 1];
// 将历史数据拼装到消息队列中(按照时间戳重排数据)
if (Number(newMsgHistory.timestamp) < Number(oldMsgCurrent.timestamp)) {
cache.chatsContent = [].concat(msgList).concat(cache.chatsContent);
} else {
msgList.reverse().forEach((msg) => {
for (let i = 0; i < cache.chatsContent.length; i++) {
if (msg.record_id === cache.chatsContent[i].record_id) {
// 重复覆盖
cache.chatsContent[i] = msg;
break;
} else if (
Number(msg.timestamp) <= Number(cache.chatsContent[i].timestamp)
) {
cache.chatsContent.splice(i, 0, msg);
break;
} else if (
i === cache.chatsContent.length - 1 &&
Number(msg.timestamp) > Number(cache.chatsContent[i].timestamp)
) {
cache.chatsContent.splice(i + 1, 0, msg);
}
}
});
}
}
}
// 消息去重。同一record_id取最新,同时保留消息最早的时间戳
cache.chatsContent = arrayUnique(
cache.chatsContent,
"record_id",
"timestamp"
);
// 消息队列变更通知事件
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type,
});
}
// 修改指定msgId的消息内容
modifyMsgContent(msgId) {
const findedMsg = this.getMsgById(msgId);
if (findedMsg) {
findedMsg.is_final = true;
findedMsg.content = findedMsg.content.concat(
`<span class="stop-ws">| 已停止生成</span>`
);
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type: MESSAGE_TYPE.STOP, // ”停止生成“事件
});
}
}
// 根据msgId获取消息
getMsgById(msgId) {
const findedMsg = cache.chatsContent.find((r) => r.record_id === msgId);
return findedMsg;
}
// 根据msgId获取其关联问题消息
getQmsgById(msgId) {
let findedQmsg = null;
const findedMsg = this.getMsgById(msgId);
if (findedMsg) {
findedQmsg = cache.chatsContent.find(
(r) => r.record_id === findedMsg.related_record_id
);
}
return findedQmsg;
}
releaseCache() {}
destroy() {
// be careful to clear the cache to avoid errors
this.releaseCache();
}
}
export default ClientData;

50
components/GPT/utils/EventHub.js

@ -0,0 +1,50 @@
import Vue from 'vue';
class EventHub {
constructor (vm) {
this.vm = vm;
this.curVm = null;
this.events = {};
this.eventMapUid = {};
}
$register (vm) {
this.curVm = vm;
}
setEventMapUid (uid, type) {
if (!this.eventMapUid[uid]) {
this.eventMapUid[uid] = [];
}
this.eventMapUid[uid].push(type);
}
$on (type, fn) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(fn);
if (this.curVm instanceof this.vm) {
this.setEventMapUid(this.curVm._uid, type);
}
}
$emit (type, ...args) {
if (this.events[type]) {
this.events[type].forEach(fn => fn(...args));
}
}
$off (type, fn) {
if (fn && this.events[type]) {
const index = this.events[type].findIndex(f => f === fn);
if (index !== -1) {
this.events[type].splice(index, 1);
}
return;
}
delete this.events[type];
}
$offAll (uid) {
const curAllTypes = this.eventMapUid[uid] || [];
curAllTypes.forEach(type => this.$off(type));
delete this.eventMapUid[uid];
}
}
export default new EventHub(Vue);;

225
components/GPT/utils/audio.js

@ -0,0 +1,225 @@
// 心跳间隔
const HEART_BEAT_TIME = 15000;
// 心跳最大失败次数(超过此次数重连)
const HEART_BEAT_FAIL_NUM = 1;
// 重连间隔
const RECONNECT_TIME = 3000;
import { generateRequestId } from "./util";
export default class Audio {
constructor(option) {
this._options = option;
this.socket = null;
this.session_id = null;
this.selfCloseStatus = false
this.connectSocketTimeOut = null
}
getToken() {
return new Promise((resolve, reject) => {
console.log("-----开始请求token-----");
uni.request({
method: "GET",
dataType: "json",
// url: `http://192.168.124.118:8083/xcx/framework/agent/${this._options.agentId}`,
// url: `https://des.js-dyyj.com/getDemoToken?id=${this._options.agentId}`,
url: `https://des.dayunyuanjian.cn/getDemoToken?id=${this._options.agentId}`,
success: (res) => {
console.log("请求token成功", res);
resolve(res);
},
fail: (err) => {
wx.showToast({
title: "创建失败",
icon: "none",
});
console.log("请求token失败", err);
reject();
},
});
});
}
async init() {
return this.createSocket();
}
async createSocket(options) {
console.log("开始创建socket", options);
return new Promise(async (resolve, reject) => {
if (this.selfCloseStatus) {return}
// const origin = "wss://des.js-dyyj.com/xcx/tts-websocket";
const origin = "wss://des.dayunyuanjian.cn/xcx/tts-websocket";
// 建立连接
const socket = wx.connectSocket({
url: `${origin}`,
success: (e) => {
console.log("创建语音长链接成功", e);
},
complete: (e) => {
console.log("socket - complete", e);
},
});
this.socket = socket;
socket.onOpen((e) => {
console.log("socket.onOpen", e);
// 监听发送
options && options.complete && options.complete();
if (this.selfCloseStatus) {
this.destroy()
}
});
socket.onMessage((e) => {
const { data } = e;
if (data instanceof ArrayBuffer) {
this._options.onMessage && this._options.onMessage(data);
} else {
if ( Object.prototype.toString.call(data) === "[object String]" ) {
const tmp = JSON.parse(data);
if (Object.prototype.toString.call(tmp.type) === "[object Number]") {
this._options.onMessage && this._options.onMessage(tmp);
} else {
if (!this.session_id) {
this.session_id = tmp.data
}
}
}
}
// this.on(type, e);
this.createInter();
resolve();
});
// 失败
socket.onError((e) => {
console.log("websocket error 创建语音长链接报错", e);
//
});
// 关闭
socket.onClose((e) => {
console.log("websocket close 语音长链接关闭", e);
this.connectSocketTimeOut && clearTimeout(this.connectSocketTimeOut);
//非自动关闭重连
if (e.code != 1000) {
this.doConnectTimeout();
}
});
});
}
doConnectTimeout() {
if (this.selfCloseStatus){
// 主动退出 不在重连
return
}
// 重连一次
console.log("websocket 异常关闭 开始重连");
this.connectSocketTimeOut = setTimeout(() => {
this.createSocket({
complete: function (res) {
// this.reconnectLock = false;
},
});
}, RECONNECT_TIME);
}
send(e, t) {
console.log("开始请求 websocket send", e);
this.socket && this.socket.send(e);
}
getMsgData(e) {
if (e && typeof e == "string") {
const status = e.indexOf("42");
const index = e.indexOf("[");
if (status > -1 && index > -1) {
const txt = e.substring(index);
const item = JSON.parse(txt);
const [type, obj] = item;
const payload = obj.payload ? obj.payload : {};
const params = {
chatId: this._options.agentId,
type,
contentType: "text", //默认文字
timestamp: new Date().getTime(),
...payload,
};
// 缓存聊天记录
return params;
}
}
return null;
}
emit(type, text, contentType) {
console.log("emit", type, text);
switch (type) {
case "send":
// 发送消息
// 发送消息
// const socketParams = {
// request_id: generateRequestId()
// };
const socketParams = {
session_id: this.session_id,
message_id: generateRequestId(),
action: "ACTION_SYNTHESIS",
data: text,
};
this.send({ data: JSON.stringify(socketParams) }, contentType);
break;
}
}
// 监听
on(type, params) {
switch (type) {
case "reply":
// 回复结束
const tmpParams = {
...params,
name: this.robotObj.name,
headImage: this.robotObj.headImage,
};
if (
params &&
params.type === "reply" &&
!params.is_from_self &&
params.can_rating
) {
// 回复消息
console.log("reply回复内容", tmpParams.content, tmpParams);
this._options.onMessage && this._options.onMessage(tmpParams);
}
if (!params.is_from_self && params.is_final) {
if (this.failMsg[tmpParams.request_id]) {
this.failMsg[tmpParams.request_id].status = true;
// 已回复 状态为true
}
// 回复结束 写入缓存
// setMsgData(tmpParams);
}
// 回复
break;
}
}
createInter() {
if (this.timeoutObj) {
clearTimeout(this.timeoutObj);
}
this.timeoutObj = setTimeout(() => {
console.log(111111, this.socket)
this.socket && this.socket.send && this.socket.send({ data: 3 });
}, HEART_BEAT_TIME);
}
// 关闭socket
destroy() {
if (this.socket && this.socket.readyState == 1) {
this.socket && this.socket.close();
this.socket = null;
}
}
}

247
components/GPT/utils/audio2.js

@ -0,0 +1,247 @@
// 心跳间隔
const HEART_BEAT_TIME = 15000;
// 心跳最大失败次数(超过此次数重连)
const HEART_BEAT_FAIL_NUM = 1;
// 重连间隔
const RECONNECT_TIME = 3000;
import { generateRequestId1 } from "./util";
export default class Audio {
constructor(option) {
this._options = option;
this.socket = null;
this.session_id = null;
this.selfCloseStatus = false
this.connectSocketTimeOut = null
this.appkey = 'hi93syrOZPxi7AZr';
this.token = '66df6ddcb4b643f08450cdf77d3de768';
}
getToken() {
return new Promise((resolve, reject) => {
console.log("-----开始请求token-----");
uni.request({
method: "GET",
dataType: "json",
// url: `http://192.168.124.118:8083/xcx/framework/agent/${this._options.agentId}`,
url: `https://des.js-dyyj.com/getDemoToken?id=${this._options.agentId}`,
success: (res) => {
console.log("请求token成功", res);
resolve(res);
},
fail: (err) => {
wx.showToast({
title: "创建失败",
icon: "none",
});
console.log("请求token失败", err);
reject();
},
});
});
}
async init() {
return this.createSocket();
}
async createSocket(options) {
console.log("开始创建socket", options);
return new Promise(async (resolve, reject) => {
const origin = `wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1?token=${this.token}`;
// 建立连接
const socket = wx.connectSocket({
url: `${origin}`,
binaryType: 'arraybuffer',
success: (e) => {
console.log("创建语音长链接成功", e);
},
complete: (e) => {
console.log("socket - complete", e);
},
});
this.socket = socket;
socket.onOpen((e) => {
console.log("socket.onOpen", e);
// 监听发送
options && options.complete && options.complete();
const socketParams = {
header: {
name: 'StartSynthesis',
appkey: this.appkey,
message_id: '1c17d4f8e7894a20aecb9f774b54ba06', //generateRequestId1(),
task_id: '1c17d4f8e7894a20aecb9f774b54ba06',//generateRequestId1(),
namespace: 'FlowingSpeechSynthesizer'
},
payload: {
voice: 'xiaoyun',
format: 'pcm',
sample_rate: 16000
}
};
this.send({ data: JSON.stringify(socketParams) }, 'txt');
});
socket.onMessage((e) => {
const { data } = e;
if (data instanceof ArrayBuffer) {
this._options.onMessage && this._options.onMessage(data);
} else {
if ( Object.prototype.toString.call(data) === "[object String]" ) {
const tmp = JSON.parse(data);
if (Object.prototype.toString.call(tmp.type) === "[object Number]") {
this._options.onMessage && this._options.onMessage(tmp);
} else {
if (!this.session_id) {
this.session_id = tmp.data
}
}
}
}
// this.on(type, e);
this.createInter();
resolve();
});
// 失败
socket.onError((e) => {
console.log("websocket error 创建语音长链接报错", e);
//
});
// 关闭
socket.onClose((e) => {
console.log("websocket close 语音长链接关闭", e);
this.connectSocketTimeOut && clearTimeout(this.connectSocketTimeOut);
//非自动关闭重连
if (e.code != 1000) {
this.doConnectTimeout();
}
});
});
}
doConnectTimeout() {
if (this.selfCloseStatus){
// 主动退出 不在重连
return
}
// 重连一次
console.log("websocket 异常关闭 开始重连");
this.connectSocketTimeOut = setTimeout(() => {
this.createSocket({
complete: function (res) {
// this.reconnectLock = false;
},
});
}, RECONNECT_TIME);
}
send(e, t) {
console.log("开始请求 websocket send", e);
this.socket && this.socket.send(e);
}
getMsgData(e) {
if (e && typeof e == "string") {
const status = e.indexOf("42");
const index = e.indexOf("[");
if (status > -1 && index > -1) {
const txt = e.substring(index);
const item = JSON.parse(txt);
const [type, obj] = item;
const payload = obj.payload ? obj.payload : {};
const params = {
chatId: this._options.agentId,
type,
contentType: "text", //默认文字
timestamp: new Date().getTime(),
...payload,
};
// 缓存聊天记录
return params;
}
}
return null;
}
emit(type, text, contentType) {
console.log("emit", type, text, generateRequestId1());
switch (type) {
case "send":
// 发送消息
// 发送消息
// const socketParams = {
// request_id: generateRequestId1()
// };
const socketParams = {
header: {
name: 'RunSynthesis',
appkey: this.appkey,
namespace: 'FlowingSpeechSynthesizer',
message_id: '1c17d4f8e7894a20aecb9f774b54ba06',
task_id: '1c17d4f8e7894a20aecb9f774b54ba06', // generateRequestId1(),
},
payload: {
text: text
}
// action: "ACTION_SYNTHESIS",
// data: text,
};
this.send({ data: JSON.stringify(socketParams) }, contentType);
break;
}
}
// 监听
on(type, params) {
switch (type) {
case "reply":
// 回复结束
const tmpParams = {
...params,
name: this.robotObj.name,
headImage: this.robotObj.headImage,
};
if (
params &&
params.type === "reply" &&
!params.is_from_self &&
params.can_rating
) {
// 回复消息
console.log("reply回复内容", tmpParams.content, tmpParams);
this._options.onMessage && this._options.onMessage(tmpParams);
}
if (!params.is_from_self && params.is_final) {
if (this.failMsg[tmpParams.request_id]) {
this.failMsg[tmpParams.request_id].status = true;
// 已回复 状态为true
}
// 回复结束 写入缓存
// setMsgData(tmpParams);
}
// 回复
break;
}
}
createInter() {
if (this.timeoutObj) {
clearTimeout(this.timeoutObj);
}
this.timeoutObj = setTimeout(() => {
console.log(111111, this.socket)
this.socket && this.socket.send && this.socket.send({ data: 3 });
}, HEART_BEAT_TIME);
}
// 关闭socket
destroy() {
if (this.socket && this.socket.readyState == 1) {
this.socket && this.socket.close();
this.socket = null;
}
}
}

12
components/GPT/utils/chat_constant.js

@ -0,0 +1,12 @@
// type: Q-问题,A-答案,H-历史消息,S-停止生成,C-结束会话,T-转接会话,R-参考来源,F-点赞/点踩回执
export const MESSAGE_TYPE = {
QUESTION: 'Q',
ANSWER: 'A',
HISTORY: 'H',
STOP: 'S',
CLOSE: 'C',
TRANSFER: 'T',
REFERENCE: 'R',
FEEDBACK: 'F',
WORKBENCH_HISTORY: 'WH'
};

8
components/GPT/utils/global.js

@ -0,0 +1,8 @@
wx.GLOBE = {
webimToken: [],
SOCKET: null,
}
export default wx.GLOBE

56
components/GPT/utils/message.js

@ -0,0 +1,56 @@
/**
* !插入数据
* @param roomId
* @param sourceId
* @param orderId
* @param type
* @param fileId
* @param txt
* @param time
* @param userId
* @param nickName
* @param sendOrReceive
* @param address
*/
export const setMsgData = data => {
console.log('setMsgData', data)
const resData = data
const session_id = resData.chatId // 群id
let msgData = wx.getStorageSync('imMsgData') || {}
// * 插入群数据
if (msgData[session_id]) {
if (resData.timestamp > msgData[session_id].timestamp) {
msgData[session_id].timestamp = resData.timestamp
}
msgData[session_id].listMsg.push(resData)
} else {
msgData[session_id] = {
listMsg: [resData],
timestamp: resData.timestamp,
session_id: resData.session_id,
}
}
wx.setStorageSync('imMsgData', msgData)
return resData
}
// ! 获取群消息
export const getHistroyMsg = id => {
let msgData = wx.getStorageSync('imMsgData') || {}
let msgList = []
if (msgData[id]) {
const data = msgData[id]
// *处理历史消息并按时间排序
const compare = property => {
return function(a, b) {
var value1 = a[property]
var value2 = b[property]
return value1 - value2
}
}
msgList = data.listMsg.sort(compare('time'))
}
return { msgList }
}

45
components/GPT/utils/observer.js

@ -0,0 +1,45 @@
import GLOBAL from './global';
class Observer {
constructor() {
this._event = {}
}
$on(eventName, handler) {
if (this._event[eventName]) {
this._event[eventName].push(handler)
} else {
this._event[eventName] = [handler]
}
}
$emit(eventName) {
let events = this._event[eventName]
let otherArgs = Array.prototype.slice.call(arguments, 1)
let that = this
if (events) {
events.forEach(event => {
event.apply(that, otherArgs)
})
}
}
$off(eventName, handler) {
let events = this._event[eventName]
if (events) {
this._event[eventName] = events.filter(event => {
return event !== handler
})
}
}
$once(eventName, handler) {
let that = this
function func() {
let args = Array.prototype.slice.call(arguments, 0)
handler.apply(that, args)
this.off(eventName, func)
}
this.on(eventName, func)
}
}
if (!GLOBAL.observer) {
GLOBAL.observer = new Observer()
}
export default GLOBAL.observer

168
components/GPT/utils/pcm-player.js

@ -0,0 +1,168 @@
export default class PcmPlayer {
constructor(options) {
this._options = options;
this.audioCtx = null;
this.rate = this._options.rate ?? 16000;
this.ch = this._options.ch ?? 1;
this.fadeMs = this._options.fadeMs ?? 20;
this.fadeSamples = (this.rate * this.fadeMs) / 1000;
this.queue = [];
this.isPlaying = false;
this.startTime = 0;
this._t = null;
this.AllTexts = [];
this.totalLen = 0
}
feed(data) {
if (!this.audioCtx) {
this.audioCtx = wx.createWebAudioContext();
}
this.queue.push(new Int16Array(data));
if (!this.isPlaying) this._play();
}
loadAudio = (url) => {
return new Promise((resolve) => {
wx.request({
url,
responseType: "arraybuffer",
success: (res) => {
console.log("res.data", res.data);
audioCtx.decodeAudioData(
res.data,
(buffer) => {
resolve(buffer);
},
(err) => {
console.error("decodeAudioData fail", err);
reject();
}
);
},
fail: (res) => {
console.error("request fail", res);
reject();
},
});
});
};
async _play() {
this.isPlaying = true;
if (!this.audioCtx) {
return;
}
// 开始播放
this.startTime = this.audioCtx.currentTime ?? 0;
setTimeout(() => {
this.syncLoop();
});
while (this.queue.length) {
const pcm = this.queue.shift();
const len = pcm.length / this.ch;
const buf = this.audioCtx.createBuffer(this.ch, len, this.rate);
// 1. 归一化(防溢出)
for (let c = 0; c < this.ch; c++) {
const data = buf.getChannelData(c);
for (let i = 0; i < len; i++) {
const val = pcm[i * this.ch + c];
data[i] = val > 0 ? val / 0x7fff : val / 0x8000;
}
}
// 2. 帧边界淡入淡出(防不连续滋滋)
const fadeSamples = Math.min(this.fadeSamples, len); // 5ms@16kHz
const data = buf.getChannelData(0);
for (let j = 0; j < fadeSamples; j++) {
const gain = j / fadeSamples;
data[j] *= gain; // fade-in
data[len - 1 - j] *= gain; // fade-out
}
// 3. 播放并等待结束
await new Promise((resolve, reject) => {
if (!this.isPlaying) {return reject()}
const src = this.audioCtx.createBufferSource();
src.buffer = buf;
src.connect(this.audioCtx.destination);
src.onended = resolve;
src.start();
});
}
this.isPlaying = false;
}
// 监听音频播放进度
syncLoop() {
const loop = () => {
if (!this.audioCtx) {
return;
}
const now = this.audioCtx.currentTime - this.startTime;
// 找当前字幕
const index = this.AllTexts.findIndex(
(s) => now >= s.beginTime && now < s.endTime
);
if (index > -1) {
const obj = this.AllTexts[index];
if (this.AllTexts.length == 1) {
// 最后一条数据
obj.is_final = true;
}
const tmp = this.AllTexts.shift();
this._options.onPlay && this._options.onPlay(tmp);
if (!this.AllTexts.length) {
clearTimeout(this._t);
return;
}
}
if (!this.AllTexts.length) {
this._t = setTimeout(loop, 16);
} else {
if (now < this.AllTexts[this.AllTexts.length - 1].endTime) {
this._t = setTimeout(loop, 16);
} else {
// 结束
clearTimeout(this._t);
console.log("语音播放结束");
this.destroy();
}
}
};
loop();
}
// 增加文字
addText(res) {
if (res.result && res.result.subtitles && res.result.subtitles.length) {
const tmpText = res.result.subtitles.map((it) => {
return {
...it,
content: it.text,
beginTime: it.beginTime / 1000,
endTime: it.endTime / 1000,
request_id: res.requestId,
sessionId: res.sessionId,
};
});
this.AllTexts = [...this.AllTexts, ...tmpText];
}
}
close() {
this.isPlaying = false;
this.audioCtx.close();
}
destroy() {
this.queue = [];
this.isPlaying = false;
this.audioCtx && this.audioCtx.close();
this.audioCtx = null;
this.AllTexts = [];
this.startTime = 0;
this._options.onAudioEnd && this._options.onAudioEnd();
}
}

322
components/GPT/utils/socket.js

@ -0,0 +1,322 @@
import Vue from "vue";
import GLOBAL_OBJ from "./global";
import "./EventHub";
import { setMsgData } from "./message";
// 心跳间隔
const HEART_BEAT_TIME = 15000;
// 心跳最大失败次数(超过此次数重连)
const HEART_BEAT_FAIL_NUM = 1;
// 重连间隔
const RECONNECT_TIME = 3000;
export default class Socket {
constructor(option) {
this.socket = null;
this._options = option;
this.timeoutObj = null;
this.robotObj = {};
this.selfCloseStatus = false
this.reconnectLock = false
console.log("Socket init", GLOBAL_OBJ);
// 失败回答
this.failMsg = {}
}
getToken() {
return new Promise((resolve, reject) => {
console.log('-----开始请求token-----')
uni.request({
method: "GET",
dataType: "json",
// url: `http://192.168.124.118:8083/xcx/framework/agent/${this._options.agentId}`,
// url: `https://des.js-dyyj.com/getDemoToken?id=${this._options.agentId}`,
url: `https://des.dayunyuanjian.cn/getDemoToken?id=${this._options.agentId}`,
success: (res) => {
console.log("请求token成功", res);
resolve(res);
},
fail: (err) => {
wx.showToast({
title: "创建失败",
icon: "none",
});
console.log("请求token失败", err);
reject()
},
});
});
}
async init() {
return this.createSocket();
}
async createSocket(options) {
return new Promise(async (resolve, reject) => {
const origin = "wss://wss.lke.cloud.tencent.com";
// const origin = "wss://des.dayunyuanjian.cn";
let path = "/v1/qbot/chat/conn/";
let initSocket = 1;
let mainToken
const res = await this.getToken();
if (
res &&
res.data &&
res.data.apiResponse &&
res.data.apiResponse.Token
) {
mainToken = res.data.apiResponse.Token;
}
// 机器人信息
this.robotObj = res.data.requestInfo;
console.log("获取token:", mainToken);
if (this.selfCloseStatus) {return}
// 建立连接
const socket = wx.connectSocket({
url: `${origin}${path}?EIO=4&transport=websocket`,
success: (e) => {
console.log("创建长链接成功", e);
},
complete: (e) => {
console.log("socket - complete", e);
},
});
this.socket = socket;
GLOBAL_OBJ.SOCKET = this;
socket.onOpen((e) => {
// 监听发送
if (initSocket === 1) {
const token = mainToken || "";
if (token) {
// cb({ token: token });
this.send({
data:
"40" +
JSON.stringify({
token: token,
}),
});
} else {
// cb({ token: '' });
this.send({
data: JSON.stringify({
token: "",
}),
});
}
initSocket++;
} else {
const token = mainToken || "";
// cb({ token: token });
this.send({
data: JSON.stringify({
token: token,
}),
});
initSocket++;
}
options && options.complete && options.complete()
if (this.selfCloseStatus) {
this.destroy()
}
});
socket.onMessage((e) => {
console.log("socket.onMessage", e);
const { data } = e;
if (data == 2) {
// 触发浪涌
this.send({
data: '3',
});
}
const params = this.getMsgData(data);
const { type } = params ?? {};
this.on(type, params);
const num = "" + data.substring(0, 2);
if (num == "40") {
console.log("创建对话成功", e);
// 发送失败信息
this.sendFailMsg()
resolve();
// 链接成功
}
this.createInter();
});
// 失败
socket.onError((e) => {
console.log("websocket error 长链接报错", e);
// 失败重建
this.doConnectTimeout();
//
});
// 关闭
socket.onClose((e) => {
console.log("websocket close 长链接关闭", e);
this.connectSocketTimeOut && clearTimeout(this.connectSocketTimeOut);
//非自动关闭重连
if (e.code != 1000) {
this.doConnectTimeout();
}
});
});
}
doConnectTimeout() {
if (this.selfCloseStatus){
// 主动退出 不在重连
return
}
// 重连一次
console.log("websocket 异常关闭 开始重连");
this.connectSocketTimeOut = setTimeout(() => {
this.createSocket({
complete: function (res) {
// this.reconnectLock = false;
},
});
}, RECONNECT_TIME);
}
onConnect(e) {
console.log("websocket connect", e);
}
send(e, t) {
console.log("开始请求 websocket send", e);
this.socket && this.socket.send(e);
this.createInter();
}
sendFailMsg() {
const content = Object.values(this.failMsg)
const lastFailMsgIndex = content.findLastIndex(it => !it.status)
if (lastFailMsgIndex > -1) {
const msg = content[lastFailMsgIndex]
console.log('开始重新发送失败信息', msg)
// 重新发送失败的信息
this.send(msg.data.socketParams,msg.data.contentType)
}
}
getMsgData(e) {
if (e && typeof e == "string") {
const status = e.indexOf("42");
const index = e.indexOf("[");
if (status > -1 && index > -1) {
const txt = e.substring(index);
const item = JSON.parse(txt);
const [type, obj] = item;
const payload = obj.payload ? obj.payload : {};
const params = {
chatId: this._options.agentId,
type,
contentType: "text", //默认文字
timestamp: new Date().getTime(),
...payload,
};
// 缓存聊天记录
return params;
}
}
return null;
}
emit(type, params, contentType) {
console.log("emit", type, params);
const tmpParams = params
tmpParams.incremental = true
const data = {
//incremental:true,
payload: tmpParams
};
switch (type) {
case "send":
// 发送消息
const socketParams = { data: "42" + JSON.stringify(["send", data]) };
this.send(socketParams, contentType);
console.log(params.request_id + '发送内容', data)
if (!params.is_msg_status) {
// 不写入缓存中
return;
}
const msgParams = {
chatId: this._options.agentId,
contentType,
type,
timestamp: new Date().getTime(),
...params,
content: params.realContent ? params.realContent : params.content,
};
msgParams && setMsgData(msgParams);
this.failMsg[params.request_id] = {
data: {
socketParams,
contentType
},
statue: false
}
break;
}
}
// 监听
on(type, params) {
switch (type) {
case "reply":
// 回复结束
const tmpParams = {
...params,
name: this.robotObj.name,
headImage: this.robotObj.headImage,
};
if (
params &&
params.type === "reply" &&
!params.is_from_self &&
params.can_rating
) {
// 回复消息
console.log('reply回复内容', tmpParams.content, tmpParams)
this._options.onMessage && this._options.onMessage(tmpParams);
}
if (!params.is_from_self && params.is_final) {
if (this.failMsg[tmpParams.request_id]) {
this.failMsg[tmpParams.request_id].status = true
// 已回复 状态为true
}
// 回复结束 写入缓存
// setMsgData(tmpParams);
}
// 回复
break;
}
}
// 关闭socket
destroy() {
if (this.socket && this.socket.readyState == 1) {
this.socket && this.socket.close();
this.socket = null;
}
this.failMsg={}
}
createInter() {
if (this.timeoutObj) {
clearTimeout(this.timeoutObj);
}
return
this.timeoutObj = setTimeout(() => {
this.socket && this.socket.send && this.socket.send({ data: 3 });
}, HEART_BEAT_TIME);
}
}

132
components/GPT/utils/util.js

@ -0,0 +1,132 @@
/**
* 获取 location hash 参数
* @param {string} variable 参数名
* @returns {string} 参数值
*/
export const getQueryVariable = (variable) => {
const query = window.location.hash.split('?');
if (query.length < 2) {
return '';
}
const vars = query[1].split('&');
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=');
if (pair[0] === variable) {
return decodeURI(pair[1]);
}
}
return '';
};
/**
* 滚动至指定dom底部
*/
export const scrollToBottom = (sDom, sTop) => {
if (!sDom) return;
sDom.scrollTo({
top: sTop
// behavior: 'smooth'
});
};
/**
* 数组去重
*/
export const arrayUnique = (arr, replaceKey, holdKey) => {
let temp = {};
return arr.reduce((prev, cur) => {
if (!temp[cur[replaceKey]]) {
temp[cur[replaceKey]] = {index: prev.length};
prev.push(cur);
} else {
const oldItem = temp[cur[replaceKey]];
cur[holdKey] = oldItem[holdKey];
prev.splice(oldItem['index'], 1, cur);
}
return prev;
}, []);
};
export const generateRequestId1 = (length = 32) => {
const data =
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
let nums = '';
for (let i = 0; i < length; i++) {
const r = parseInt(Math.random() * 61, 10);
nums += data[r];
}
return nums //+ '-' + parseInt(Math.random() * 10000000000, 10);
};
export const generateRequestId = (length = 10) => {
const data =
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
let nums = '';
for (let i = 0; i < length; i++) {
const r = parseInt(Math.random() * 61, 10);
nums += data[r];
}
return nums + '-' + parseInt(Math.random() * 10000000000, 10);
};
function escapeHtml (str) {
return str.replace(/[&<>"'/]/g, function (match) {
return {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;'
}[match];
});
}
export const splitTextForTTS = (text, maxLength = 25) => {
// 定义优先分割的标点符号
const punctuation = ['。', ',', ';', '?', '!', ',', ';', '?', '!', '、'];
let segments = [];
let segment = '';
let tempSegment = '';
for (let i = 0; i < text.length; i++) {
tempSegment += text[i]; // 如果超过最大长度,则尝试在上一个标点符号处分割
if (tempSegment.length > maxLength) {
let lastPunctuationIndex = -1;
for (let j = tempSegment.length - 1; j >= 0; j--) {
if (punctuation.includes(tempSegment[j])) {
lastPunctuationIndex = j;
break;
}
} // 如果找到标点符号,则在标点符号后分割
if (lastPunctuationIndex !== -1) {
segments.push(tempSegment.slice(0, lastPunctuationIndex + 1).trim());
tempSegment = tempSegment.slice(lastPunctuationIndex + 1).trim();
} else {
// 如果没有找到标点符号,则在最大长度处分割
segments.push(tempSegment.slice(0, maxLength).trim());
tempSegment = tempSegment.slice(maxLength).trim();
}
}
} // 添加最后一个段落
if (tempSegment.length > 0) {
segments.push(tempSegment.trim());
}
return segments;
}

364
components/IPComponents.vue

@ -0,0 +1,364 @@
<template>
<view class="product-section">
<view class="title-section">
<div style="
display: flex;
align-items: center;
justify-content: space-between;
">
<text class="title">文化IP合作体</text>
</div>
</view>
<!-- 轮播容器 -->
<view class="carousel-container">
<!-- 左箭头 -->
<view class="nav-arrow left-arrow" @click="prevSlide" v-if="list.length > 1 && currentIndex > 0">
<image style="width: 50rpx;height: 50rpx;"
:src="showImg('/uploads/20250908/29beeddf1e45571d2c5a4187f2f1ae05.png')"></image>
</view>
<!-- 轮播内容 -->
<scroll-view class="carousel-scroll" scroll-x="true" :show-scrollbar="false" :enhanced="true"
:scroll-with-animation="true" :scroll-left="scrollLeft" @scroll="onScroll" @scrollend="onScrollEnd">
<view class="carousel-content">
<view class="carousel-item" v-for="(item, index) in list" :key="index" @click="gotoDetail(item,index)"
v-if="item && item.image">
<view class="issue-card">
<!-- 背景图片 -->
<image class="card-bg" :src="showImg(item.image)" mode="aspectFill"></image>
<view class="title-info">
<view class="title-item">
{{item.title}}
</view>
<view class="title-des">
{{item.des}}
</view>
</view>
</view>
</view>
<!-- 无数据时的提示 -->
<view v-if="!list || list.length === 0" class="no-data">
<text>暂无数据</text>
</view>
</view>
</scroll-view>
<!-- 右箭头 -->
<view class="nav-arrow right-arrow" @click="nextSlide"
v-if="list.length > 1 && currentIndex < list.length - 1">
<image style="width: 50rpx;height: 50rpx;"
:src="showImg('/uploads/20250908/6622b3699518d6b559e1241d7addb7af.png')"></image>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
list: [{
image: '/uploads/20250908/bb069c36e88de7cde4e8dd24ac3b33b5.png',
title: '太湖NAINO',
des: 'NAINO的异次元发现',
},
{
image: '/uploads/20250908/ab1ffc93875de79fc87ee2c5e70cad3f.png',
title: 'SUNRISE日出东方',
des: '扬州瓜洲古渡',
},
{
image: '/uploads/20250908/4d38fcd2e403a13b85a96188de12a2a8.png',
title: '中国昆曲博物馆',
des: '姹紫婿红里',
},
{
image: '/uploads/20250908/d4115d6c907f8b3ed5d30a11f9912460.png',
title: '退思园',
des: '苏州古典园林生活',
mini: {
appID: 'wxf3af8e268906fd6d',
path: 'pages/index/index'
}
}
],
currentIndex: 0,
scrollLeft: 0,
itemWidth: 224, // 207rpx + 30rpx margin
};
},
mounted() {
// this.getList();
},
methods: {
handleMoreClick() {
uni.switchTab({
url: '/pages/index/readingBody'
})
},
gotoDetail(item,index) {
uni.navigateTo({
url:`/subPackages/other/ipPoster?index=${index}&item=${encodeURIComponent(JSON.stringify(item))}`
})
},
getList() {
// this.Post(
// {
// type_id: 3,
// offset: 0,
// limit: 10, //
// },
// "/api/article/getArticleByType"
// ).then((res) => {
// if (res.data && res.data.length > 0) {
// this.list = res.data;
// //
// this.currentIndex = 0;
// this.scrollLeft = 0;
// }
// });
},
//
prevSlide() {
if (!this.validateData()) return;
if (this.list.length > 1) {
this.currentIndex =
this.currentIndex > 0 ? this.currentIndex - 1 : this.list.length - 1;
this.$nextTick(() => {
this.scrollToCurrentItem();
});
}
},
//
nextSlide() {
if (!this.validateData()) return;
if (this.list.length > 1) {
this.currentIndex =
this.currentIndex < this.list.length - 1 ? this.currentIndex + 1 : 0;
this.$nextTick(() => {
this.scrollToCurrentItem();
});
}
},
//
scrollToCurrentItem() {
//
if (this.currentIndex < 0) {
this.currentIndex = 0;
}
if (this.currentIndex >= this.list.length) {
this.currentIndex = this.list.length - 1;
}
const scrollPosition = this.currentIndex * this.itemWidth;
this.scrollLeft = scrollPosition;
},
//
onScroll(e) {
const scrollLeft = e.detail.scrollLeft;
const index = Math.round(scrollLeft / this.itemWidth);
if (
index !== this.currentIndex &&
index >= 0 &&
index < this.list.length
) {
this.currentIndex = index;
}
},
//
onScrollEnd(e) {
const scrollLeft = e.detail.scrollLeft;
const index = Math.round(scrollLeft / this.itemWidth);
//
this.currentIndex = Math.max(0, Math.min(index, this.list.length - 1));
this.$nextTick(() => {
this.scrollToCurrentItem();
});
},
//
handleItemClick(item) {
console.log("点击了卡片:", item);
//
},
//
validateData() {
if (!this.list || this.list.length === 0) {
this.currentIndex = 0;
this.scrollLeft = 0;
return false;
}
//
if (this.currentIndex < 0) {
this.currentIndex = 0;
}
if (this.currentIndex >= this.list.length) {
this.currentIndex = this.list.length - 1;
}
return true;
},
},
};
</script>
<style lang="scss" scoped>
.product-section {
width: 100%;
background-color: #fffdd6;
padding: 40rpx 25rpx;
margin: 30rpx 0;
border-radius: 40rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1),
0 0 0 1rpx rgba(255, 255, 255, 0.2) inset;
}
//
.carousel-container {
position: relative;
width: 100%;
height: 191rpx;
display: flex;
align-items: center;
justify-content: center;
}
//
.carousel-scroll {
width: 100%;
height: 100%;
white-space: nowrap;
}
//
.carousel-content {
display: flex;
align-items: center;
height: 100%;
min-width: max-content;
}
//
.carousel-item {
flex-shrink: 0;
width: 154rpx;
height: 191rpx;
margin: 0 7rpx;
will-change: transform;
}
//
.issue-card {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
//
.card-bg {
width: 100%;
height: 100%;
}
//
.nav-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 3;
transition: all 0.3s ease;
&.left-arrow {
left: 0rpx;
}
&.right-arrow {
right: 0rpx;
}
}
.arrow-icon {
font-size: 32rpx;
color: #ffffff;
font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
//
.no-data {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 368rpx;
color: #999;
font-size: 28rpx;
}
//
.title-section {
display: inline-block;
padding: 0rpx 0 30rpx;
width: 100%;
.title {
font-size: 34rpx;
font-weight: bold;
color: #000000;
}
.more-btn {
font-size: 26rpx;
color: #000000;
margin-left: 35rpx;
}
}
.title-info {
position: absolute;
bottom: 10rpx;
right: 0;
width: 100%;
color: white;
padding: 0 5rpx;
.title-item {
text-align: center;
font-size: 16rpx;
font-weight: bold;
overflow: hidden;
word-wrap: normal;
text-overflow: ellipsis;
}
.title-des {
margin-top: 5rpx;
text-align: center;
font-size: 14rpx;
overflow: hidden;
word-wrap: normal;
text-overflow: ellipsis;
}
}
</style>

4
components/MusicControl.vue

@ -113,7 +113,7 @@ export default {
.music-control {
position: fixed;
right: 30rpx;
bottom: 150rpx;
bottom: 210rpx;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
@ -121,7 +121,7 @@ export default {
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
z-index: 998;
}
.music-text {

407
components/ProductSection.vue

@ -0,0 +1,407 @@
<template>
<view class="product-section" :style="{'background-color':color}">
<!-- 商品标题区域 -->
<view class="title-section" >
<div style="display: flex; align-items: center;justify-content: space-between;" @click="handleMoreClick">
<text class="title">{{ title }}</text>
<view class="more-btn" v-if="type!=3">
<image src="https://des.dayunyuanjian.cn/data/2025/08/31/affb21f0-fcc1-4746-9543-0d54369aa315.png" style="width:124.6rpx;height: 36rpx;"/>
</view>
</div>
</view>
<!-- 商品列表 -->
<view class="product-list">
<view class="" v-if="type==3" @click="toCGCwx">
<image
style="height: 357rpx;width: 657rpx;border-radius: 20rpx;"
:src="showImg('/uploads/20250829/66732062c2d566802a7842525e9b92e6.png')"
mode="aspectFill"
></image>
</view>
<template v-else>
<view
class="product-card"
v-for="(item, index) in productList"
:key="index"
@click="handleProductClick(item)"
>
<view class="card-image-container">
<image
class="card-image"
:src="showImg(item.image)"
mode="aspectFill"
></image>
<!-- 图片蒙层 -->
<view class="image-overlay" v-if="type==1"></view>
<!-- 智能体标签 -->
<view class="content-box-info" v-if="item.agent" @click.stop="toAgent(item)">
<!-- 头像 -->
<image
class="avatar"
:src="showImg(item.agent.headImage)"
mode="aspectFill"
></image>
<view class="ai-tag">
<view
class="ai-label"
:style="{
borderColor: aiTagBorderColor,
color: aiTagTextColor,
}"
>智能体</view
>
</view>
<view class="ai-name">{{ item.agent.name }}</view>
</view>
</view>
<view class="card-content">
<view class="title-price-heart">
<view class="card-title">{{ item.title }}</view>
<view class="card-price">{{ item.price }} <text style="font-size:24rpx ;margin-left: 5rpx;" v-if="type==2"></text></view>
<template v-if="type==1">
<image
v-if="!item.type"
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
@click.stop="handleLikeClick(item, index)"
></image>
</template>
<!-- <template v-if="isFeel">
<image
v-if="!item.isShop"
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/195bfc195a54b93c13595a01a5d8bb3b.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/77c4546ac6415f9db69bb10888d2a975.png"
@click.isShop="handleLikeClick(item, index)"
></image>
</template> -->
</view>
</view>
</view>
</template>
</view>
</view>
</template>
<script>
export default {
name: "ProductSection",
props: {
//
title: {
type: String,
default: "商品列表",
},
//
productList: {
type: Array,
default: () => [],
},
//
moreUrl: {
type: String,
default: "",
},
//
detailUrlPrefix: {
type: String,
default: "/subPackages/techan/detail",
},
//
titleBgColor: {
type: String,
default: "#9efffa",
},
// AI
aiTagBorderColor: {
type: String,
default: "#01f1f196",
},
// AI
aiTagTextColor: {
type: String,
default: "#02fcfc",
},
type: {
type: Number,
default: 1,
},
color: {
type: String,
default: "#D3FCFF",
},
},
methods: {
//
handleMoreClick() {
if (this.moreUrl) {
uni.navigateTo({
url: this.moreUrl,
});
}
this.$emit("more-click");
},
//
handleProductClick(item) {
if (this.detailUrlPrefix) {
uni.navigateTo({
url: `${this.detailUrlPrefix}?id=${item.id}`,
});
}
this.$emit("product-click", item);
},
//
handleLikeClick(item, index) {
//
this.Post({
packageId: item.id,
type:!item.type
},
"/framework/benefitPackage/collect",'DES'
).then((res) => {
//
const updatedItem = { ...item, type: !item.type };
this.productList[index].type = !item.type
uni.showToast({
title: updatedItem.type ? "已收藏" : "取消收藏",
icon: "none",
duration: 1500,
});
this.$forceUpdate()
//
this.$emit("like-toggle", { item: updatedItem, index });
});
},
toCGCwx(){
wx.navigateToMiniProgram({
appId: 'wx9d68934300b1fe90',
path: 'pages/index/index',
envVersion: 'release',
success(res) {
//
},
fail(e){
console.log(e)
}
})
},
toAgent(e){
uni.navigateTo({
url: `/subPackages/other/evita?id=${e.agentId}&product=${e.benefitPackageId}`
});
},
//
showImg(img) {
if (!img) return;
if (img.indexOf("https://") != -1 || img.indexOf("http://") != -1) {
return img;
} else {
return this.$options._base.prototype.NEWAPIURLIMG + img;
}
},
},
};
</script>
<style lang="scss" scoped>
@font-face {
font-family: "Futura";
src: url(https://des.dayunyuanjian.cn/epicSoul/taozi/fonts/Futura.ttc);
}
.product-section {
width: 100%;
background-color: #D3FCFF;
padding:40rpx 25rpx;
margin: 30rpx 0;
border-radius: 40rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1),
0 0 0 1rpx rgba(255, 255, 255, 0.2) inset;
}
//
.title-section {
display: inline-block;
padding: 0rpx 0 30rpx;
width: 100%;
.title {
font-size: 34rpx;
font-weight: bold;
color: #000000;
}
.more-btn {
font-size: 26rpx;
color: #000000;
margin-left: 35rpx;
}
}
//
.product-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
//
.product-card {
background: #ffffff;
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1),
0 0 0 1rpx rgba(255, 255, 255, 0.2) inset;
overflow: hidden;
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
}
//
.card-image-container {
position: relative;
height: 320rpx;
overflow: hidden;
border-radius: 20rpx 20rpx 0 0 ;
}
.card-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 20rpx 20rpx 0 0 ;
}
//
.image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000047;
pointer-events: none;
border-radius: 20rpx;
}
//
.content-box-info {
position: absolute;
top: 30rpx;
right: 20rpx;
padding: 0rpx 20rpx;
text-align: center;
}
.ai-tag {
padding: 8rpx 0rpx;
gap: 8rpx;
}
.ai-label {
border: 1rpx solid;
padding: 0rpx 15rpx;
height: 40rpx;
line-height: 38rpx;
font-weight: bold;
font-size: 20rpx;
border-radius: 4rpx;
}
.ai-name {
font-size: 27rpx;
font-weight: bold;
color: #ffffff;
margin-left: 10rpx;
}
//
.avatar {
width: 119rpx;
height: 119rpx;
border-radius: 50%;
}
//
.card-content {
padding: 18rpx;
}
.title-price-heart {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.card-title {
font-size: 30rpx;
color: #000000;
line-height: 1.4;
flex: 1;
margin-right: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-price {
font-size: 30rpx;
color: #000000;
margin-right: 24rpx;
flex-shrink: 0;
min-width: 120rpx;
font-family: "Futura";
}
.heart-icon {
width: 35rpx;
height: 29rpx;
transition: all 0.3s ease;
flex-shrink: 0;
&.liked {
opacity: 1;
filter: hue-rotate(320deg) saturate(2);
}
&:active {
transform: scale(1.2);
}
}
.shop-icon {
width: 39rpx;
height: 36rpx;
transition: all 0.3s ease;
flex-shrink: 0;
margin-left: 10rpx;
&.liked {
opacity: 1;
filter: hue-rotate(320deg) saturate(2);
}
&:active {
transform: scale(1.2);
}
}
</style>

2
components/ShareGuide.vue

@ -1,7 +1,7 @@
<template>
<view v-if="modelValue" class="share-guide-mask" @click="closeGuide">
<view class="guide-content">
<image class="share-tips" src="https://static.ticket.sz-trip.com/epicSoul/share-tips.png" mode=""></image>
<image class="share-tips" src="https://des.dayunyuanjian.cn/epicSoul/share-tips.png" mode=""></image>
</view>
</view>
</template>

293
components/SwipeToNext使用文档.md

@ -1,293 +0,0 @@
# SwipeToNext 组件使用文档
## 组件介绍
`SwipeToNext` 是一个通用的触底跳转组件,封装了手势检测、延迟防抖、提示文字等功能,可以在任何需要滑动跳转的页面中使用。
## 组件特性
- ✅ 手势滑动检测
- ✅ 防误触发机制(延迟允许跳转)
- ✅ 可配置的滑动阈值
- ✅ 自定义提示文字
- ✅ 支持事件监听
- ✅ 完全可配置的参数
## 使用方法
### 1. 引入组件
```vue
<script>
import SwipeToNext from '@/components/SwipeToNext.vue';
export default {
components: {
SwipeToNext
}
}
</script>
```
### 2. 基础使用
```vue
<template>
<SwipeToNext
:is-last-slide="isLastSlide"
:target-path="'/next/page'"
@swipe-to-next="handleSwipeToNext"
>
<!-- 你的页面内容 -->
<view class="content">
<!-- 轮播图或其他内容 -->
</view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
isLastSlide: false
}
},
methods: {
// 处理swiper切换或其他逻辑
handlePageChange() {
// 根据你的逻辑设置 isLastSlide
this.isLastSlide = true; // 当到达最后一页时
},
handleSwipeToNext(targetPath) {
console.log('即将跳转到:', targetPath);
// 可以在这里添加额外的逻辑,如数据统计
}
}
}
</script>
```
### 3. 完整配置使用
```vue
<template>
<SwipeToNext
:is-last-slide="isLastSlide"
:target-path="'/next/page'"
:show-tip="true"
tip-text="向上滑动查看更多内容"
:swipe-threshold="100"
:delay-time="800"
:enable-delay="true"
@swipe-to-next="handleSwipeToNext"
>
<!-- 你的页面内容 -->
<view class="content">
<!-- 内容区域 -->
</view>
</SwipeToNext>
</template>
```
## Props 参数
| 参数名 | 类型 | 默认值 | 必填 | 说明 |
|--------|------|--------|------|------|
| `isLastSlide` | Boolean | `false` | ✅ | 是否在最后一页/最后一个状态 |
| `targetPath` | String | - | ✅ | 跳转的目标路径 |
| `showTip` | Boolean | `true` | ❌ | 是否显示提示文字 |
| `tipText` | String | `'继续向上滑动进入下一章节'` | ❌ | 提示文字内容 |
| `swipeThreshold` | Number | `80` | ❌ | 滑动阈值(像素) |
| `delayTime` | Number | `500` | ❌ | 延迟允许跳转的时间(毫秒) |
| `enableDelay` | Boolean | `true` | ❌ | 是否启用延迟机制 |
| `alwaysEnable` | Boolean | `false` | ❌ | 是否总是启用跳转(忽略isLastSlide状态) |
## Events 事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| `swipe-to-next` | `targetPath` | 触发跳转时的回调事件 |
## 使用场景示例
### 场景1:图片轮播页面
```vue
<template>
<SwipeToNext
:is-last-slide="currentIndex === images.length - 1"
:target-path="'/gallery/next'"
>
<swiper @change="handleSwiperChange">
<swiper-item v-for="(img, index) in images" :key="index">
<image :src="img" mode="aspectFit" />
</swiper-item>
</swiper>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
currentIndex: 0,
images: ['img1.jpg', 'img2.jpg', 'img3.jpg']
}
},
methods: {
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
}
}
}
</script>
```
### 场景2:文章阅读页面
```vue
<template>
<SwipeToNext
:is-last-slide="isReadComplete"
:target-path="'/article/next'"
tip-text="继续滑动阅读下一篇文章"
:swipe-threshold="60"
>
<scroll-view @scrolltolower="handleScrollToBottom">
<view class="article-content">
<!-- 文章内容 -->
</view>
</scroll-view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
isReadComplete: false
}
},
methods: {
handleScrollToBottom() {
// 滚动到底部时认为阅读完成
this.isReadComplete = true;
}
}
}
</script>
```
### 场景4:单张图片或总是启用触底跳转
```vue
<template>
<SwipeToNext
:always-enable="true"
:target-path="'/next/chapter'"
tip-text="向上滑动查看下一内容"
:enable-delay="false"
>
<view class="single-image">
<image :src="imageUrl" mode="aspectFit" />
</view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
imageUrl: 'single-image.jpg'
}
}
}
</script>
```
```vue
<template>
<SwipeToNext
:is-last-slide="currentStep === totalSteps - 1"
:target-path="'/guide/complete'"
tip-text="向上滑动完成引导"
>
<view class="guide-step">
<view class="step-content">
步骤 {{ currentStep + 1 }} / {{ totalSteps }}
</view>
<button @click="nextStep">下一步</button>
</view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
currentStep: 0,
totalSteps: 5
}
},
methods: {
nextStep() {
if (this.currentStep < this.totalSteps - 1) {
this.currentStep++;
}
}
}
}
</script>
```
## 特殊情况处理
### 单张图片问题
当只有一张图片时,传统的 `isLastSlide` 逻辑不适用。这时可以使用 `alwaysEnable` 参数:
```vue
<!-- 单张图片的解决方案 -->
<SwipeToNext
:always-enable="true"
:target-path="'/next/page'"
:enable-delay="false"
>
<image src="single-image.jpg" />
</SwipeToNext>
```
### 参数优先级
`alwaysEnable="true"` 时:
- 忽略 `isLastSlide` 的值
- 总是显示提示文字
- 总是允许触底跳转
- 建议设置 `enableDelay="false"` 以获得更好的响应速度
1. **确保正确设置 `isLastSlide`**:这是控制是否允许跳转的关键属性
2. **路径格式**:`targetPath` 需要是有效的 uni-app 路由路径
3. **性能考虑**:如果不需要延迟机制,可以设置 `enableDelay: false` 来提高响应速度
4. **样式覆盖**:组件内的提示文字样式可以通过全局样式覆盖
5. **事件监听**:建议监听 `swipe-to-next` 事件进行数据统计或其他操作
## 自定义样式
如果需要自定义提示文字的样式,可以在页面中添加:
```scss
// 覆盖组件样式
.swipe-to-next .bottom-tip {
bottom: 200rpx !important; // 调整位置
background: rgba(255, 255, 255, 0.9) !important; // 改变背景色
text {
color: #333 !important; // 改变文字颜色
font-size: 32rpx !important; // 改变字体大小
}
}
```
这个组件极大地简化了触底跳转功能的实现,让你可以专注于业务逻辑而不用重复编写相同的手势检测代码。

508
components/WaterfallLayout.vue

@ -0,0 +1,508 @@
<template>
<view class="waterfall-layout">
<!-- 空状态 -->
<view v-if="!leftItems.length && !rightItems.length" class="empty-state">
<text class="empty-title">暂无内容</text>
<text class="empty-desc">快来发布第一篇笔记吧</text>
</view>
<!-- 瀑布流内容 -->
<view v-else class="waterfall-container">
<!-- 左列 -->
<view class="column">
<view
v-for="(item, index) in leftItems"
class="waterfall-item"
@click="handleItemClick(index, leftItems)"
>
<view class="image-container">
<image
v-if="item.coverImage"
:src="item.coverImage && item.coverImage.split(',')[0]"
class="item-image"
mode="aspectFill"
/>
<!-- 状态蒙层 -->
<view
v-if="item.status === 0 || item.status === -1"
class="status-overlay"
>
<text class="status-text">{{
item.status === 0 ? "待审核" : "审核不通过"
}}</text>
</view>
</view>
<view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text>
<view class="item-footer">
<view class="user-info">
<image
:src="item.headImg"
class="user-avatar"
mode="aspectFill"
/>
<text class="username">{{ item.nickname }}</text>
</view>
<view
v-if="item.status !== 0 && item.status !== -1"
class="like-info"
@click.stop="handleLikeClick(item)"
>
<image
v-if="!item.userLiked"
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
style="height: 30rpx; width: 35rpx"
></image>
<image
v-else
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
style="height: 30rpx; width: 35rpx"
></image>
<text class="like-count">{{ item.likeCount || 0 }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 右列 -->
<view class="column">
<view
v-for="(item, index) in rightItems"
class="waterfall-item"
@click="handleItemClick(index, rightItems)"
>
<view class="image-container">
<image
v-if="item.coverImage"
:src="item.coverImage && item.coverImage.split(',')[0]"
class="item-image"
mode="aspectFill"
/>
<!-- 状态蒙层 -->
<view
v-if="item.status === 0 || item.status === -1"
class="status-overlay"
>
<text class="status-text">{{
item.status === 0 ? "待审核" : "审核不通过"
}}</text>
</view>
</view>
<view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text>
<view class="item-footer">
<view class="user-info">
<image
:src="item.headImg"
class="user-avatar"
mode="aspectFill"
/>
<text class="username"
>{{ item.nickname }}{{ item.nickname
}}{{ item.nickname }}</text
>
</view>
<view
v-if="item.status !== 0 && item.status !== -1"
class="like-info"
@click.stop="handleLikeClick(item)"
>
<image
v-if="!item.userLiked"
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
style="height: 30rpx; width: 35rpx"
></image>
<image
v-else
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
style="height: 30rpx; width: 35rpx"
></image>
<text class="like-count">{{ item.likeCount || 0 }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "WaterfallLayout",
props: {
//
items: {
type: Array,
default: () => [],
},
// 2
columnCount: {
type: Number,
default: 2,
},
// rpx
columnGap: {
type: Number,
default: 16,
},
// rpx
itemGap: {
type: Number,
default: 16,
},
},
data() {
return {
leftItems: [],
rightItems: [],
};
},
watch: {
items: {
handler(newItems) {
this.calculateLayout(newItems);
},
immediate: true,
deep: true,
},
},
mounted() {
this.calculateLayout(this.items);
},
methods: {
// /
handleLikeClick(item) {
if (!item || !item.id) {
uni.showToast({
title: "操作失败,笔记ID不存在",
icon: "none",
});
return;
}
// UI
const isLiked = !item.userLiked;
item.userLiked = isLiked;
item.likeCount = isLiked
? (item.likeCount || 0) + 1
: Math.max((item.likeCount || 0) - 1, 0);
// API
const url = isLiked
? `/framework/noteLike/add/${item.id}`
: `/framework/noteLike/cancel/${item.id}`;
this.Post({}, url, "DES")
.then((res) => {
if (res.code !== 200) {
// UI
item.userLiked = !isLiked;
item.likeCount = !isLiked
? (item.likeCount || 0) + 1
: Math.max((item.likeCount || 0) - 1, 0);
uni.showToast({
title: res.msg || "操作失败,请稍后重试",
icon: "none",
});
} else {
//
this.$emit("like-change", {
noteId: item.id,
isLiked: isLiked,
likeCount: item.likeCount,
});
//
uni.$emit("note-like-change", {
noteId: item.id,
isLiked: isLiked,
likeCount: item.likeCount,
});
}
})
.catch((err) => {
// UI
item.userLiked = !isLiked;
item.likeCount = !isLiked
? (item.likeCount || 0) + 1
: Math.max((item.likeCount || 0) - 1, 0);
uni.showToast({
title: err.msg || "网络异常,请稍后重试",
icon: "none",
});
});
},
// DOM
getColumnHeight(columnRef) {
if (!columnRef) return 0;
const query = uni.createSelectorQuery().in(this);
return new Promise((resolve) => {
query
.select(columnRef)
.boundingClientRect((data) => {
resolve(data ? data.height : 0);
})
.exec();
});
},
//
calculateLayout(items) {
if (!items || !items.length) {
this.leftItems = [];
this.rightItems = [];
return;
}
//
this.leftItems = [];
this.rightItems = [];
//
for (let i = 0; i < items.length; i++) {
this.addItem(items[i]);
}
},
//
addItem(item) {
//
if (this.leftItems.length <= this.rightItems.length) {
this.leftItems.push(item);
} else {
this.rightItems.push(item);
}
},
//
clearItems() {
this.leftItems = [];
this.rightItems = [];
this.$emit("items-cleared");
},
//
handleItemClick(index, list) {
this.$emit("item-click", list[index]);
},
//
getAllItems() {
return [...this.leftItems, ...this.rightItems];
},
//
removeItem(itemId) {
//
let index = this.leftItems.findIndex((item) => item.id === itemId);
if (index !== -1) {
this.leftItems.splice(index, 1);
this.$emit("item-removed", itemId);
return;
}
//
index = this.rightItems.findIndex((item) => item.id === itemId);
if (index !== -1) {
this.rightItems.splice(index, 1);
this.$emit("item-removed", itemId);
}
},
},
};
</script>
<style scoped>
.waterfall-layout {
width: 100%;
box-sizing: border-box;
}
/* 空状态样式 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 160rpx 40rpx;
text-align: center;
}
.empty-icon {
width: 240rpx;
height: 240rpx;
margin-bottom: 40rpx;
opacity: 0.6;
}
.empty-title {
font-size: 32rpx;
color: #666;
margin-bottom: 16rpx;
font-weight: 500;
}
.empty-desc {
font-size: 28rpx;
color: #999;
line-height: 1.4;
}
.waterfall-container {
display: flex;
gap: 16rpx;
padding: 0 20rpx;
box-sizing: border-box;
}
.column {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.waterfall-item {
box-sizing: border-box;
border-radius: 12rpx;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: transform 0.2s ease;
position: relative;
}
.waterfall-item:active {
transform: scale(0.98);
}
.item-image {
width: 100%;
height: 476rpx;
object-fit: cover;
}
.item-content {
padding: 16rpx;
}
.item-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
line-height: 1.3;
margin-bottom: 12rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-desc {
font-size: 24rpx;
color: #666;
line-height: 1.4;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-bottom: 16rpx;
}
.tag {
padding: 4rpx 12rpx;
background: #f5f5f5;
color: #666;
font-size: 20rpx;
border-radius: 12rpx;
white-space: nowrap;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
}
.user-info {
display: flex;
align-items: center;
gap: 12rpx;
}
.user-avatar {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
}
.username {
font-size: 22rpx;
color: #666;
width: 160rpx;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.like-info {
display: flex;
align-items: center;
gap: 6rpx;
padding: 4rpx 8rpx;
}
.like-icon {
font-size: 24rpx;
color: #ff6b6b;
}
.like-count {
font-size: 22rpx;
color: #666;
}
.image-container {
position: relative;
width: 100%;
overflow: hidden;
height: 476rpx;
}
/* 状态蒙层样式 */
.status-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
.status-text {
color: #ffffff;
font-size: 28rpx;
font-weight: 500;
padding: 10rpx 20rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 8rpx;
}
</style>

288
components/header.vue

@ -1,56 +1,306 @@
<template>
<view class="header" :style="{'height': height+'px','padding-top':statusBarHeight+'px'}">
<image src="https://static.ticket.sz-trip.com/epicSoul/readingBody/search.png" mode="heightFix"
@click="gotoPath('/subPackages/search/search?type='+type)" v-if="isSearch"></image>
<view style="width: 50rpx;" v-else></view>
<image src="https://static.ticket.sz-trip.com/uploads/20250625/9bb05097e07570a934235983e1681a9f.png" mode="heightFix"></image>
<view style="width: 50rpx;"></view>
<view>
<!-- 占位区域防止内容塌陷 -->
<view v-if="fixed" class="header-placeholder" :style="{ height: height + 'px' }"></view>
<view
class="header"
:class="{ 'header-fixed': fixed }"
:style="{ height: height + 'px', 'padding-top': statusBarHeight + 'px' }"
>
<!-- 左侧地区筛选和搜索 -->
<view class="left-section" v-if="isSearch">
<!-- 使用省市区选择组件 -->
<!-- 原有的简单地区选择 -->
<view v-if="isLocation&&address.cityId ">
<AreaPicker
:defaultValue="{
provinceId: address.provinceId,
cityId: address.cityId,
areaId:address.areaId
}"
ref="areaPicker3"
placeholder="请选择省市区"
:selectedText="selectedText"
@change="changeAddress"
>
<template v-slot="{ selectedText, placeholder, currentSelection }">
<view class="location-selector">
<text class="location-text">{{ selectedText }}</text>
<image
class="dropdown-icon"
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEyIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0xIDFMNiA2TDExIDEiIHN0cm9rZT0iIzk5OTk5OSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+"
mode="heightFix"
></image>
</view>
</template>
</AreaPicker>
</view>
<!-- <image
class="search-icon"
src="https://des.dayunyuanjian.cn/epicSoul/readingBody/search.png"
mode="heightFix"
@click="gotoPath('/subPackages/search/search?type=' + type)"
></image> -->
<uni-icons @click="handleBack" v-if="isBack" type="left" size="28"></uni-icons>
</view>
<view class="left-section" v-else></view>
<!-- 中间Logo -->
<view class="logo">
<image
class=""
:src="showImg('/uploads/20250910/fff8aa38c1338a9bfceaf33763e9f4c6.png')"
mode="heightFix"
></image>
</view>
<!-- 右侧占位 -->
<view class="right-section"></view>
</view>
</view>
</template>
<script>
export default {
import AreaPicker from './AreaPicker.vue'
export default {
components: {
AreaPicker
},
props: {
isSearch: {
type: Boolean,
default: true
default: true,
},
isBack: {
type: Boolean,
default: true,
},
address: {
type: Object,
default: () =>{},
},
type: {
type: String,
default: "",
},
fixed: {
type: Boolean,
default: false,
},
isLocation: {
type: Boolean,
default: false,
},
selectedText: {
type: String,
default: ''
},
// 使
isAreaPicker: {
type: Boolean,
default: true,
},
//
areaPlaceholder: {
type: String,
default: '请选择地区'
},
//
defaultAreaValue: {
type: Object,
default: () => ({
provinceId: null,
cityId: null,
areaId: null
})
}
},
name:"header",
name: "header",
data() {
return {
//
height: 0,
statusBarHeight: 0,
//
areaPickerVisible: false,
selectedAreaText: ''
};
},
mounted() {
this.initRectInfo()
this.initRectInfo();
},
methods: {
handleBack(){
uni.switchTab({
url:'/pages/index/index'
})
},
methods:{
initRectInfo () {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight
initRectInfo() {
const sysInfo = uni.getSystemInfoSync();
this.statusBarHeight = sysInfo.statusBarHeight;
//
this.height = sysInfo.statusBarHeight + 40
console.log("sysInfo" ,sysInfo)
this.height = sysInfo.statusBarHeight + 40;
console.log("sysInfo", sysInfo);
},
showLocationPicker() {
//
uni.showActionSheet({
itemList: ["苏州", "上海", "杭州", "南京", "无锡"],
success: (res) => {
const locations = ["苏州", "上海", "杭州", "南京", "无锡"];
this.selectedText = locations[res.tapIndex];
//
this.$emit("locationChange", this.selectedText);
},
});
},
changeAddress(e){
this.$emit('change',e)
},
//
showAreaPicker() {
this.areaPickerVisible = true;
},
//
onAreaChange(areaData) {
this.selectedAreaText = areaData.fullText;
//
this.$emit('areaChange', areaData);
},
//
getAreaValue() {
if (this.$refs.areaPicker) {
return this.$refs.areaPicker.getValue();
}
return null;
},
//
resetArea() {
this.selectedAreaText = '';
if (this.$refs.areaPicker) {
this.$refs.areaPicker.reset();
}
},
// AreaPicker Post
Post(data, url) {
//
// mixin
return this.$parent.Post ? this.$parent.Post(data, url) : Promise.reject('Post method not found');
}
},
};
</script>
<style scoped lang="scss">
.header{
.header {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-around;
justify-content: space-between;
padding: 0 20rpx;
background-color: #fff;
&.header-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
}
}
.header-placeholder {
width: 100%;
}
.left-section {
display: flex;
align-items: center;
flex: 1;
.area-picker-wrapper {
margin-right: 20rpx;
min-width: 200rpx;
max-width: 300rpx;
}
.area-display {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8rpx 16rpx;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
background-color: #fff;
min-height: 60rpx;
}
.area-text {
font-size: 30rpx;
color: #333;
flex: 1;
&.placeholder {
color: #999;
}
}
.dropdown-icon {
width: 24rpx;
height: 16rpx;
margin-left: 8rpx;
}
.search-icon {
height: 36.16rpx;
}
}
.logo {
position: absolute;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
color: #000000;
font-weight: bold;
font-size: 35rpx;
image{
height: 46.16rpx;
height: 85rpx;
}
}
.right-section {
flex: 1;
}
.location-selector {
display: flex;
align-items: center;
padding: 8rpx 0rpx;
// border: 2rpx solid #e0e0e0;
border-radius: 20rpx;
background-color: #fff;
min-width: 100rpx;
margin-right: 20rpx;
.location-text {
font-size: 30rpx;
color: #333;
margin-right: 8rpx;
}
.dropdown-icon {
width: 24rpx;
height: 16rpx;
}
}
</style>

2
components/messagePop.vue

@ -1,7 +1,7 @@
<template>
<view>
<image @click="togglePopup" class="suspension-img" :class="{'rotated': isOpen}"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/suspension-icon.png" mode=""></image>
src="https://des.dayunyuanjian.cn/epicSoul/taozi/suspension-icon.png" mode=""></image>
<view v-if="isOpen" class="mask" @click="closeMessage"></view>

52
components/nmr-icon/nmr-icon.vue

@ -0,0 +1,52 @@
<template>
<text :class="[type,'iconfont-' + name,{'nmr-linear':islinear}]" :style="{'background-image':islinear?linearStyle:'',color: color,'line-height':size + 'rpx', 'font-size': size + 'rpx' }"
@click="_onClick" />
</template>
<script>
// 使
// <nmr-icon name="renwu" size="30" color="#ff0000"></nmr-icon>
export default {
name: 'UniIcon',
props: {
type: {
type: String,
default: 'des'
},
islinear: {//
type: Boolean,
default: false,
},
linearStyle: {//
type: String,
default: 'linear-gradient(180deg, #64BDE7 0%, #A7D9F3 100%)'
},
name: {
type: String,
default: ''
},
color: {
type: String,
default: '#333333'
},
size: {
type: [Number, String],
default: 32
}
},
methods: {
_onClick() {
this.$emit('click')
}
}
}
</script>
<style>
.nmr-linear{
-webkit-background-clip: text;
background-clip: text;
color: transparent !important;
background-image: linear-gradient(180deg, #64BDE7 0%, #A7D9F3 100%);
}
</style>

225
components/跨页面音频控制解决方案.md

@ -1,225 +0,0 @@
# 跨页面音频控制解决方案
## 进一步优化:解决跨页面状态同步问题
### 问题描述
在跨页面场景下,当音频正在播放时跳转到新页面,新页面的MusicControl组件不知道有音频在播放,点击背景音乐按钮时会直接播放背景音乐,导致音频和背景音乐同时播放。
### 解决方案
#### 1. MusicControl组件增强检测
```javascript
// 在mounted生命周期中添加全局音频状态检测
mounted() {
this.syncMusicState();
this.checkGlobalAudioState(); // 新增:检查全局音频状态
// 定时器也要检查全局音频状态
this.timer = setInterval(() => {
this.syncMusicState();
this.checkGlobalAudioState();
}, 1000);
}
// 新增方法:检查全局音频状态
checkGlobalAudioState() {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
const globalAudio = app.globalData.currentAudio;
this.isAudioPlaying = !globalAudio.paused;
} else {
this.isAudioPlaying = false;
}
}
```
#### 2. 全局音频管理工具优化
```javascript
// 在音频状态变化时发送全局事件
pauseCurrentAudio() {
const audio = this.getCurrentAudio();
if (audio && !audio.paused) {
audio.pause();
this.notifyAudioStateChange(false); // 通知状态变化
return true;
}
return false;
}
// 新增:通知音频状态变化
notifyAudioStateChange(isPlaying) {
if (typeof uni !== 'undefined') {
uni.$emit('audioPlaying', isPlaying);
}
}
```
### 修复后的交互流程
```mermaid
graph TD
A[跨页面跳转] --> B[MusicControl组件加载]
B --> C[检查全局音频状态]
C --> D{有音频在播放?}
D -->|是| E[设置isAudioPlaying=true]
D -->|否| F[设置isAudioPlaying=false]
E --> G[点击背景音乐按钮]
F --> G
G --> H{检查isAudioPlaying}
H -->|有音频| I[先暂停音频再播放背景音乐]
H -->|无音频| J[直接播放背景音乐]
```
## 问题描述
AudioControl组件在页面跳转时会出现以下问题:
1. 组件状态重置,图标显示不正确
2. 音频实例丢失连接,但音频可能仍在播放
3. 无法在其他页面控制正在播放的音频
## 解决方案
### 1. 全局音频实例管理
在`App.vue`的`globalData`中添加`currentAudio`属性,用于保存当前的音频实例:
```javascript
globalData: {
// ... 其他属性
currentAudio: null // 全局音频实例
}
```
### 2. AudioControl组件优化
#### 状态同步机制
- 组件挂载时检查全局音频状态
- 复用已存在的音频实例(如果URL匹配)
- 组件销毁时不销毁全局音频实例
#### 核心方法改进
```javascript
// 检查全局音频状态
checkGlobalAudioState() {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
const globalAudio = app.globalData.currentAudio;
if (globalAudio.src === this.audioSrc) {
this.isAudioPlaying = !globalAudio.paused;
}
}
}
// 初始化音频时复用全局实例
initAudio() {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
if (app.globalData.currentAudio.src === this.audioSrc) {
// 复用现有实例
this.audioContext = app.globalData.currentAudio;
this.isAudioPlaying = !this.audioContext.paused;
return;
}
}
// 创建新实例...
}
```
### 3. 全局音频管理工具
创建了`utils/globalAudioManager.js`工具类,提供统一的音频控制接口:
```javascript
// 在任何页面或组件中使用
uni.$globalAudio.pauseCurrentAudio(); // 暂停当前音频
uni.$globalAudio.playCurrentAudio(); // 播放当前音频
uni.$globalAudio.isAudioPlaying(); // 检查播放状态
uni.$globalAudio.getCurrentAudioSrc(); // 获取当前音频源
```
## 使用示例
### 在页面中控制音频
```vue
<template>
<view>
<!-- 音频控制组件 -->
<AudioControl :audioSrc="audioUrl" />
<!-- 手动控制按钮 -->
<button @click="toggleAudio">切换音频</button>
<button @click="stopAudio">停止音频</button>
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
export default {
components: { AudioControl },
data() {
return {
audioUrl: this.showImg('/uploads/audio/chapter1.mp3')
}
},
methods: {
toggleAudio() {
if (uni.$globalAudio.isAudioPlaying()) {
uni.$globalAudio.pauseCurrentAudio();
} else {
uni.$globalAudio.playCurrentAudio();
}
},
stopAudio() {
uni.$globalAudio.stopCurrentAudio();
}
}
}
</script>
```
### 在其他页面检查音频状态
```javascript
// 在任何页面的onShow生命周期中
onShow() {
// 检查是否有音频在播放
if (uni.$globalAudio.isAudioPlaying()) {
console.log('有音频正在播放:', uni.$globalAudio.getCurrentAudioSrc());
}
}
```
## 技术优势
### ✅ **状态持久化**
- 音频实例在页面跳转时不会丢失
- 组件状态能够正确同步全局音频状态
### ✅ **跨页面控制**
- 在任何页面都可以控制当前播放的音频
- 提供统一的音频管理接口
### ✅ **资源优化**
- 避免创建多个音频实例
- 自动清理无用的音频资源
### ✅ **用户体验**
- 页面跳转时音频播放不中断
- 图标状态显示正确
- 音频控制逻辑一致
## 注意事项
1. **页面生命周期**:音频实例与页面生命周期解耦,需要手动管理
2. **内存管理**:确保在应用退出时正确清理音频资源
3. **状态同步**:多个AudioControl组件需要监听相同的全局状态
4. **错误处理**:增强错误处理机制,确保音频异常时的状态恢复
## 兼容性
- ✅ uni-app
- ✅ 小程序环境
- ✅ H5环境
- ✅ APP环境

144
components/音频背景音乐交互说明.md

@ -1,144 +0,0 @@
# 音频与背景音乐交互功能说明
## Bug修复记录
### 问题描述
当音频播放时点击背景音乐按钮,背景音乐暂停音频并开始播放。此时再点击关闭背景音乐,图标显示关闭状态但背景音乐仍在播放。
### 问题原因
1. 点击背景音乐按钮时会发送事件暂停音频
2. 音频暂停时会调用restoreBackgroundMusic恢复背景音乐
3. 然后MusicControl再执行自己的切换逻辑
4. 导致背景音乐被恢复后又被操作,状态混乱
### 解决方案
1. **MusicControl组件优化**
- 检测是否有音频在播放
- 如有音频,先暂停音频,延迟执行背景音乐切换
- 如无音频,直接切换背景音乐状态
2. **AudioControl组件优化**
- 在handleBackgroundMusicToggle中只暂停音频
- 不自动恢复背景音乐,让MusicControl自己控制
### 修复后的交互流程
```mermaid
graph TD
A[点击背景音乐按钮] --> B{是否有音频播放?}
B -->|是| C[发送暂停音频事件]
C --> D[AudioControl暂停音频\n不恢复背景音乐]
D --> E[延迟100ms后切换背景音乐]
B -->|否| F[直接切换背景音乐状态]
```
## 实现的功能
### 🎵 **背景音乐控制音频**
当点击背景音乐控制按钮时:
- 如果有音频正在播放,会自动暂停音频
- 然后正常切换背景音乐的播放/暂停状态
### 🎧 **音频控制背景音乐**
当点击音频控制按钮时:
- 播放音频时自动暂停背景音乐
- 暂停音频时自动恢复背景音乐
- 音频播放结束时自动恢复背景音乐
## 技术实现
### 事件通信机制
使用uni-app的全局事件机制实现组件间通信:
```javascript
// AudioControl组件发送事件
uni.$emit('audioPlaying', true/false);
// MusicControl组件发送事件
uni.$emit('backgroundMusicToggle');
// 组件监听事件
uni.$on('eventName', this.handlerFunction);
```
### 交互流程
#### 点击背景音乐按钮:
```mermaid
graph TD
A[点击背景音乐按钮] --> B[发送backgroundMusicToggle事件]
B --> C[AudioControl收到事件]
C --> D{音频是否在播放?}
D -->|是| E[暂停音频]
D -->|否| F[继续背景音乐操作]
E --> G[恢复背景音乐]
F --> H[切换背景音乐状态]
```
#### 点击音频按钮:
```mermaid
graph TD
A[点击音频按钮] --> B{当前音频状态?}
B -->|未播放| C[暂停背景音乐]
C --> D[播放音频]
D --> E[发送audioPlaying:true事件]
B -->|正在播放| F[暂停音频]
F --> G[恢复背景音乐]
G --> H[发送audioPlaying:false事件]
```
## 使用方法
在页面中同时使用两个组件:
```vue
<template>
<view class="page-container">
<!-- 页面内容 -->
<!-- 音频控制组件 -->
<AudioControl :audioSrc="audioUrl" />
<!-- 背景音乐控制组件 -->
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
export default {
components: {
AudioControl,
MusicControl
},
data() {
return {
audioUrl: this.showImg('/uploads/audio/your-audio.mp3')
}
}
}
</script>
```
## 优势特点
### ✅ **智能交互**
- 两个组件能够智能感知对方的状态
- 避免同时播放音频和背景音乐造成的冲突
- 提供良好的用户体验
### ✅ **解耦设计**
- 组件间通过事件通信,保持松耦合
- 每个组件都能独立工作
- 易于维护和扩展
### ✅ **状态同步**
- 实时同步音频和背景音乐的播放状态
- 确保状态的一致性和准确性
## 注意事项
1. **事件监听清理**:组件销毁时会自动清理事件监听,避免内存泄漏
2. **状态管理**:两个组件都维护各自的状态,通过事件保持同步
3. **错误处理**:包含完善的错误处理机制,确保功能稳定运行

1122
libs/qqmap-wx-jssdk1.2/qqmap-wx-jssdk.js

File diff suppressed because it is too large

1
libs/qqmap-wx-jssdk1.2/qqmap-wx-jssdk.min.js

File diff suppressed because one or more lines are too long

7
main.js

@ -6,14 +6,17 @@ import '@/static/js/request.js'
import '@/static/js/CommonFunction.js'
import '@/utils/globalAudioManager.js'
import {myMixins} from '@/mixins/myMixins.js'
import main from "@/common/index.js"
import BackButton from "@/components/BackButton.vue";
Vue.prototype.$main = main
Vue.mixin(myMixins)
Vue.config.productionTip = false
Vue.component('BackButton', BackButton)
// 去除生产环境console
if (uni.getSystemInfoSync().platform !== "devtools") {
console.log = () => {}
// console.log = () => {}
}
App.mpType = 'app'

12
manifest.json

@ -50,14 +50,22 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx8954209bb3ad489e",
"appid" : "wx9660f8c5776663e0",
"setting" : {
"urlCheck" : false,
"es6" : true,
"postcss" : true,
"minified" : true
},
"usingComponents" : true
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "为了提供更好更快速的体验,需要获取您的当前位置"
}
},
"lazyCodeLoading" : "requiredComponents",
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ],
"requiredBackgroundModes" : [ "audio" ]
},
"mp-alipay" : {
"usingComponents" : true

2
mixins/myMixins.js

@ -36,7 +36,7 @@ export const myMixins ={
title: 'Epic Soul交响', // 分享的名称
path: `${view.route}?url=${url}`, // 将 url 作为参数传递
// imageUrl: "https://cgc.js-dyyj.com/uploads/20250619/172a730c88bd8894dee1e64c703795eb.jpg",
mpId: 'wx8954209bb3ad489e' // 此处配置微信小程序的 AppId
mpId: 'wx9660f8c5776663e0' // 此处配置微信小程序的 AppId
};
}
}

23
package-lock.json

@ -9,10 +9,21 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"decimal.js": "^10.6.0",
"js-md5": "^0.8.3",
"moment": "^2.30.1",
"ydui-district": "^1.1.0"
}
},
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="
},
"devDependencies": {}
"node_modules/js-md5": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz",
"integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ=="
},
"node_modules/moment": {
"version": "2.30.1",
@ -29,6 +40,16 @@
}
},
"dependencies": {
"decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="
},
"js-md5": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz",
"integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ=="
},
"moment": {
"version": "2.30.1",
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",

3
package.json

@ -4,10 +4,11 @@
"description": "",
"main": "main.js",
"dependencies": {
"decimal.js": "^10.6.0",
"js-md5": "^0.8.3",
"moment": "^2.30.1",
"ydui-district": "^1.1.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},

280
pages.json

@ -1,5 +1,6 @@
{
"pages": [{
"pages": [
{
"path": "pages/stratIndex",
"style": {
"navigationStyle": "custom"
@ -9,48 +10,77 @@
"path": "pages/index/index",
"style": {
"navigationStyle": "custom"
// "navigationBarTitleText": "CGC-ICH"
}
},
{
"path": "pages/index/readingBody",
"style": {
"navigationStyle": "custom"
// "navigationBarTitleText": "阅读体"
}
},
{
"path": "pages/index/sensoryStore",
"style": {
"navigationStyle": "custom"
// "navigationBarTitleText": "有感商店"
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/index/intelligentAgent",
"style": {
"navigationStyle": "custom"
// "navigationBarTitleText": "智能体"
}
},
{
"path": "pages/index/iSoul",
"style": {
"navigationStyle": "custom"
// "navigationBarTitleText": "iSoul"
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/index/timeShopBank",
"style": {
"navigationBarTitleText": "时间银行",
"navigationStyle": "custom"
}
},
{
"path": "pages/notes/detail",
"style": {
"navigationBarTitleText": "笔记详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/notes/publish",
"style": {
"navigationBarTitleText": "发布笔记",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/agent/index",
"style": {
"navigationBarTitleText": "DES智能体",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
}
],
"subPackages": [{
"subPackages": [
{
"root": "subPackages",
"pages": [{
"pages": [
{
"path": "order/trades",
"style": {
"navigationBarTitleText": "全部订单"
@ -168,17 +198,209 @@
"navigationBarTitleText": "修改昵称"
}
},
{
"path": "user/collection",
"style": {
"navigationBarTitleText": "我的收藏",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "search/search",
"style": {
"navigationBarTitleText": "搜索"
}
},
{
"path": "equityGoods/index",
"style": {
"navigationBarTitleText": "权益商品"
}
},
{
"path": "equityGoods/list",
"style": {
"navigationBarTitleText": "权益商品"
}
},
{
"path": "equityGoods/detail",
"style": {
"navigationBarTitleText": "权益商品"
}
},
{
"path": "orderQy/list",
"style": {
"navigationBarTitleText": "权益商品订单"
}
},
{
"path": "orderQy/detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "orderQy/confrim",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "orderQy/confrimWriteOff",
"style": {
"navigationBarTitleText": "核销订单"
}
},
{
"path": "orderQy/writeOffCode",
"style": {
"navigationBarTitleText": "核销码"
}
},
{
"path": "haveFeeling/list",
"style": {
"navigationBarTitleText": "有感商品订单"
}
},
{
"path": "haveFeeling/detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "haveFeeling/detailAll",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "haveFeeling/logistics",
"style": {
"navigationBarTitleText": "物流详情"
}
},
{
"path": "haveFeeling/aftersale",
"style": {
"navigationBarTitleText": "申请售后"
}
},
{
"path": "memorialAlbum/index",
"style": {
"navigationBarTitleText": "数字资产纪念册",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "memorialAlbum/detail",
"style": {
"navigationBarTitleText": "藏品详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "afterSale/list",
"style": {
"navigationBarTitleText": "售后列表",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "afterSale/add",
"style": {
"navigationBarTitleText": "发起售后",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "afterSale/detail",
"style": {
"navigationBarTitleText": "售后详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "other/introduction",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "other/evita",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "user/couponList",
"style": {
"navigationBarTitleText": "优惠券"
}
},
{
"path": "other/ipPoster",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "other/index",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "other/read",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "other/novelCatalog",
"style": {
"navigationBarTitleText": "园门修真传",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "other/playNovel",
"style": {
"navigationBarTitleText": "园门修真传",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "points/index",
"style": {
"navigationBarTitleText": "积分明细"
}
}
]
},
{
"root": "xxdf",
"pages": [{
"pages": [
{
"path": "home/home",
"style": {
"navigationBarTitleText": "",
@ -261,7 +483,8 @@
},
{
"root": "taozi",
"pages": [{
"pages": [
{
"path": "home/home",
"style": {
"navigationBarTitleText": "",
@ -300,7 +523,8 @@
},
{
"root": "bmzm",
"pages": [{
"pages": [
{
"path": "home/home",
"style": {
"navigationBarTitleText": "",
@ -360,7 +584,8 @@
},
{
"root": "pig",
"pages": [{
"pages": [
{
"path": "home/home",
"style": {
"navigationBarTitleText": "",
@ -496,7 +721,8 @@
},
{
"root": "xrcc",
"pages": [{
"pages": [
{
"path": "home/home",
"style": {
"navigationBarTitleText": "",
@ -556,7 +782,8 @@
},
{
"root": "xqk",
"pages": [{
"pages": [
{
"path": "home/home",
"style": {
"navigationBarTitleText": "",
@ -606,6 +833,18 @@
}
}
]
},
{
"root": "audioBook",
"pages": [
{
"path": "pages/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
}
]
}
],
"tabBar": {
@ -617,7 +856,8 @@
"fontSize": "24rpx",
"height": "123rpx",
"iconWidth": "40rpx",
"list": [{
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"visible": false
@ -628,8 +868,8 @@
"visible": false
},
{
"pagePath": "pages/index/sensoryStore",
"text": "有感商店",
"pagePath": "pages/index/timeShopBank",
"text": "时间银行",
"visible": false
},
{

47
pages/agent/index.vue

@ -0,0 +1,47 @@
<template>
<view class="content">
<GPT :agentId="agentId"></GPT>
</view>
</template>
<script>
import GPT from '@/components/GPT/index.vue'
export default {
components: {
GPT,
},
data() {
return {
agentId:''
}
},
onLoad(option) {
if(option){
this.agentId = option.id
}
wx.hideShareMenu()
const app = getApp();
const bgMusic = app.globalData.bgMusic;
bgMusic.pause();
},
onLaunch: options => {
this.options = options
},
onShow() {
console.log('【init message connect type------>】', );
},
methods: {
},
}
</script>
<style scoped lang="scss">
.content {
height: 100vh;
}
</style>

748
pages/index/components/FollowTab.vue

@ -0,0 +1,748 @@
<template>
<view class="follow-tab-container">
<!-- 搜索栏 -->
<view class="search-section">
<view class="search-bar">
<image
class="search-icon"
:src="
showImg('/uploads/20250826/a4d605e82622223c270df0af4e378ab3.png')
"
></image>
<input
class="search-input"
placeholder="搜索已关注的人"
v-model="searchText"
@confirm="handleSearch"
/>
</view>
</view>
<!-- 我的关注标题和排序 -->
<view class="follows-header">
<text class="follows-title">我的关注 ({{ totalCount || 0 }})</text>
<!-- <view class="sort-option" @click="toggleSort">
<text class="sort-text">综合排序</text>
<image class="sort-arrow" :src="showImg('/uploads/20250826/8e40deaa0bc67da3a9b104ff0e6b3e7c.png')"></image>
</view> -->
</view>
<!-- 分类导航 -->
<!-- <view class="category-tabs">
<view
class="tab-item"
:class="{ active: activeCategory === 'all' }"
@click="switchCategory('all')"
>
全部
</view>
<view
class="tab-item"
:class="{ active: activeCategory === 'merchant' }"
@click="switchCategory('merchant')"
>
商家
</view>
</view> -->
<!-- 关注用户列表 -->
<view class="follows-list">
<!-- 加载状态 -->
<view class="loading-state" v-if="loading">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 空状态显示 -->
<view class="empty-state" v-else-if="filteredFollowsList.length === 0">
<image
class="empty-icon"
:src="
showImg('/uploads/20250826/a4d605e82622223c270df0af4e378ab3.png')
"
mode="aspectFit"
></image>
<text class="empty-text">{{
searchText ? "未找到相关用户" : "暂无关注"
}}</text>
<text class="empty-desc">{{
searchText ? "换个关键词试试吧" : "关注你感兴趣的人,获取更多精彩内容"
}}</text>
</view>
<view
class="follow-item"
v-for="(item, index) in filteredFollowsList"
:key="item.id"
>
<image
class="user-avatar"
:src="
item.followUserHeadimg ||
'https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png'
"
mode="aspectFill"
></image>
<view class="user-info">
<text class="user-name">{{ item.followUserNickname }}</text>
<view class="update-time">
{{ item.formatTime }}
</view>
</view>
<view class="action-buttons">
<view class="follow-status-btn"> 已关注 </view>
<view class="more-options" @click="showOptions(item)">
<text class="more-dots"></text>
</view>
</view>
</view>
</view>
<!-- 推荐用户分隔线 -->
<view class="divider-line" v-if="false"></view>
<!-- 推荐用户区域 -->
<view class="recommend-section" v-if="false">
<view class="recommend-header">
<view
class="recommend-title"
style="display: flex; align-items: center"
>
你可能感兴趣的人
<image
style="width: 30rpx; height: 30rpx; margin-left: 10rpx"
:src="
showImg('/uploads/20250826/f1422cbef4c33e8c21d9e7e805c8bad9.png')
"
></image>
</view>
<view class="close-btn" @click="closeRecommend">
<text class="close-text">关闭</text>
</view>
</view>
<view class="recommend-list">
<view
class="recommend-item"
v-for="(item, index) in recommendList"
:key="item.id"
>
<image
class="user-avatar"
src="https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png"
mode="aspectFill"
></image>
<view class="user-info">
<text class="user-name">{{ item.name }}</text>
<text class="user-desc">{{ item.description }}</text>
</view>
<view class="action-buttons">
<view class="follow-btn" @click="followUser(item)"> 关注 </view>
<view class="dismiss-btn" @click="dismissUser(item)">
<text class="dismiss-text">×</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "FollowTab",
data() {
return {
searchText: "",
activeCategory: "all",
followsList: [],
recommendList: [
{
id: 101,
name: "颜真卿",
avatar: "/uploads/20250826/avatar1.png",
description: "介绍介绍介绍",
},
{
id: 102,
name: "颜真卿",
avatar: "/uploads/20250826/avatar1.png",
description: "介绍介绍介绍",
},
],
//
pageNum: 1,
pageSize: 10,
loading: false,
hasMore: true,
totalCount: 0,
};
},
computed: {
filteredFollowsList() {
//
return this.followsList;
},
},
mounted() {
//
this.getFollowList();
},
//
onReachBottom() {
this.loadMoreFollows();
},
//
onPullDownRefresh() {
this.getFollowList();
},
methods: {
handleSearch() {
//
this.getFollowList();
},
toggleSort() {
//
uni.showToast({
title: "排序功能开发中",
icon: "none",
});
},
switchCategory(category) {
this.activeCategory = category;
},
//
getFollowList(refresh = true) {
let token = uni.getStorageSync("token1");
if (!token) {
uni.showToast({
title: "请先登录",
icon: "none",
});
uni.navigateTo({
url: "/pages/login/login",
});
return;
}
if (this.loading) return;
if (refresh) {
this.pageNum = 1;
this.hasMore = true;
}
this.loading = true;
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize,
};
//
if (this.searchText.trim()) {
params.followUserNickname = this.searchText.trim();
}
this.Post(params, "/framework/follow/followList", "DES")
.then((res) => {
if (res.code === 200 && res.rows) {
const newItems = res.rows || [];
if (this.pageNum === 1) {
//
this.followsList = newItems;
this.totalCount = res.total || 0;
} else {
//
this.followsList.push(...newItems);
}
//
this.hasMore = newItems.length === this.pageSize;
} else {
uni.showToast({
title: res.msg || "获取关注列表失败",
icon: "none",
});
}
})
.catch((error) => {
console.error("获取关注列表失败:", error);
uni.showToast({
title: "加载失败,请重试",
icon: "none",
});
})
.finally(() => {
this.loading = false;
uni.stopPullDownRefresh();
});
},
//
loadMoreFollows() {
if (!this.loading && this.hasMore) {
this.pageNum++;
this.getFollowList(false);
}
},
showOptions(item) {
uni.showActionSheet({
itemList: ["取消关注"],
success: (res) => {
switch (res.tapIndex) {
case 0:
this.unfollowUser(item);
break;
case 1:
this.reportUser(item);
break;
case 2:
this.blockUser(item);
break;
}
},
});
},
unfollowUser(item) {
uni.showModal({
title: "提示",
content: "确定要取消关注该用户吗?",
success: (res) => {
if (res.confirm) {
//
this.Post(
{
followUserId: item.followUserId,
type: 2, // 2
},
"/framework/follow/followUser",
"DES"
)
.then((res) => {
if (res.code === 200) {
//
const index = this.followsList.findIndex(
(user) => user.id === item.id
);
if (index > -1) {
this.followsList.splice(index, 1);
this.totalCount--;
}
uni.showToast({
title: "已取消关注",
icon: "success",
});
} else {
uni.showToast({
title: res.msg || "取消关注失败",
icon: "none",
});
}
})
.catch((error) => {
console.error("取消关注失败:", error);
uni.showToast({
title: "操作失败,请重试",
icon: "none",
});
});
}
},
});
},
reportUser(item) {
uni.showToast({
title: "举报功能开发中",
icon: "none",
});
},
blockUser(item) {
uni.showToast({
title: "拉黑功能开发中",
icon: "none",
});
},
followUser(item) {
//
this.followsList.unshift({
id: item.id,
name: item.name,
avatar: item.avatar,
newItems: 0,
category: "user",
});
//
const index = this.recommendList.findIndex((user) => user.id === item.id);
if (index > -1) {
this.recommendList.splice(index, 1);
}
uni.showToast({
title: "关注成功",
icon: "success",
});
},
dismissUser(item) {
const index = this.recommendList.findIndex((user) => user.id === item.id);
if (index > -1) {
this.recommendList.splice(index, 1);
uni.showToast({
title: "已移除推荐",
icon: "success",
});
}
},
closeRecommend() {
this.recommendList = [];
uni.showToast({
title: "已关闭推荐",
icon: "success",
});
},
},
};
</script>
<style lang="scss" scoped>
.follow-tab-container {
background: #fff;
}
/* 搜索栏 */
.search-section {
padding: 32rpx;
background: #fff;
}
.search-bar {
display: flex;
align-items: center;
background: #f8f9fa;
border-radius: 40rpx;
padding: 0 32rpx;
height: 80rpx;
.search-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
color: #999;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #333;
&::placeholder {
color: #999;
}
}
}
/* 关注标题和排序 */
.follows-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
padding: 0 32rpx 24rpx;
margin-bottom: 20rpx;
.follows-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.sort-option {
display: flex;
align-items: center;
.sort-text {
font-size: 28rpx;
color: #000000;
margin-right: 8rpx;
}
.sort-arrow {
width: 9rpx;
height: 24rpx;
}
}
}
/* 分类导航 */
.category-tabs {
display: flex;
padding: 0 32rpx 32rpx;
gap: 16rpx;
.tab-item {
border-radius: 32rpx;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
padding: 15rpx 30rpx;
font-size: 28rpx;
color: #000000;
font-weight: bold;
&.active {
background: #00ffff;
color: #000000;
}
}
}
/* 关注用户列表 */
.follows-list {
padding: 0 32rpx;
/* 加载状态样式 */
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #00ffff;
border-radius: 50%;
margin-bottom: 20rpx;
animation: spin 1s linear infinite;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 空状态样式 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-icon {
width: 120rpx;
height: 120rpx;
margin-bottom: 30rpx;
opacity: 0.5;
}
.empty-text {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-bottom: 16rpx;
}
.empty-desc {
font-size: 26rpx;
color: #999;
text-align: center;
max-width: 80%;
}
}
.follow-item {
display: flex;
align-items: center;
padding: 32rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.user-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
margin-right: 24rpx;
}
.user-info {
flex: 1;
.user-name {
display: block;
font-size: 30rpx;
font-weight: 500;
color: #000000;
margin-bottom: 8rpx;
}
.update-tag {
display: inline-block;
background: #f8f9fa;
border-radius: 16rpx;
padding: 10rpx 12rpx;
font-size: 24rpx;
color: #666;
}
.update-time {
font-size: 24rpx;
color: #999;
}
}
.action-buttons {
display: flex;
align-items: center;
gap: 16rpx;
.follow-status-btn {
border-radius: 24rpx;
padding: 12rpx 24rpx;
border: 2rpx solid #e5e5e5;
font-size: 26rpx;
color: #666;
}
.more-options {
padding: 8rpx;
.more-dots {
font-size: 24rpx;
color: #000;
letter-spacing: 2rpx;
}
}
}
}
}
/* 分隔线 */
.divider-line {
height: 1rpx;
background: #f0f0f0;
margin: 32rpx 0;
}
/* 推荐用户区域 */
.recommend-section {
padding: 0 32rpx;
.recommend-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
.recommend-title {
font-size: 28rpx;
font-weight: 500;
color: #666666;
margin-right: 10rpx;
}
.close-btn {
padding: 8rpx 16rpx;
.close-text {
font-size: 28rpx;
color: #666;
}
}
}
.recommend-list {
.recommend-item {
display: flex;
align-items: center;
padding: 32rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.user-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
margin-right: 24rpx;
}
.user-info {
flex: 1;
.user-name {
display: block;
font-size: 30rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
}
.user-desc {
font-size: 26rpx;
color: #999;
}
}
.action-buttons {
display: flex;
align-items: center;
gap: 16rpx;
.follow-btn {
border: 2rpx solid #00ffff;
border-radius: 24rpx;
padding: 12rpx 24rpx;
font-size: 26rpx;
color: #000;
font-weight: bold;
}
.dismiss-btn {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
.dismiss-text {
font-size: 32rpx;
color: #999;
font-weight: 300;
}
}
}
}
}
}
</style>

662
pages/index/iSoul - 副本.vue

@ -0,0 +1,662 @@
<template>
<view class="bg">
<view class="title">我的</view>
<view class="topBox" @click="gotoProfile">
<view class="avatar-box flex-center">
<image :src="showImg(userInfo.avatar)" mode="aspectFill" class="headImg" v-if="userInfo.avatar"></image>
<image src="https://changshu.js-dyyj.com/uploads/20250326/516242619f0772bee371a60684618c01.png" mode="aspectFill"
class="headImg" v-else></image>
</view>
<view class="username" v-if="userInfo.nickname">{{userInfo.nickname}}</view>
<view class="username" v-else>请登录/注册 ></view>
<view class="personalCenter flex-center" v-if="userInfo.nickname">
个人中心
</view>
</view>
<view class="orderBox">
<navigator :url="'/subPackages/order/trades'" class="moreBox flex-between">
我的订单
<span class="flex-between">查看更多 <img
src="https://static.ticket.sz-trip.com/cgc/images/user/rightIcon.png" alt=""></span>
</navigator>
<view class="flex-around" style="margin-top: 20rpx;">
<view class="orderItem" v-for="(item,index) in orderList" :key="index" @click="goTrades(item)">
<image :src="item.src" mode="heightFix"></image>
<view style="margin-top: 10rpx;">{{item.title}}</view>
</view>
</view>
<!-- 待付款轮播 -->
<!-- <swiper class="my-swipe" :autoplay="3000" indicator-color="white" v-if="dfkList && dfkList.length>0" circular>
<swiper-item v-for="(item,index) in dfkList" :key="item.id">
<div class="dfkBox" @click="goToOrderDetail(item)">
<image :src="showImg(item.order_child[0].specifications_image)" mode="aspectFill"></image>
<div class="contentBox">
<div style="width:300rpx;">
<div style="font-size: 27rpx;margin-bottom: 10rpx;">等待付款 </div>
<div style="display: flex;color: #8A8A8A;font-size: 27rpx;">剩余时间<uni-countdown class="countdown" @timeup="timeup(index)" :show-day="false" :hour="differTimeList[index].slice(0,2)" :minute="differTimeList[index].slice(3,5)" :second="differTimeList[index].slice(6,8)"/></div>
</div>
<div class="orderBtn" @click.stop="setOrderId(item.order_id)">去支付</div>
</div>
</div>
</swiper-item>
</swiper> -->
</view>
<view class="cygj">
<view class="cyItem" style="font-weight: bold;font-size: 31rpx;color: #333333;">
常用工具
</view>
<view class="cyItem flex-between" v-for="(item,index) in cyList" :key="index"
@click="gotoUrl(item,index)" v-if="item.isShow">
<view class="flex-center">
<img :src="item.src" class="headIcon">
{{item.title}}
</view>
<img src="https://static.ticket.sz-trip.com/yandu/images/user/rightIcon-gray.png" class="rightIcon">
</view>
<button id="contact" class="cyItem flex-between" open-type="contact" bindcontact="handleContact" session-from="sessionFrom">
<view class="flex-center">
<img src="https://static.ticket.sz-trip.com/cgc/images/user/kfdh.png" class="headIcon">
在线客服
</view>
<img src="https://static.ticket.sz-trip.com/dongtai/images/user/rightIcon-gray.png" class="rightIcon">
</button>
</view>
<!-- 旅游咨询弹框 -->
<uni-popup ref="popup" type="center">
<view class="consult-popup">
即将拨打客服电话
<view class="consult-subtitle">服务时间周一至周五<br>9:00-12:0013:00-18:00</view>
<view class="consult-btns">
<view @click="$refs.popup.close()">取消</view>
<view @click="clickPhone('0515-69186109')">确定</view>
<!-- <view>
<button class="confirm" open-type="contact">确定</button>
</view> -->
</view>
</view>
</uni-popup>
<CustomTabBar :currentTab="4" />
<MusicControl />
</view>
</template>
<script>
import moment from "moment";
import CustomTabBar from '@/components/CustomTabBar.vue';
import MusicControl from '@/components/MusicControl.vue';
export default {
components: {CustomTabBar,MusicControl},
data() {
return {
dfkList: [],
differTimeList: [],
nowDateTime: '', //
userInfo: {},
orderList: [{
src: 'https://des.dayunyuanjian.cn/epicSoul/image/user/dfk.png',
title: '待付款',
status: 'WAIT_PAYMENT'
},
{
src: 'https://des.dayunyuanjian.cn/epicSoul/image/user/dfh.png',
title: '待使用/发货',
status: 'PAYMENT_SUCCESSFULLY'
},
{
src: 'https://des.dayunyuanjian.cn/epicSoul/image/user/dsh.png',
title: '待收货',
status: 'POST'
},
// {
// src: 'https://changshu.js-dyyj.com/uploads/20250326/3f13d3a10dd0f88e764e3ddf1157c108.png',
// title: '',
// status: 'WAIT_COMMENT'
// },
{
src: 'https://des.dayunyuanjian.cn/epicSoul/image/user/sh.png',
title: '退款/售后',
status: 'WAIT_REFUND,REFUND_SUCCESS,REFUND_REFUSAL,REFUND_ERROR,REFUND_PART'
},
],
cyList: [
{
src: 'https://static.ticket.sz-trip.com/cgc/images/user/gwc.png',
title: '购物车',
path: '/subPackages/user/gwc',
isShow: true
},
// {
// src: 'https://static.ticket.sz-trip.com/cgc/images/user/kfdh.png',
// title: '',
// path: '',
// isShow: true
// },
{
src: 'https://static.ticket.sz-trip.com/cgc/images/user/shdz.png',
title: '收货地址',
path: '/subPackages/user/travelerList',
isShow: true
},
{
src: 'https://static.ticket.sz-trip.com/cgc/images/user/ysgl.png',
title: '隐私管理',
path: '/subPackages/user/privacy',
isShow: true
},
// {
// src: 'https://changshu.js-dyyj.com/uploads/20250326/3e977f62b6cbfeec5a17d945b96b8c8c.png',
// title: '',
// path: '/subPackages/service/service',
// isShow: true
// },
],
}
},
onShow() {
this.userInfo = (uni.getStorageSync('userInfo') && JSON.parse(uni.getStorageSync('userInfo'))) || this.$store.state.user.userInfo || {}
console.log(this.userInfo)
// this.dfkList = []
// this.nowDateTime = parseInt(new Date().getTime() / 1000)
// this.Post({}, "/api/user/userInfo").then((res) => {
// if (res.data) {
// this.userInfo = res.data;
// // this.getDfk()
// //
// this.Post({},'/api/merchants/is_merchant').then(res => {
// this.cyList[6].isShow = res.data
// })
// }
// });
},
methods: {
//
gotoProfile() {
// token
if(this.userInfo.token) {
uni.navigateTo({
url: '/subPackages/user/profile'
})
}else {
uni.navigateTo({
url: '/pages/login/login'
})
}
},
timeup(index) {
// return this.dfkList.splice(index,1)
},
setOrderId(id) {
let that = this;
that.orderId = id;
that.Post({
order_id: id,
type: "miniprogram",
platform: 'miniprogram'
},
'/api/pay/unify'
).then(res => {
if (res.data) {
uni.requestPayment({
nonceStr: res.data.nonceStr,
package: res.data.package,
paySign: res.data.paySign,
signType: res.data.signType,
timeStamp: res.data.timeStamp
});
}
});
},
//
getDfk() {
this.differTimeList = []
let params = {
offset: this.dfkList.length,
limit: 10,
status: 'WAIT_PAYMENT',
}
this.Post(params, '/api/order/orderList').then(res => {
this.isLoading = false
if (res) {
this.dfkList = [...this.dfkList, ...res.data]
this.dfkList.forEach(item => {
// ,-differTimeList
let del;
if (moment(item.close_time).diff(moment()) > 0) {
del = moment.utc(moment(item.close_time).diff(moment())).format('HH:mm:ss')
} else {
del = '00:00:00'
}
this.differTimeList.push(del)
})
console.log(this.differTimeList);
console.log('this.differTimeList:' + this.differTimeList[0].slice(0, 2))
}
})
},
goToOrderDetail(item) {
uni.navigateTo({
url: '/subPackages/order/detail?id=' + item.order_id
});
},
getChild(list) {
let arr = []
for (let i = 0; i < list.length; i++) {
if (list[i].product_model == "ticket") {
console.log(list[i]);
arr.push(list[i])
break
}
}
console.log(arr);
if (arr.length > 0) {
return arr[0]
} else {
return list[0]
}
},
goCoupon() {
uni.navigateTo({
url: "/subPackages/user/coupon",
});
},
goKeFu() {
uni.navigateTo({
url: "/subPackages/publicservices/ServiceOnline",
});
},
// open(){
// this.$refs.popup.open('center')
// },
gotoUrl(item, index) {
if (item.title == "客服电话") {
this.$refs.popup.open()
return;
}
uni.navigateTo({
url: item.path
})
},
qidai() {
uni.showToast({
title: '功能建设中...',
icon: 'none'
})
},
goTrades(item) {
if (item) {
uni.navigateTo({
url: "/subPackages/order/trades?type=" + item.title,
});
} else {
uni.navigateTo({
url: "/subPackages/order/trades",
});
}
},
//
isGz(item) {
this.$refs.pop.openPop(
'https://yjks.oss-cn-shanghai.aliyuncs.com/uploads/20230517/db9eb60e0abfea8be1075b406fefe551.jpg');
// this.Post({}, '/api/wechat/getSubcribeInfo').then(res => {
// if (res.data) {
// uni.navigateTo({
// url:'/subPackages/webPage/webPage?url='+'https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzU2NjQwNTYxNg==#wechat_redirect'
// })
// } else {
// console.log(this.$refs.pop);
// }
// });
},
}
}
</script>
<style scoped lang="scss">
view{
box-sizing: border-box;
}
::v-deep .uni-countdown {
font-size: 20px !important;
::v-deep .uni-countdown__splitor {
font-size: 20px !important;
}
}
.bg {
min-height: 100vh;
overflow-x: hidden;
background: url('https://des.dayunyuanjian.cn/epicSoul/image/user/bg.png') no-repeat;
background-size: 100% auto;
background-color: #F7F7F7;
padding-bottom: 100rpx;
}
.title {
font-weight: bold;
font-size: 36rpx;
color: #333333;
position: absolute;
top: 110rpx;
left: 50%;
transform: translate(-50%, 0);
}
.topBox {
width: 750rpx;
height: 373rpx;
padding: 100rpx 0 0 26rpx;
box-sizing: border-box;
display: flex;
margin-top: 90rpx;
.avatar-box {
width: 120rpx;
height: 120rpx;
background: #FFFFFF;
border-radius: 50%;
overflow: hidden;
}
.headImg {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
}
.username {
margin: 40rpx 0 0 28rpx;
font-weight: 500;
font-size: 40rpx;
color: #000000;
}
.personalCenter {
width: 165rpx;
height: 59rpx;
background: rgba(24, 135, 145, 1);
border-radius: 29rpx 0rpx 0rpx 29rpx;
margin: 40rpx 0 0 auto;
font-weight: bold;
font-size: 28rpx;
color: #FFFFFF;
}
}
.orderBox {
width: 697rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 23rpx 0rpx rgba(80, 80, 80, 0.12);
border-radius: 20rpx;
margin: -48rpx auto 0;
padding-bottom: 30.6rpx;
.moreBox {
height: 84rpx;
margin: auto;
font-size: 31rpx;
font-family: PingFang SC;
font-weight: bold;
color: #000000;
padding-left: 26rpx;
span {
padding-right: 18rpx;
font-size: 27rpx;
font-family: PingFang SC;
font-weight: 500;
box-sizing: border-box;
img {
width: 12rpx;
height: 21rpx;
margin-left: 12rpx;
}
}
}
.orderItem {
font-size: 27rpx;
font-family: PingFang SC;
font-weight: 500;
color: #000000;
width: 25%;
text-align: center;
image {
height: 60rpx;
}
}
}
.lxwm {
width: 696rpx;
height: 447rpx;
background: #FFFFFF;
box-shadow: 0px 4rpx 12rpx 0px rgba(150, 149, 149, 0.3);
border-radius: 20rpx;
margin: auto;
padding: 27rpx 19rpx 0 19rpx;
font-size: 31rpx;
font-family: PingFang SC;
font-weight: bold;
color: #000000;
.midBox {
padding: 26rpx 43rpx 21rpx 44rpx;
box-sizing: border-box;
img {
width: 265rpx;
height: 252rpx;
border-radius: 15rpx;
}
}
.botBox {
padding: 0 30rpx 0 51rpx;
font-size: 27rpx;
font-family: PingFang SC;
font-weight: bold;
color: #000000;
}
}
.cygj {
width: 697rpx;
background: #FFFFFF;
border-radius: 20rpx;
margin: 30rpx auto 0;
padding: 0 27rpx;
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 500;
color: #000000;
box-sizing: border-box;
.cyItem {
height: 106rpx;
display: flex;
align-items: center;
border-bottom: 1rpx solid #D8D8D8;
.headIcon {
width: 42rpx;
height: 42rpx;
margin-right: 15rpx;
}
.rightIcon {
width: 20rpx;
height: 20rpx;
}
}
.cyItem:last-child {
border: none;
}
}
.my-swipe {
// width: 100%;
margin: 0 30rpx;
margin-top: 37.3rpx;
}
swiper {
height: 111rpx !important;
}
.dfkBox {
width: 100%;
height: 111rpx;
background: #F7F7F7;
margin: 0 auto 30.64rpx;
// padding: 0.25rem;
display: flex;
}
.dfkBox image {
width: 137rpx;
height: 111rpx;
border-radius: 13rpx;
flex-shrink: 0;
// margin-right: 16.7rpx;
}
.dfkBox .contentBox {
padding-left: 5rpx;
padding-right: 5rpx;
height: 111rpx;
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
}
// .van-count-down{
// font-size: 0.39rem;
// font-family: PingFang SC;
// font-weight: 400;
// color: #FB6E4D;
// }
.orderBtn {
width: 152rpx;
height: 56rpx;
background: linear-gradient(270deg, #FC5109, #FDC43A);
;
border-radius: 28rpx;
text-align: center;
line-height: 56rpx;
font-size: 27rpx;
font-family: PingFang SC;
font-weight: 400;
color: #FFFFFF;
// margin-left: 50rpx;
// margin-right: 20rpx;
}
#contact {
-webkit-tap-highlight-color: transparent;
background-color: rgba(0, 0, 0, 0);
border-radius: 0;
box-sizing: border-box;
color: transparent;
cursor: pointer;
overflow: hidden;
padding: 0;
position: relative;
text-align: center;
text-decoration: none;
border: transparent 0px solid;
display: flex;
align-items: center;
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 500;
color: #000000;
}
button::after {
border: none;
background-color: rgba(0, 0, 0, 0);
}
.more {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, .5);
position: fixed;
top: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
image {
width: 646rpx;
height: 959rpx;
}
img {
width: 80rpx;
height: 80rpx;
}
}
.consult-popup {
width: 487rpx;
height: 367rpx;
background: #F0F0F0;
border-radius: 20rpx;
padding: 65rpx 0 23rpx;
font-weight: bold;
font-size: 32rpx;
color: #000000;
text-align: center;
.consult-subtitle {
font-weight: 500;
font-size: 27rpx;
color: #333333;
margin-top: 43rpx;
}
.consult-btns {
display: flex;
margin-top: 75rpx;
view {
width: 50%;
font-weight: bold;
font-size: 32rpx;
color: #00AEA0;
line-height: 54rpx;
}
view:first-child {
border-right: 1rpx solid #D8D8D8;
color: #000000;
}
}
}
.confirm{
height: 58rpx;
line-height: 58rpx;
background: none;
font-weight: bold;
font-size: 32rpx;
color: #00AEA0;
}
</style>

2155
pages/index/iSoul.vue

File diff suppressed because it is too large

328
pages/index/index.vue

@ -1,99 +1,321 @@
<template>
<view class="bg">
<headerVue :isSearch="false"></headerVue>
<view class="content">
<swiper class="top-banner" :indicator-dots="false" :autoplay="false" v-if="topBanner && topBanner.length > 0">
<swiper-item v-for="(item, index) in topBanner" :key="index" @click.stop="gotoUrlNew(item,index)">
<image class="top-banner" :src="showImg(item.head_img)" mode="aspectFill" lazy-load="true"></image>
</swiper-item>
</swiper>
<headerVue
@change="changeAddress"
:address="addressInfo"
fixed
isLocation
:isBack="false"
></headerVue>
<!-- 灵动岛组件 -->
<!-- 灵动岛组件 - 自包含无需传递参数 -->
<DynamicIsland
ref="dynamicIsland"
:page-id="'index_page'"
@toggle="handleIslandToggle"
@action="handleIslandAction"
/>
<view class="content" @click="handleContentClick">
<IPComponents></IPComponents>
<Book></Book>
<!-- 权益商品区域 -->
<ProductSection
v-show="productList.length"
title="EPIC SOUL文旅权益商品"
:productList="productList"
moreUrl="/subPackages/equityGoods/list"
detailUrlPrefix="/subPackages/equityGoods/detail"
@like-toggle="handleLikeToggle"
color="#D2FFDE"
:type="1"
/>
<ProductSection
titleBgColor="#92FF8F"
aiTagTextColor="#08FB05"
aiTagBorderColor="#6EAA3D"
title="EPIC SOUL文化有感商品"
:productList="productListFeeling"
moreUrl="/pages/index/sensoryStore"
detailUrlPrefix="/subPackages/techan/detail"
@like-toggle="handleLikeToggle"
:type="2"
color="#D3FCFF"
/>
<ProductSection
titleBgColor="#92FF8F"
aiTagTextColor="#08FB05"
aiTagBorderColor="#6EAA3D"
title="CGC-ICH 大运河非物质文化遗产"
@like-toggle="handleLikeToggle"
:type="3"
color="#FFEAD2"
/>
</view>
<CustomTabBar :currentTab="0" />
<MusicControl />
</view>
</template>
<script>
import MusicControl from '@/components/MusicControl.vue';
import headerVue from "@/components/header.vue"
import CustomTabBar from '@/components/CustomTabBar.vue';
export default {
components: {CustomTabBar,headerVue,MusicControl},
import MusicControl from "@/components/MusicControl.vue";
import headerVue from "@/components/header.vue";
import CustomTabBar from "@/components/CustomTabBar.vue";
import DynamicIsland from "@/components/DynamicIsland.vue";
import ProductSection from "@/components/ProductSection.vue";
import Book from "@/components/Book.vue";
import IPComponents from "@/components/IPComponents.vue";
export default {
components: {
CustomTabBar,
headerVue,
MusicControl,
DynamicIsland,
ProductSection,
Book,
IPComponents
},
computed: {
//
},
data() {
return {
topBanner: []
}
topBanner: [],
productList: [
// {
// id: 1,
// image:
// "https://epic.js-dyyj.com/uploads/20250728/58aed304917c9d60761f833c4f8dceb8.png",
// avatar:
// "https://epic.js-dyyj.com/uploads/20250728/d27ef6e6c26877da7775664fed376c6f.png",
// aiName: "",
// title: " | ",
// price: "588.00",
// isLiked: true,
// },
// {
// id: 2,
// image:
// "https://epic.js-dyyj.com/uploads/20250728/00e8704b23a0c9fd57023527146211b9.png",
// avatar:
// "https://epic.js-dyyj.com/uploads/20250728/d7bf0dd2f3f272afba687b525a7c575c.png",
// aiName: "",
// title: " | ",
// price: "398.00",
// isLiked: false,
// },
],
productListFeeling: [
// {
// id: 37,
// image:
// "https://epic.js-dyyj.com/uploads/20250822/f0ade3dd98a5a5e24ed0b60a023979e4.png",
// title: "IP ",
// price: "198.00",
// isLiked: true,
// isShop: true,
// },
// {
// id: 39,
// image:
// "https://epic.js-dyyj.com/uploads/20250822/19b1fb3e07fd459d347e727274af445c.png",
// title: " ",
// price: "35.00",
// isLiked: false,
// },
],
selectedText: "",
addressInfo: null,
isLoading: true, //
};
},
onLoad() {
},
onReady() {
this.getList()
onLoad() {},
async onReady() {
let res = await this.$main.getLocationInfo();
console.log(res);
if (!res.cityId) {
res = {
address: "江苏省苏州市吴中区太湖东路288号",
area: "吴中区",
areaId: "320506",
city: "苏州市",
cityId: "320500",
latitude: 31.26249,
longitude: 120.63212,
province: "江苏省",
provinceId: "320000",
street: "太湖东路",
};
}
this.addressInfo = res;
this.selectedText = res && res.city;
uni.setStorageSync("SYS_ADDRESS_INFO", JSON.stringify(res));
this.getList();
},
onShow() {
this.browse_record({type: 'page',title: '首页'});
this.geBenefitPackaget();
this.browse_record({
type: "page",
title: "首页",
});
this.$nextTick(() => {
this.$refs.dynamicIsland.getUserInfo();
});
},
onReachBottom() {
onPageScroll(e) {
// ID
uni.$emit("pageScroll_index_page", e.scrollTop);
},
onReachBottom() {},
methods: {
gotoUrlNew(item,index) {
if(index == 0) {
changeAddress(res) {
this.addressInfo = res;
uni.setStorageSync("SYS_ADDRESS_INFO", JSON.stringify(res));
this.geBenefitPackaget();
},
gotoUrlNew(item, index) {
if (index == 0) {
uni.switchTab({
url:"/pages/index/readingBody"
})
}else if(index == 1) {
url: "/pages/index/readingBody",
});
} else if (index == 1) {
uni.switchTab({
url:"/pages/index/intelligentAgent"
})
url: "/pages/index/intelligentAgent",
});
}
},
viewDetail(item) {
if (item.url) {
uni.navigateTo({
url:"/subPackages/webPage/webPage?url="+encodeURIComponent(item.url)
})
return
url:
"/subPackages/webPage/webPage?url=" + encodeURIComponent(item.url),
});
return;
}
uni.navigateTo({
url:'/subPackages/letter/detail?id='+item.id
})
url: "/subPackages/letter/detail?id=" + item.id,
});
},
getList() {
//
this.Post({
this.Post(
{
type_id: 3,
position: 17,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.topBanner = res.data;
}
});
},
gotoVideo(item) {
uni.navigateTo({
url: '/subPackages/video/video?item=' + encodeURIComponent(JSON.stringify(item))
})
geBenefitPackaget() {
this.Post(
{
cityId: this.addressInfo&&this.addressInfo.cityId||320500,
},
"/framework/index/benefitPackage/list",
"DES"
).then((res) => {
if (res.data) {
this.productList = res.data.map((item) => {
return {
...item,
image: item.mainUrl,
id: item.benefitPackageId,
avatar:
"https://epic.js-dyyj.com/uploads/20250728/d27ef6e6c26877da7775664fed376c6f.png",
aiName: "文徵明",
};
});
}
});
this.Post(
{
},
"/framework/index/goods/indexList",
"DES"
).then((res) => {
if (res.data) {
this.productListFeeling = res.data.map(item =>{
return {
title:item.goodsName,
price:item.salePrice,
image:item.posterUrl,
id:item.goodsId,
...item
}
})
}
});
},
gotoVideo(item) {
uni.navigateTo({
url:
"/subPackages/video/video?item=" +
encodeURIComponent(JSON.stringify(item)),
});
},
//
handleLikeToggle({ item, index }) {
this.productList[index] = item;
},
//
handleIslandToggle(isExpanded) {
console.log("灵动岛状态切换:", isExpanded ? "展开模式" : "紧凑模式");
},
//
handleIslandAction() {
uni.showToast({
title: "执行操作",
icon: "none",
});
console.log("执行操作");
},
//
handleContentClick() {
//
if (this.$refs.dynamicIsland) {
this.$refs.dynamicIsland.collapseIsland();
}
},
},
};
</script>
<style>
page {
}
</style>
<style lang="scss" scoped>
.bg {
.bg {
min-height: 100vh;
background: #FFFFFF;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.content{
height: calc(100vh - 123rpx);
width: 100%;
}
.top-banner {
}
/* 页面样式 */
.content {
height: 100%;
width: 100%;
height: calc(100vh - 123rpx);
}
padding: 0 20rpx;
box-sizing: border-box;
margin-top: 20rpx;
}
.tab-bar-placeholder {
height: 143rpx;
width: 100%;
}
</style>

9
pages/index/intelligentAgent.vue

@ -1,11 +1,13 @@
<template>
<view class="bg">
<BackButton />
<view class="content">
<swiper class="top-banner" :indicator-dots="false" :autoplay="false" v-if="topBanner && topBanner.length > 0">
<!-- <swiper class="top-banner" :indicator-dots="false" :autoplay="false" v-if="topBanner && topBanner.length > 0">
<swiper-item v-for="(item, index) in topBanner" :key="index" @click.stop="gotoUrlNew(item)">
<image class="top-banner" :src="showImg(item.head_img)" mode="aspectFill" lazy-load="true"></image>
</swiper-item>
</swiper>
</swiper> -->
<image class="top-banner" :src="showImg('/uploads/20250821/ebc938df8942dee581971d0584980fba.gif')" mode="aspectFill" lazy-load="true"></image>
</view>
<CustomTabBar :currentTab="3" />
<MusicControl />
@ -16,8 +18,9 @@
import headerVue from "@/components/header.vue"
import CustomTabBar from '@/components/CustomTabBar.vue';
import MusicControl from '@/components/MusicControl.vue';
import BackButton from "@/components/BackButton.vue";
export default {
components: {CustomTabBar,headerVue,MusicControl},
components: {CustomTabBar,headerVue,MusicControl,BackButton},
data() {
return {
topBanner: []

442
pages/index/readingBody.vue

@ -1,16 +1,87 @@
<template>
<view class="bg">
<headerVue :type="'article'" />
<view class="start-swiper" v-show="isShow && startSwiper && startSwiper.length > 0" @click="this.isShow = false">
<swiper class="start-swiper" :indicator-dots="false" :autoplay="true" :interval="5000" :duration="500"
@change="onSwiperChange">
<view class="category-scroll">
<video
src="https://des.dayunyuanjian.cn/epicSoul/categorys.mp4"
objectFit="cover"
></video>
</view>
<view class="title-box-about">
关于阅读体
<image
src="https://des.dayunyuanjian.cn/data/2025/08/29/a1bfbd3e-f63c-4dea-8aa6-9008bcf129c6.png"
mode=""
>
</image>
</view>
<view
class="start-swiper"
v-show="isShow && startSwiper && startSwiper.length > 0"
@click="this.isShow = false"
>
<swiper
class="start-swiper"
:indicator-dots="false"
:autoplay="true"
:interval="5000"
:duration="500"
@change="onSwiperChange"
>
<swiper-item v-for="(item, index) in startSwiper" :key="index">
<image :src="showImg(item.head_img)" mode="aspectFill"></image>
</swiper-item>
</swiper>
</view>
<view class="desc-box">
<view class="">
Epic soul交响 |
阅读体以数字文化资产IP为核心以消费产品创新为导向每个生命都有史诗般的灵魂作为创作宗旨通过文字图像音视频交互式展览等多种技术工具创作完成的轻量化数字文化内容产品邀您一起收藏这颗星球所有未被传唱的英雄史诗
</view>
<view class="">
Epic
soul交响|阅读体将长期关注乡村振兴城市更新文化焕新等领域,践行探索未来文化信息的解构分发和互动方式
</view>
</view>
<image
style="width: 700rpx; height: 14rpx; margin: 80rpx auto; display: block"
:src="showImg('/uploads/20250829/f7214bc2a4f4e236561de893ca7b9113.png')"
></image>
<view class="title-box">
阅读合集
<image
src="https://des.dayunyuanjian.cn/epicSoul/readingBody/titleSpan.png"
mode=""
></image>
</view>
<image
style="width: 700rpx; height: 23.5rpx; margin: 30rpx auto; display: block"
src="https://des.dayunyuanjian.cn/data/2025/08/29/ee445087-f64d-40a1-861b-586d1a9c7e31.png"
></image>
<view
class="new-book-body"
@click="gotoUrlNew(readingList[0])"
v-if="readingList.length"
>
<view
class="newBookCard"
style=""
:style="{ backgroundImage: `url(${showImg(readingList[0].image)})` }"
>
</view>
<!-- <image
style="width: 583rpx; height: 706rpx"
mode="aspectFill"
:src="showImg(readingList[0].image)"
></image> -->
<view class="new-book-body-title" style="">
一只蟹的生命远征
</view>
<view class="new-book-body-desc">
本期由DAYUN远见文化工厂携手清水村阳澄湖大闸蟹推出苏青壳IP赋魂讲述蟹灵自山海初生万里逆行终归阳澄澄波的史诗清水至净守蟹人循时以待金爪青背蜕变成天地诗篇这是一次湖光与生命的壮丽合奏见证蜕变见证信仰见证原乡
</view>
</view>
<!-- <view class="top-box">
<swiper class="carousel-swiper" :indicator-dots="false" :autoplay="true" :interval="5000" :duration="500"
@change="onSwiperChange">
@ -26,15 +97,23 @@
</view>
</view> -->
<uni-swiper-dot class="uni-swiper-dot-box" :info="swiperList" :current="current" :mode="mode"
<!-- <uni-swiper-dot class="uni-swiper-dot-box" :info="swiperList" :current="current" :mode="mode"
:dots-styles="dotsStyles" field="content">
<swiper class="swiper-box" @change="change" :current="swiperDotIndex" :style="{height : swiperHeight + 'px'}">
<swiper-item v-for="(item, index) in swiperList" :key="index" @click="gotoUrlNew(item)">
<image :src="showImg(item.head_img)" mode="widthFix" :id="`swiper-item-${index}`"></image>
</swiper-item>
</swiper>
</uni-swiper-dot>
</uni-swiper-dot> -->
<image
style="
width: 700rpx;
height: 23.5rpx;
margin: 50rpx auto 30rpx;
display: block;
"
src="https://des.dayunyuanjian.cn/data/2025/08/29/d23d170b-9ae8-4306-a6f3-193327d844f8.png"
></image>
<!-- <swiper class="category-scroll">
<swiper-item v-for="(item, index) in categories" :key="index" @click="gotoUrlNew(item)">
<image :src="showImg(item.head_img)" mode="aspectFill"></image>
@ -42,25 +121,38 @@
</swiper> -->
<!-- <view class="category-scroll" v-if="categories && categories.length > 0">
<video src="https://static.ticket.sz-trip.com/epicSoul/category.mp4" :poster="showImg(categories[0].head_img)" objectFit="cover"></video>
<video src="https://des.dayunyuanjian.cn/epicSoul/category.mp4" :poster="showImg(categories[0].head_img)" objectFit="cover"></video>
</view> -->
<view class="category-scroll">
<video src="https://static.ticket.sz-trip.com/epicSoul/categorys.mp4" objectFit="cover"></video>
</view>
<view class="title-box">
阅读合集
<image src="https://static.ticket.sz-trip.com/epicSoul/readingBody/titleSpan.png" mode=""></image>
</view>
<view class="reading-title flex-between" v-if="false">
<!-- <view v-for="(item,index) in readingType" :key="index" @click="changeType(index)">{{item.title}}</view>
<span :style="{left: readingIndex == 0 ? '32rpx' : (readingIndex == 1 ? '245rpx' : '448rpx')}"></span> -->
</view>
<view class="reading-box">
<image v-for="(item,index) in readingList" :key="index" :src="showImg(item.image)" @click="gotoUrlNew(item)"></image>
<!-- <image :src="showImg('/uploads/20250903/1aadd1b9a4c94ff0aa6b3f880a725b50.png')" @click="gotoUrlNew({jump_type:2,front_model:{mini:'/xqk/home/home'}})"></image> -->
<view
class="reading-box-item"
v-for="(item, index) in readingList.slice(1)"
v-if="item.image && item.front_model && item.front_model.mini"
:key="index"
@click="gotoUrlNew(item)"
>
<view
:style="{ backgroundImage: `url(${showImg(item.image)})` }"
class="img-wrapper"
>
</view>
<!-- <image
mode="aspectFill"
:src="showImg(item.image)"
@click="gotoUrlNew(item)"
></image> -->
<view class="reading-box-item-title">
{{ item.title }}
</view>
<view class="reading-box-item-desc">
{{ descList[index] }}
</view>
</view>
</view>
<CustomTabBar :currentTab="1" />
@ -69,14 +161,14 @@
</template>
<script>
import headerVue from "@/components/header.vue";
import CustomTabBar from '@/components/CustomTabBar.vue';
import MusicControl from '@/components/MusicControl.vue';
export default {
import headerVue from "@/components/header.vue";
import CustomTabBar from "@/components/CustomTabBar.vue";
import MusicControl from "@/components/MusicControl.vue";
export default {
components: {
headerVue,
CustomTabBar,
MusicControl
MusicControl,
},
data() {
return {
@ -84,136 +176,166 @@
current: 0,
swiperDotIndex: 0,
swiperHeight: 524,
mode: 'round',
mode: "round",
dotsStyles: {
selectedBackgroundColor: 'rgb(133, 133, 133)',
selectedBorder: 'none',
backgroundColor: 'rgba(150, 150, 150, 0.5)',
border: 'none'
selectedBackgroundColor: "rgb(133, 133, 133)",
selectedBorder: "none",
backgroundColor: "rgba(150, 150, 150, 0.5)",
border: "none",
},
currentIndex: 0,
categories: [],
readingList: [],
readingType: [{title: '2025', id: 3}, {title: '2026', id: 6}, {title: '全部', id: ''},],
readingType: [
{
title: "2025",
id: 3,
},
{
title: "2026",
id: 6,
},
{
title: "全部",
id: "",
},
],
readingIndex: 0,
startSwiper: [],
isShow: true
}
isShow: true,
descList: [
"本期由DAYUN远见文化工厂与吴文化博物馆联合推出,将其馆藏文物“元代朱碧山造银槎杯”活化为数字IP。这不仅是一次文物活化的深度实践,更是一份献给现代人的精神远航指南,邀你随时登船,自在漂浮。",
"颠覆“笨猪”的刻板印象,讲述一段关于文明、家国、美味与勇气的趣味进化史。为新中式烘焙品牌圣百合提供文化寻根的灵感,让最寻常的食物也能讲述不凡的故事。",
"以一根不眠之“丝”为线索,展现其从天虫吐丝的生命史诗,到织就华夏文明经纬的惊人跨度。致力于让古老的丝绸非遗,重焕新生,成为可被穿戴的东方美学。",
"以一缕千年柏香为引,探索人类被低估的“嗅觉指纹” 。通过对千年古柏背后人文风骨的挖掘,为新中式香氛产业提供深度文化叙事,将无形的意境转化为可被感知的灵魂香气。",
"以“三个桃子”数字IP为核心,追溯一颗果实260万年的迁徙史与文化象征,赋能国家地理标志农产品,将一颗普通的果实,塑造成蕴含中国桃园江湖情义的味觉与美学信物 。",
],
};
},
onReady() {
this.sendRequest()
this.sendRequest();
},
onShow() {
const app = getApp();
if(app.globalData.musicSrc != 'https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3') {
app.updateMusicSrc('https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3');
if (
app.globalData.musicSrc !=
"https://des.dayunyuanjian.cn/epicSoul/EpicSouls.mp3"
) {
app.updateMusicSrc(
"https://des.dayunyuanjian.cn/epicSoul/EpicSouls.mp3"
);
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
}
//
this.pauseAllOtherAudio();
},
methods: {
sendRequest() {
this.Post({
this.Post(
{
type_id: 3,
position: 2,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.swiperList = res.data;
setTimeout(() => {
this.$nextTick(() => {
this.change({detail: {current: 0}})
})
},500)
this.change({
detail: {
current: 0,
},
});
});
}, 500);
}
});
this.Post({
this.Post(
{
type_id: 3,
position: 1,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.categories = res.data;
}
});
this.Post({
this.Post(
{
type_id: 3,
position: 4,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.startSwiper = res.data;
}
});
this.getList()
this.getList();
},
//
getList() {
this.Post({
this.Post(
{
type_id: this.readingType[this.readingIndex].id,
offset: 0,
limit: 1
}, '/api/article/getArticleByType').then(res => {
if(res.data) {
this.readingList = res.data
limit: 10,
},
"/api/article/getArticleByType"
).then((res) => {
if (res.data) {
console.log('----',res.data)
this.readingList = res.data;
}
})
});
},
change(e) {
this.current = e.detail.current
let element = '#swiper-item-' + e.detail.current
let query = uni.createSelectorQuery().in(this)
query.select(element).boundingClientRect()
this.current = e.detail.current;
let element = "#swiper-item-" + e.detail.current;
let query = uni.createSelectorQuery().in(this);
query.select(element).boundingClientRect();
query.exec((res) => {
if (res && res[0]) {
this.swiperHeight = res[0].height
this.swiperHeight = res[0].height;
}
})
});
},
onSwiperChange(e) {
this.currentIndex = e.detail.current;
},
changeType(index) {
this.readingIndex = index
this.getList()
this.readingIndex = index;
this.getList();
},
viewDetail(item) {
if (item.url) {
uni.navigateTo({
url:"/subPackages/webPage/webPage?url="+encodeURIComponent(item.url)
})
return
url:
"/subPackages/webPage/webPage?url=" + encodeURIComponent(item.url),
});
return;
}
uni.navigateTo({
url:'/subPackages/letter/detail?id='+item.id
})
url: "/subPackages/letter/detail?id=" + item.id,
});
},
// AudioControl
pauseAllOtherAudio() {
try {
// AudioControl
if (uni.$globalAudio && uni.$globalAudio.isAudioPlaying()) {
uni.$globalAudio.pauseCurrentAudio();
console.log('readingBody: 暂停其他音频,只保留背景音乐');
}
} catch (error) {
console.error('readingBody: 暂停音频失败:', error);
}
}
}
}
},
};
</script>
<style lang="scss" scoped>
.bg {
.bg {
padding-bottom: 200rpx;
}
}
.swiper-box, .top-box {
margin: 60rpx 25rpx 0;
.swiper-box,
.top-box {
margin: 10rpx 25rpx 0;
height: 830rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
border-radius: 20rpx;
@ -225,9 +347,9 @@
width: 100%;
height: 100%;
}
}
}
.carousel-swiper {
.carousel-swiper {
width: 582.13rpx;
height: 706.24rpx;
margin: -40rpx auto 0;
@ -236,18 +358,18 @@
width: 100%;
height: 100%;
}
}
}
.exhibit-issue {
.exhibit-issue {
font-family: serif;
font-size: 28rpx;
color: #999;
font-style: italic;
padding: 40rpx 30rpx 0 0;
text-align: right;
}
}
.custom-indicators {
.custom-indicators {
display: flex;
justify-content: center;
gap: 8rpx;
@ -265,9 +387,9 @@
width: 20rpx;
background-color: rgb(133, 133, 133);
}
}
}
.category-scroll {
.category-scroll {
// padding: 40rpx 0 0 40rpx;
// display: flex;
height: 393rpx;
@ -286,27 +408,40 @@
height: 393rpx;
border-radius: 20rpx;
}
}
}
.title-box {
.title-box {
font-size: 36rpx;
font-weight: 500;
margin-top: 40rpx;
padding-left: 60rpx;
padding-left: 25rpx;
image {
width: 185.85rpx;
height: 20.98rpx;
margin-left: 21rpx;
}
}
.title-box-about {
font-size: 36rpx;
font-weight: 500;
margin-top: 50rpx;
padding-left: 25rpx;
image {
width: 106.5rpx;
height: 20.98rpx;
margin-left: 21rpx;
}
}
.reading-title {
.reading-title {
margin: 60rpx auto 0;
width: 585rpx;
height: 30rpx;
border-radius: 15rpx;
background-color: rgba(46, 220, 78, .8);
background-color: rgba(46, 220, 78, 0.8);
font-weight: bold;
font-size: 28rpx;
color: #000000;
@ -318,10 +453,12 @@
z-index: 20;
bottom: 15rpx;
}
&>view:nth-child(2) {
& > view:nth-child(2) {
left: 268rpx;
}
&>view:nth-child(3) {
& > view:nth-child(3) {
right: 55rpx;
}
@ -333,28 +470,51 @@
position: absolute;
left: 32rpx;
}
}
}
.reading-box {
.reading-box {
margin: 20rpx 25rpx 0;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
padding: 35rpx 10rpx 5rpx;
padding: 35rpx 0rpx 5rpx;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
position: relative;
// top: -15rpx;
background-color: #fff;
gap: 16rpx;
image {
width: 212rpx;
height: 376rpx;
margin-bottom: 30rpx;
width: 100%;
height: 346rpx;
}
.reading-box-item {
flex: 0 0 calc(50% - 20rpx);
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
background-color: #fff;
padding: 20rpx;
font-size: 30rpx;
}
.reading-box-item-title {
margin: 15rpx 0;
font-weight: bold;
}
.reading-box-item-desc {
font-size: 20rpx;
color: #808080;
}
}
.start-swiper {
.reading-box::after {
content: '';
flex: 0 0 calc(50% - 20rpx);
height: 0;
}
.start-swiper {
width: 100vw;
height: calc(100vh - 123rpx);
position: fixed;
@ -366,5 +526,63 @@
width: 100%;
height: 100%;
}
}
.desc-box {
padding: 0 25rpx;
color: #616161;
margin: 30rpx 0;
font-size: 24rpx;
padding: 0 40rpx;
view {
margin-bottom: 20rpx;
}
}
.new-book-body {
margin: 20rpx 25rpx 0;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
padding: 50rpx 0rpx 30rpx;
background-color: #fff;
text-align: left;
image {
margin: 0 auto;
display: block;
}
.new-book-body-title {
width: 583rpx;
font-size: 37rpx;
font-weight: bold;
margin: 20rpx auto;
}
.new-book-body-desc {
width: 583rpx;
color: #808080;
font-size: 24rpx;
margin: 20rpx auto;
}
}
.newBookCard {
width: 583rpx;
height: 706rpx;
margin: 0 auto;
background-size: 100% auto;
background-repeat: no-repeat;
background-position: top;
background-color: #fff;
}
.img-wrapper {
width: 100%;
height: 346rpx;
margin: 0 auto;
background-size: 100% auto;
background-repeat: no-repeat;
background-position: top;
background-color: #fff;
}
</style>

262
pages/index/sensoryStore - 副本.vue

@ -0,0 +1,262 @@
<template>
<view class="bg">
<BackButton />
<!-- <headerVue :type="'goods'"></headerVue> -->
<view class="banner-content">
<swiper class="top-banner" :circular="true" :interval="6000" :duration="800"
:indicator-dots="false" :autoplay="true" @change="swiperChange" >
<swiper-item v-for="(item, index) in topBanner" :key="index" @click.stop="gotoUrlNew(item)">
<image class="top-banner" :src="showImg(item.head_img)" mode="aspectFill"></image>
</swiper-item>
</swiper>
<view class="dot-container">
<view :class="['dot-line',index==swiperIndex?'active':'']" v-for="(item, index) in topBanner" :key="index"></view>
</view>
</view>
<view class="desc-box">
<view class="title-sec" style="color: black;">
关于有感商品
</view>
<view class="">
寻常商品满足你的日常所需有感商品则回应你的精神所向
</view>
<view class="">
我们坚信意义大于产品这里的每一件商品都诞生于EPIC SOUL交响阅读体的史诗是精神漫游的实体回响它的存在是为了让你在消费中完成一次次深刻的情感连接与自我认同
</view>
<view class="">
在这里消费的终点不是拥有而是更深刻的连接与共鸣欢迎探索一件件写满故事与想象力的生活信物
</view>
</view>
<view class="product-content">
<!-- <image class="head-img" src="https://static.ticket.sz-trip.com/uploads/20250625/e3112c280ef9761af741907a737ef221.png"></image> -->
<view class="title-sec">
有感商品上新
</view>
<scroll-view style="width: 100%;" scroll-x>
<view class="product">
<view class="item" v-for="(item,i) in list" :key="item.goods.id" @click="goDetailByType(item)">
<image class="item-img" :src="showImg(item.goods.image)"></image>
<view class="content">
<view class="title text-overflow">{{item.goods.title}}</view>
<view class="bottom">
<view class="price">{{item.goods.money/100}}</view>
<image src="https://des.dayunyuanjian.cn/epicSoul/readingBody/gwc.png" class="buy-cart"></image>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- <image style="margin: 53rpx 0 35rpx;" class="head-img" src="https://static.ticket.sz-trip.com/uploads/20250627/73153098ff5c115e02afb0328ade1e29.png"></image> -->
<view class="title-sec">
有感商品精选
</view>
<view class="img-container">
<image v-for="(type,i) in typeList" :key="i" :src="showImg(type.img)"
@click="gotoPath(`/subPackages/haveFeeling/detailXiang?id=${type.id}`)"></image>
</view>
</view>
<!-- <CustomTabBar :currentTab="2" /> -->
<MusicControl />
</view>
</template>
<script>
import headerVue from "@/components/header.vue"
import CustomTabBar from '@/components/CustomTabBar.vue';
import MusicControl from '@/components/MusicControl.vue';
import BackButton from "@/components/BackButton.vue";
export default {
components: {CustomTabBar,headerVue,MusicControl,BackButton},
data() {
return {
topBanner: [],
list: [],
swiperIndex: 0,
typeList: [],
}
},
onLoad() {
},
onReady() {
this.getProduct()
this.getTypes()
this.getList()
},
onReachBottom() {
},
methods: {
swiperChange(e) {
this.swiperIndex = e.detail.current
},
viewDetail(item) {
if (item.url) {
uni.navigateTo({
url:"/subPackages/webPage/webPage?url="+encodeURIComponent(item.url)
})
return
}
uni.navigateTo({
url:'/subPackages/letter/detail?id='+item.id
})
},
getProduct () {
this.Post({
tag_id: 40,
offset: 0,
},'/api/tag/getGoodsByTagId').then(res => {
this.list = res.data;
})
},
getList() {
//
this.Post({
type_id: 3,
position: 18,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
this.topBanner = res.data;
}
});
},
//
getTypes () {
this.Post({
parent_id: 0,
}, '/api/goods/type').then(res => {
if(res.data) {
this.typeList = res.data;
}
});
},
}
}
</script>
<style lang="scss" scoped>
.bg {
min-height: 100vh;
background: #FFFFFF;
padding-bottom: 200rpx;
}
.banner-content{
width: 100%;
height: 496.4rpx;
position: relative;
.top-banner {
width: 100%;
height: 100%;
}
.dot-container{
position: absolute;
bottom: 43rpx;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
left: 0;
.dot-line{
width: 52rpx;
height: 4rpx;
margin: 0 8rpx;
background: RGBA(189, 170, 173, 0.8);
&.active{
background: RGBA(255, 255, 255, 0.8);
}
}
}
}
.head-img{
width: 697.24rpx;
height: 42.57rpx;
margin: 0 auto;
display: block;
}
.product-content{
padding: 63rpx 26rpx 0;
padding-top: 0;
.product{
padding: 36rpx 0 0;
padding-top: 0;
display: flex;
flex-wrap: nowrap;
// justify-content: space-between;
.item{
width: 214.69rpx;
margin-right: 20rpx;
}
.item-img{
width: 214.69rpx;
height: 286.33rpx;
}
.content{
height: 80rpx;
padding: 0rpx 4rpx;
font-weight: 400;
font-size: 24rpx;
color: #000000;
}
.bottom{
display: flex;
padding-top: 13rpx;
justify-content: space-between;
align-items: center;
}
.buy-cart{
width: 28rpx;
height: 24rpx;
}
}
}
.img-container{
width: 100%;
image{
display: block;
width: 100%;
height: 314rpx;
border-radius: 40rpx;
margin-bottom: 20rpx;
}
}
.line{
width: 220rpx;
height: 1rpx;
background: #E4E4E4;
flex-shrink: 0;
}
.head-img-yougan{
width: 183.45rpx;
height: 42.57rpx;
}
.title-sec{
font-size: 34rpx;
font-weight: 500;
margin: 30rpx 0;
}
.desc-box{
padding: 0 20rpx;
color: #616161;
margin: 30rpx 0;
font-size: 24rpx;
padding: 0 30rpx;
view{
margin-bottom: 20rpx;
}
}
</style>

136
pages/index/sensoryStore.vue

@ -1,12 +1,13 @@
<template>
<view class="bg">
<headerVue :type="'goods'"></headerVue>
<BackButton />
<!-- <headerVue :type="'goods'"></headerVue> -->
<view class="banner-content">
<swiper class="top-banner" :circular="true" :interval="6000" :duration="800"
:indicator-dots="false" :autoplay="true" @change="swiperChange" >
<swiper-item v-for="(item, index) in topBanner" :key="index" @click.stop="gotoUrlNew(item)">
<image class="top-banner" :src="showImg(item.head_img)" mode="aspectFill"></image>
<swiper-item v-for="(item, index) in topBanner" :key="index" @click.stop="gotoUrlNewDes(item)">
<image class="top-banner" :src="showImg(item.image)" mode="aspectFill"></image>
</swiper-item>
</swiper>
@ -15,19 +16,35 @@
</view>
</view>
<view class="desc-box">
<view class="title-sec" style="color: black;">
关于有感商品
</view>
<view class="">
寻常商品满足你的日常所需有感商品则回应你的精神所向
</view>
<view class="">
我们坚信意义大于产品这里的每一件商品都诞生于EPIC SOUL交响阅读体的史诗是精神漫游的实体回响它的存在是为了让你在消费中完成一次次深刻的情感连接与自我认同
</view>
<view class="">
在这里消费的终点不是拥有而是更深刻的连接与共鸣欢迎探索一件件写满故事与想象力的生活信物
</view>
</view>
<view class="product-content">
<image class="head-img" src="https://static.ticket.sz-trip.com/uploads/20250625/e3112c280ef9761af741907a737ef221.png"></image>
<!-- <image class="head-img" src="https://static.ticket.sz-trip.com/uploads/20250625/e3112c280ef9761af741907a737ef221.png"></image> -->
<view class="title-sec">
有感商品上新
</view>
<scroll-view style="width: 100%;" scroll-x>
<view class="product">
<view class="item" v-for="(item,i) in list" :key="item.goods.id" @click="goDetailByType(item)">
<image class="item-img" :src="showImg(item.goods.image)"></image>
<view class="item" v-for="(item,i) in list" :key="item.goodsId" @click="gotoPath(`/subPackages/techan/detail?id=${item.goodsId}`)">
<image mode="aspectFill" class="item-img" :src="showImg(item.mainUrl.split(',')[0])"></image>
<view class="content">
<view class="title text-overflow">{{item.goods.title}}</view>
<view class="title text-overflow">{{item.goodsName}}</view>
<view class="bottom">
<view class="price">{{item.goods.money/100}}</view>
<image src="https://static.ticket.sz-trip.com/epicSoul/readingBody/gwc.png" class="buy-cart"></image>
<view class="price">{{item.salePrice}}</view>
<!-- <image @click.stop="addShopCart(item)" src="https://des.dayunyuanjian.cn/epicSoul/readingBody/gwc.png" class="buy-cart"></image> -->
</view>
</view>
</view>
@ -35,14 +52,16 @@
</view>
</scroll-view>
<image style="margin: 53rpx 0 35rpx;" class="head-img" src="https://static.ticket.sz-trip.com/uploads/20250627/73153098ff5c115e02afb0328ade1e29.png"></image>
<!-- <image style="margin: 53rpx 0 35rpx;" class="head-img" src="https://static.ticket.sz-trip.com/uploads/20250627/73153098ff5c115e02afb0328ade1e29.png"></image> -->
<view class="title-sec">
有感商品精选
</view>
<view class="img-container">
<image v-for="(type,i) in typeList" :key="i" :src="showImg(type.img)"
@click="gotoPath(`/subPackages/haveFeeling/detailXiang?id=${type.id}`)"></image>
<image mode="aspectFill" v-for="(type,i) in typeList" :key="i" :src="showImg(type.image)"
@click="gotoUrlNewDes(type)"></image>
</view>
</view>
<CustomTabBar :currentTab="2" />
<!-- <CustomTabBar :currentTab="2" /> -->
<MusicControl />
</view>
</template>
@ -51,8 +70,9 @@
import headerVue from "@/components/header.vue"
import CustomTabBar from '@/components/CustomTabBar.vue';
import MusicControl from '@/components/MusicControl.vue';
import BackButton from "@/components/BackButton.vue";
export default {
components: {CustomTabBar,headerVue,MusicControl},
components: {CustomTabBar,headerVue,MusicControl,BackButton},
data() {
return {
topBanner: [],
@ -65,9 +85,7 @@
},
onReady() {
this.getProduct()
this.getTypes()
this.getList()
this.getInfo()
},
onReachBottom() {
@ -76,6 +94,23 @@
swiperChange(e) {
this.swiperIndex = e.detail.current
},
getInfo(){
this.Post({
code:'ygsp_top'
},'/framework/banner/getImageByBannerCode',"DES").then(res => {
this.topBanner = res.data;
})
this.Post({
},'/framework/goods/ygPageList',"DES").then(res => {
this.list = res.rows;
})
this.Post({
code:'ygsp_bottom'
},'/framework/banner/getImageByBannerCode',"DES").then(res => {
this.typeList = res.data;
})
},
viewDetail(item) {
if (item.url) {
@ -88,45 +123,15 @@
url:'/subPackages/letter/detail?id='+item.id
})
},
getProduct () {
this.Post({
tag_id: 40,
offset: 0,
},'/api/tag/getGoodsByTagId').then(res => {
this.list = res.data;
})
},
getList() {
//
this.Post({
type_id: 3,
position: 18,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
this.topBanner = res.data;
}
});
},
//
getTypes () {
this.Post({
parent_id: 0,
}, '/api/goods/type').then(res => {
if(res.data) {
this.typeList = res.data;
}
});
},
}
}
</script>
<style lang="scss" scoped>
@font-face {
font-family: "Futura";
src: url(https://des.dayunyuanjian.cn/epicSoul/taozi/fonts/Futura.ttc);
}
.bg {
min-height: 100vh;
background: #FFFFFF;
@ -168,18 +173,20 @@
}
.product-content{
padding: 63rpx 26rpx 0;
padding-top: 0;
.product{
padding: 36rpx 0 0;
padding-top: 0;
display: flex;
flex-wrap: nowrap;
// justify-content: space-between;
.item{
width: 214.69rpx;
width: 280rpx;
margin-right: 20rpx;
}
.item-img{
width: 214.69rpx;
height: 286.33rpx;
width: 280rpx;
height: 280rpx;
}
.content{
height: 80rpx;
@ -193,6 +200,7 @@
padding-top: 13rpx;
justify-content: space-between;
align-items: center;
font-family: "Futura";
}
.buy-cart{
width: 28rpx;
@ -221,4 +229,20 @@
width: 183.45rpx;
height: 42.57rpx;
}
.title-sec{
font-size: 34rpx;
font-weight: 500;
margin: 30rpx 0;
}
.desc-box{
padding: 0 20rpx;
color: #616161;
margin: 30rpx 0;
font-size: 24rpx;
padding: 0 30rpx;
view{
margin-bottom: 20rpx;
}
}
</style>

599
pages/index/timeShopBank.vue

@ -0,0 +1,599 @@
<template>
<view class="page-container">
<!-- 导航栏组件 -->
<headerVue fixed></headerVue>
<!-- 灵动岛组件 -->
<DynamicIsland
ref="dynamicIsland"
:page-id="'timeShopBank_page'"
:style-type="'timeShop'"
/>
<view class="desc-box">
<view class=""> 欢迎来到旅行时间行你的精神财富储蓄所 </view>
<view class="">
在这里你的每一次人文漫游每一次灵感闪现都值得被郑重记录我们鼓励你分享高质量的图文笔记将旅途中的美与感动化为这座精神星球上的璀璨星辰
</view>
<view class="">
为他人的美好驻足点赞留言每一次真诚的互动都是在为你的时间银行存入一笔宝贵的精神货币这些资产不仅可以兑换独家福利与实体好物更能为你解锁专属的荣誉身份让你成为这座星球上最闪耀的共创者
</view>
<view class=""> 即刻发布你的第一篇笔记开启你的财富积累之旅吧 </view>
</view>
<image
style="width: 700rpx; height: 14rpx; margin: 20rpx auto; display: block"
:src="showImg('/uploads/20250829/f7214bc2a4f4e236561de893ca7b9113.png')"
></image>
<!-- Tab切换组件 -->
<view class="tab-container">
<view class="tab-wrapper">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: currentTab === index }"
@click="switchTab(index)"
>
<text class="tab-text">{{ tab.name }}</text>
<view v-if="currentTab === index" class="tab-indicator"></view>
</view>
</view>
</view>
<!-- 内容区域 -->
<view class="content-area">
<!-- <view v-if="currentTab == 0" class="notes-content">
<WaterfallLayout
:items="waterfallItems"
:column-count="2"
:column-gap="16"
:item-gap="16"
@item-click="handleItemClick"
@like-change="handleNoteLikeChange"
style="margin-top: 20rpx"
/>
</view> -->
<view v-if="currentTab === 0" class="follow-content recommend-content">
<text class="coming-soon">笔记功能开发中...</text>
</view>
<!-- 关注tab内容 -->
<view v-if="currentTab == 1" class="follow-content recommend-content">
<!-- <FollowTab ref="followTab" /> -->
<text class="coming-soon">关注功能开发中...</text>
</view>
<!-- 推荐tab内容 -->
<!-- <view v-if="currentTab == 2" class="notes-content">
<WaterfallLayout
:items="waterfallItems"
:column-count="2"
:column-gap="16"
:item-gap="16"
@item-click="handleItemClick"
@like-change="handleNoteLikeChange"
style="margin-top: 20rpx"
/>
</view> -->
<view v-if="currentTab ==2" class="notes-content recommend-content">
<text class="coming-soon">笔记功能开发中...</text>
</view>
</view>
<!-- <view class="fab-container" v-if="canPublish"> -->
<!-- <image
@click="goToPublish"
:src="showImg('/uploads/20250825/7ea7864b8abb89c3dd7834f025e49b3f.png')"
style="width: 91rpx; height: 91rpx"
></image>
</view> -->
<!-- 控制按钮 -->
<!-- <view class="controls">
<button @click="addRandomItem" class="control-btn primary">
添加项目
</button>
<button @click="clearAllItems" class="control-btn danger">清空</button>
</view> -->
<!-- 底部占位区域防止内容被TabBar遮挡 -->
<view class="tab-bar-placeholder"></view>
<!-- 底部TabBar组件 -->
<CustomTabBar :currentTab="2" />
</view>
</template>
<script>
import WaterfallLayout from "@/components/WaterfallLayout.vue";
import headerVue from "@/components/header.vue";
import CustomTabBar from "@/components/CustomTabBar.vue";
import DynamicIsland from "@/components/DynamicIsland.vue";
import FollowTab from "./components/FollowTab.vue";
export default {
name: "TimeShopBank",
components: {
WaterfallLayout,
headerVue,
CustomTabBar,
DynamicIsland,
FollowTab,
},
data() {
return {
currentTab: 2, // ""
tabs: [
{ name: "笔记", id: "notes" },
{ name: "关注", id: "follow" },
{ name: "推荐", id: "recommend" },
],
waterfallItems: [],
//
pageNum: 1,
pageSize: 10,
loading: false,
hasMore: true,
autoAddEnabled: false,
whiteListUsers: [], // ID
};
},
computed: {
//
canPublish() {
//
if (!this.whiteListUsers || this.whiteListUsers.length === 0) {
return true;
}
//
if (!this.userInfo || !this.userInfo.id) {
return false;
}
// ID
return this.whiteListUsers.includes(this.userInfo.id.toString());
},
},
onLoad() {
this.userInfo =
(uni.getStorageSync("userInfo") &&
JSON.parse(uni.getStorageSync("userInfo"))) ||
this.$store.state.user.userInfo ||
{};
//
this.getRecommendList(1);
//
uni.$on("note-like-change", this.handleNoteLikeChange);
},
onShow() {
if (this.userInfo && this.userInfo.id) {
this.getUserInfo();
this.getWhiteListConfig(); //
}
},
//
onUnload() {
uni.$off("note-like-change", this.handleNoteLikeChange);
},
//
onReachBottom() {
this.loadMoreItems();
},
// -
onPageScroll(e) {
// ID
uni.$emit("pageScroll_timeShopBank_page", e.scrollTop);
},
methods: {
getUserInfo() {
this.Post({}, "/framework/user/getInfo", "DES").then((res) => {
res.data.token = this.userInfo.token;
uni.setStorageSync("userInfo", JSON.stringify(res.data));
this.userInfo = res.data;
this.$nextTick(() => {
this.$refs.dynamicIsland.getUserInfo();
});
});
},
//
getWhiteListConfig() {
this.Post({}, `/system/config/configKey/note_white_user`, "DES")
.then((res) => {
if (res.code === 200) {
//
this.whiteListUsers = res.msg.split(",").map((item) => item.trim());
console.log("白名单用户列表:", this.whiteListUsers);
}
})
.catch((err) => {
console.error("获取白名单配置失败:", err);
});
},
//
refreshFollowList() {
// FollowTab
if (this.$refs.followTab) {
this.$refs.followTab.getFollowList();
}
},
//
getRecommendList(type = 1) {
if (type == 0) {
let token = uni.getStorageSync("token1");
if (!token) {
uni.showToast({
title: "请先登录",
icon: "none",
});
uni.navigateTo({
url: "/pages/login/login",
});
return;
}
}
if (this.loading) return;
this.loading = true;
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize,
type: type, // 0: tab, 1: tab
};
this.Post(params, "/framework/note/list", "DES")
.then((res) => {
if (res.code === 200 && res.rows) {
const newItems = res.rows || [];
if (this.pageNum === 1) {
//
this.waterfallItems = newItems;
} else {
//
this.waterfallItems.push(...newItems);
}
//
this.hasMore = newItems.length === this.pageSize;
}
})
.catch((error) => {
console.error("获取推荐列表失败:", error);
uni.showToast({
title: "加载失败,请重试",
icon: "none",
});
})
.finally(() => {
this.loading = false;
});
},
//
clearAllItems() {
uni.showModal({
title: "确认清空",
content: "确定要清空所有项目吗?",
success: (res) => {
if (res.confirm) {
this.waterfallItems = [];
}
},
});
},
//
handleItemClick(item) {
console.log(item);
//
uni.navigateTo({
url: `/pages/notes/detail?id=${item.id}`,
});
},
//
goToPublish() {
console.log(this.userInfo, "userInfo");
if (!this.userInfo.id) {
uni.showToast({
title: "请先登录",
icon: "none",
});
uni.navigateTo({
url: "/pages/login/login",
});
return;
}
//
const userId = this.userInfo.id.toString();
if (
this.whiteListUsers.length > 0 &&
!this.whiteListUsers.includes(userId)
) {
uni.showToast({
title: "暂无发布权限",
icon: "none",
});
return;
}
uni.navigateTo({
url: "/pages/notes/publish",
});
},
//
handleItemAdded(item) {},
//
handleAutoAddRequest() {
this.loadMoreItems();
},
//
loadMoreItems() {
if (!this.loading && this.hasMore) {
this.pageNum++;
// tabtype: 0-, 1-
const type = this.currentTab === 0 ? 0 : 1;
this.getRecommendList(type);
}
},
// Tab
switchTab(index) {
this.currentTab = index;
// tab
this.loadTabContent(this.tabs[index].id);
//
if (this.tabs[index].id === "follow") {
this.refreshFollowList();
}
},
// tab
loadTabContent(tabId) {
// tabId
if (tabId === "notes") {
// tab:
this.pageNum = 1;
this.waterfallItems = [];
this.hasMore = true;
this.getRecommendList(0);
} else if (tabId === "recommend") {
// tab:
this.pageNum = 1;
this.waterfallItems = [];
this.hasMore = true;
this.getRecommendList(1);
}
// tab
},
//
handleNoteLikeChange(data) {
console.log(data, "data");
if (!data || !data.noteId) return;
//
const updateItemInList = (items) => {
for (let i = 0; i < items.length; i++) {
if (items[i].id == data.noteId) {
//
items[i].userLiked = data.isLiked;
items[i].likeCount = data.likeCount;
return true;
}
}
return false;
};
//
const updated = updateItemInList(this.waterfallItems);
console.log(updated, "updated");
//
if (updated) {
// 使Vue
this.$forceUpdate();
}
},
},
};
</script>
<style>
page {
background-color: white;
}
</style>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: white;
color: #333;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
justify-content: center;
margin: 40rpx 0;
padding: 0 20rpx;
}
.control-btn {
padding: 24rpx 48rpx;
border-radius: 48rpx;
font-size: 28rpx;
border: none;
cursor: pointer;
transition: all 0.3s ease;
background: white;
color: #333;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
font-weight: 500;
}
.control-btn.primary {
background: #ff4757;
color: white;
}
.control-btn.danger {
background: #ff6b6b;
color: white;
}
.control-btn:active {
transform: scale(0.95);
}
.tab-bar-placeholder {
height: 120rpx;
}
/* Tab切换样式 */
.tab-container {
background: #ffffff;
padding: 0 32rpx;
margin-top: 20rpx;
margin-bottom: 20rpx;
}
.tab-wrapper {
display: flex;
align-items: center;
justify-content: space-around;
height: 88rpx;
position: relative;
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
position: relative;
}
.tab-text {
font-size: 32rpx;
color: #999999;
font-weight: 400;
transition: all 0.3s ease;
}
.tab-item.active .tab-text {
color: #333333;
font-weight: 600;
}
.tab-indicator {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80rpx;
height: 4rpx;
background: #33fefe;
border-radius: 3rpx;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
width: 0;
opacity: 0;
}
to {
width: 80rpx;
opacity: 1;
}
}
/* 悬浮发布按钮 */
.fab-container {
position: fixed;
bottom: 250rpx;
right: 40rpx;
z-index: 999;
}
.fab-btn {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(135deg, #ff4757, #ff6b7a);
color: #fff;
border: none;
box-shadow: 0 8rpx 24rpx rgba(255, 71, 87, 0.4);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
box-shadow: 0 4rpx 12rpx rgba(255, 71, 87, 0.3);
}
.fab-icon {
font-size: 48rpx;
font-weight: 300;
line-height: 1;
}
}
/* 内容区域样式 */
.content-area {
flex: 1;
overflow: hidden;
padding: 10rpx 0;
}
.notes-content {
position: relative;
height: 100%;
}
.follow-content {
height: 100%;
overflow-y: auto;
}
.recommend-content {
display: flex;
align-items: center;
justify-content: center;
height: 400rpx;
.coming-soon {
font-size: 28rpx;
color: #999;
}
}
.desc-box {
padding: 0 20rpx;
color: #616161;
margin: 30rpx 0;
font-size: 24rpx;
padding: 0 40rpx;
view {
margin-bottom: 20rpx;
&:nth-child(1) {
font-size: 24rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
}
}
/* 自定义样式已移至WaterfallLayout组件内部 */
</style>

347
pages/login/login.vue

@ -1,32 +1,52 @@
<template>
<view class="content" style="overflow-x: auto;">
<view class="search-header" :style="{'height': height+'px','padding-top':statusBarHeight+'px'}">
<view class="title">epic soul</view>
<view class="subtitle">大运河非物质文化遗产</view>
<view class="content">
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 权限申请卡片 -->
<view class="permission-card">
<view class="app-icon">
<text class="icon-text">ES</text>
</view>
<view :style="{'height':height+'px','flex-shrink':0}"></view>
<div style="padding-top: 88rpx;">
<div class="login-tip">epic soul 申请获得</div>
<div class="login-tip2">以下权限</div>
<div class="login-tip-box">
<text>获得你的公开信息昵称头像地区及性别</text>
</div>
<div class="btn-box">
<button bindtap="cancel" type="default" @click="redirectIndex">取消</button>
<button type="primary" @click="getUserInfo">同意</button>
</div>
<div class="flex-center article-box">
<view class="login-tip">Epic Soul 申请获得</view>
<view class="login-tip2">以下权限</view>
<view class="login-tip-box">
<view class="permission-item">
<text class="permission-icon">👤</text>
<text class="permission-text">获得你的公开信息昵称头像地区及性别</text>
</view>
</view>
</view>
<!-- 按钮区域 -->
<view class="btn-container">
<view class="btn-box">
<button class="cancel-btn" @click="redirectIndex">取消</button>
<button class="confirm-btn" @click="getUserInfo">同意</button>
</view>
<!-- 协议同意区域 -->
<view class="agreement-box">
<radio-group @change="toggleAgreement">
<radio value="1" :checked="isAgreed" style="transform:scale(0.7)"></radio>
<label class="agreement-label">
<radio value="1" :checked="isAgreed" class="agreement-radio"></radio>
<text class="agreement-text">同意</text>
<text class="agreement-link" @click="gotoInfo">用户服务协议隐私政策</text>
</label>
</radio-group>
<div>同意<text @click="gotoInfo">用户服务协议隐私政策</text></div>
</div>
</view>
</view>
<!-- 手机号授权弹窗 -->
<uni-popup ref="popup" type="bottom" background-color="#fff">
<button type="default" open-type="getPhoneNumber" @getphonenumber="handlePhoneNumber" style="width: 100%;height: 12vh;line-height: 12vh;">点击授权手机号</button>
<view class="phone-auth-popup">
<view class="popup-title">手机号授权</view>
<view class="popup-desc">为了更好地为您提供服务需要获取您的手机号</view>
<button class="phone-auth-btn" open-type="getPhoneNumber" @getphonenumber="handlePhoneNumber">
点击授权手机号
</button>
</view>
</uni-popup>
</div>
</view>
</view>
</template>
@ -68,13 +88,19 @@ export default {
token: uni.getStorageSync('token1')
}, '/api/mini_program/bindPhoneNumber')
.then(res => {
this.Post({
}, '/framework/points/add','DES')
.then(res => {
})
this.$store.commit('changeUserInfo', res.data.userinfo);
this.navigateBasedOnPath();
})
.catch(error => {
console.error('绑定手机号失败:', error);
uni.showToast({
title: '绑定手机号失败,请稍后重试',
title: error.data.msg,
icon: 'none'
});
});
@ -174,85 +200,274 @@ export default {
</script>
<style scoped lang="scss">
.search-header{
//
.content {
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
position: relative;
}
//
.search-header {
width: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
background: white;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
text-align: center;
z-index:99;
.title{
font-size: 30rpx
z-index: 99;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.title {
font-size: 32rpx;
font-weight: 600;
color: #2c3e50;
letter-spacing: 1rpx;
}
.subtitle{
font-size: 22rpx;
.subtitle {
font-size: 24rpx;
color: #7f8c8d;
margin-top: 4rpx;
}
}
//
.main-content {
padding: 120rpx 40rpx 40rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: calc(100vh - 120rpx);
}
//
.permission-card {
background: white;
border-radius: 24rpx;
padding: 60rpx 40rpx;
margin-bottom: 60rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
text-align: center;
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 8rpx;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 24rpx 24rpx 0 0;
}
}
.content {
min-height: 100vh;
//
.app-icon {
width: 120rpx;
height: 120rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 24rpx;
margin: 0 auto 40rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
.icon-text {
color: white;
font-size: 36rpx;
font-weight: bold;
letter-spacing: 2rpx;
}
}
//
.login-tip {
font-size: 28rpx;
margin: 0 60rpx;
margin-top: 40rpx;
font-size: 32rpx;
color: #2c3e50;
margin-bottom: 16rpx;
font-weight: 500;
}
.login-tip2 {
font-size: 44rpx;
margin: 0rpx 60rpx;
margin-top: 20rpx;
font-weight: 400;
font-size: 48rpx;
color: #2c3e50;
margin-bottom: 40rpx;
font-weight: 600;
}
//
.login-tip-box {
background: #f8f9fa;
border-radius: 16rpx;
padding: 32rpx;
margin-top: 32rpx;
border-left: 6rpx solid #667eea;
}
.permission-item {
display: flex;
align-items: flex-start;
margin: 0 60rpx;
gap: 16rpx;
.permission-icon {
font-size: 32rpx;
line-height: 1;
margin-top: 4rpx;
}
.permission-text {
font-size: 28rpx;
margin-top: 40rpx;
line-height: 40rpx;
color: #5a6c7d;
line-height: 1.6;
flex: 1;
}
}
.login-tip-box .icon-gou1 {
line-height: 80rpx;
margin-top: -20rpx;
margin-right: 30rpx;
color: #666;
//
.btn-container {
// margin-top: auto;
}
//
.btn-box {
display: flex;
gap: 24rpx;
margin-bottom: 40rpx;
button {
flex: 1;
height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
&::after {
border: none;
}
}
.cancel-btn {
background: #f8f9fa;
color: #6c757d;
border: 2rpx solid #e9ecef;
&:active {
background: #e9ecef;
transform: scale(0.98);
}
}
.confirm-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.4);
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
}
&::before {
content: '';
position: absolute;
bottom: 100rpx;
left: 0;
right: 0;
}
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.btn-box button {
width: 400rpx;
&:active::before {
left: 100%;
}
}
}
.article-box {
position: absolute;
left: 0;
right: 0;
//
.agreement-box {
display: flex;
justify-content: center;
font-size: 24rpx;
color: #1aad19;
bottom: 40rpx;
}
align-items: center;
.article-box .iconfont {
margin-right: 10rpx;
.agreement-label {
display: flex;
align-items: center;
gap: 12rpx;
cursor: pointer;
}
.agreement-radio {
transform: scale(0.8);
margin: 0;
}
.agreement-text {
font-size: 26rpx;
color: #6c757d;
}
.agreement-link {
font-size: 26rpx;
color: #667eea;
text-decoration: underline;
margin-left: 8rpx;
&:active {
color: #5a67d8;
}
}
}
.article-box text {
border-bottom: 1px solid;
//
.phone-auth-popup {
padding: 60rpx 40rpx;
text-align: center;
border-radius: 24rpx 24rpx 0 0;
.popup-title {
font-size: 36rpx;
font-weight: 600;
color: #2c3e50;
margin-bottom: 24rpx;
}
.popup-desc {
font-size: 28rpx;
color: #6c757d;
line-height: 1.6;
margin-bottom: 60rpx;
}
.phone-auth-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.4);
transition: all 0.3s ease;
&::after {
border: none;
}
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
}
}
}
</style>

1440
pages/notes/detail.vue

File diff suppressed because it is too large

623
pages/notes/publish.vue

@ -0,0 +1,623 @@
<template>
<view class="publish-container">
<!-- 内容区域 -->
<view class="content-scroll">
<!-- 图片区域 -->
<view class="image-section">
<uni-file-picker
v-model="selectedImages"
mode="grid"
file-mediatype="image"
file-extname="png,jpg,jpeg"
:auto-upload="false"
@select="onImageSelect"
@delete="onImageDelete"
></uni-file-picker>
</view>
<!-- 标题区域 -->
<view class="title-section">
<input
class="title-input"
v-model="noteForm.title"
placeholder="请输入标题..."
maxlength="100"
auto-height
/>
</view>
<!-- 详情区域 -->
<view class="content-section">
<textarea
class="content-input"
v-model="noteForm.content"
placeholder="分享你的想法..."
maxlength="2000"
auto-height
/>
</view>
<!-- 快速标签区域 -->
<view class="quick-tags-section">
<view class="section-title">快速标签</view>
<scroll-view
class="quick-tags-scroll"
scroll-x="true"
show-scrollbar="false"
>
<view class="quick-tags-list">
<view
class="quick-tag-item"
v-for="tag in quickTags"
:key="tag"
@click="insertQuickTag(tag)"
>
#{{ tag.name }}
</view>
</view>
</scroll-view>
</view>
<!-- 已添加标签 -->
<view class="selected-tags-section" v-if="noteForm.tags.length">
<scroll-view
class="selected-tags-scroll"
scroll-x
:show-scrollbar="false"
>
<view class="selected-tags-list">
<view
class="selected-tag-item"
v-for="(tag, index) in noteForm.tags"
:key="index"
@click="removeTag(index)"
>
<view class="tag-text">#{{ tag.name }}</view>
<view class="tag-close">×</view>
</view>
</view>
</scroll-view>
</view>
<!-- 底部占位 -->
<view class="bottom-placeholder"></view>
</view>
<!-- 底部发布按钮 -->
<view class="bottom-publish">
<button class="publish-btn" @click="publishNote">
{{ isEditMode ? "保存修改" : "发布笔记" }}
</button>
</view>
</view>
</template>
<script>
export default {
name: "PublishNote",
data() {
return {
selectedImages: [],
noteForm: {
id: "", // ID
title: "",
content: "",
tags: [], // {id, name}
images: [],
},
quickTags: [], //
imageStyles: {
height: "200rpx", //
width: "200rpx", //
},
isEditMode: false, //
};
},
computed: {
canPublish() {
return (
(this.noteForm.title.trim() || this.noteForm.content.trim()) &&
this.selectedImages &&
this.selectedImages.length > 0
);
},
},
onLoad(options) {
//
this.getTagList().then(() => {
// noteId
if (options && options.id) {
this.noteForm.id = options.id;
this.isEditMode = true;
this.loadNoteDetail();
}
});
},
methods: {
//
async getTagList() {
return new Promise(async (resolve) => {
try {
const res = await this.Post({}, "/framework/tag/list", "DES");
if (res && res.data) {
this.quickTags = res.data;
}
} catch (error) {
console.error("获取标签列表失败:", error);
//
this.quickTags = [
{ id: "1", name: "DES" },
{ id: "2", name: "AGENT" },
{ id: "3", name: "时间银行" },
{ id: "4", name: "阅读体验" },
{ id: "5", name: "时间力" },
];
}
resolve();
});
},
//
goBack() {
if (this.canPublish) {
uni.showModal({
title: "确认退出",
content: "退出后内容将不会保存,确定要退出吗?",
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
},
});
} else {
uni.navigateBack();
}
},
//
onImageSelect(e) {
console.log("选择图片·:", e);
// selectedImagesv-model
this.uploadNewImages(e.tempFiles || []);
},
//
onImageDelete(e) {
let index = e.index;
// selectedImagesv-modelnoteForm.images
// selectedImagesnoteForm.images
if (this.noteForm.images && this.noteForm.images.length > index) {
this.noteForm.images.splice(index, 1);
}
},
//
insertQuickTag(tag) {
//
const existingTag = this.noteForm.tags.find((t) => t.id == tag.id);
if (!existingTag) {
this.noteForm.tags.unshift(tag);
}
},
//
removeTag(index) {
this.noteForm.tags.splice(index, 1);
},
//
async uploadSingleImage(file) {
return new Promise((resolve, reject) => {
const token = uni.getStorageSync("userInfo")
? JSON.parse(uni.getStorageSync("userInfo")).token
: "";
uni.uploadFile({
url: this.NEWAPIURL_DES + "/system/oss/upload", //
filePath: file.url || file.path,
name: "file",
header: {
token: token || "",
// token
},
success: (res) => {
console.log(res);
try {
const data = JSON.parse(res.data);
if (data.code === 200) {
resolve(data.data.url); // URL
} else {
reject(new Error(data.message || "上传失败"));
}
} catch (e) {
reject(new Error("解析响应失败"));
}
},
fail: (err) => {
reject(err);
},
});
});
},
//
async uploadNewImages(newFiles) {
if (!newFiles || newFiles.length === 0) {
return;
}
try {
uni.showLoading({ title: "上传图片中..." });
const uploadPromises = newFiles.map((file) =>
this.uploadSingleImage(file)
);
const uploadedUrls = await Promise.all(uploadPromises);
// URLnoteForm.images
this.noteForm.images = [
...(this.noteForm.images || []),
...uploadedUrls,
];
uni.hideLoading();
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || "图片上传失败",
icon: "none",
});
}
},
//
async uploadImages() {
if (!this.selectedImages || this.selectedImages.length === 0) {
return [];
}
const uploadPromises = this.selectedImages.map((file) =>
this.uploadSingleImage(file)
);
try {
const uploadedUrls = await Promise.all(uploadPromises);
return uploadedUrls;
} catch (error) {
throw new Error("图片上传失败: " + error.message);
}
},
//
async publishNote() {
console.log(this.noteForm, "0000");
if (!this.noteForm.images || this.noteForm.images.length === 0) {
uni.showToast({
title: "请至少选择一张图片",
icon: "none",
});
return;
}
if (!this.noteForm.title.trim() && !this.noteForm.content.trim()) {
uni.showToast({
title: "请添加标题或内容",
icon: "none",
});
return;
}
try {
//
uni.showLoading({ title: this.isEditMode ? "保存中..." : "发布中..." });
//
await this.submitNote(this.noteForm);
uni.hideLoading();
uni.showToast({
title: this.isEditMode ? "修改成功" : "发布成功",
icon: "none",
duration: 2000,
});
//
setTimeout(() => {
uni.navigateBack();
}, 800);
} catch (error) {
uni.hideLoading();
uni.showToast({
title:
error.data.msg ||
(this.isEditMode ? "修改失败,请重试" : "发布失败,请重试"),
icon: "none",
});
}
},
//
async loadNoteDetail() {
try {
uni.showLoading({ title: "加载中..." });
const res = await this.Post(
{ noteId: this.noteForm.id },
"/framework/note/getInfo/" + this.noteForm.id,
"DES"
);
if (res.code === 200 && res.data) {
const noteData = res.data;
//
this.noteForm.title = noteData.title || "";
this.noteForm.content = noteData.content || "";
//
if (noteData.coverImage) {
const imageUrls = noteData.coverImage.split(",");
this.noteForm.images = imageUrls;
// uni-file-picker
this.selectedImages = imageUrls.map((url) => ({
url: url,
extname: url.split(".").pop(),
name: url.split("/").pop(),
}));
}
//
if (noteData.tagNames && noteData.tagIds) {
// tagNamestagIds
const tagNames = noteData.tagNames.split(",");
const tagIds = noteData.tagIds.split(",");
this.noteForm.tags = tagIds.map((id, index) => ({
id: id,
name: tagNames[index] || "",
}));
} else if (noteData.tags) {
// tags"3,4"ids
const tagIds = noteData.tags.split(",");
// quickTags
this.noteForm.tags = tagIds.map((id) => {
const matchedTag = this.quickTags.find((tag) => tag.id == id);
if (matchedTag) {
return {
id: id,
name: matchedTag.name,
};
} else {
console.warn(`未找到ID为${id}的标签,请确保标签列表已正确加载`);
return {
id: id,
name: `标签${id}`, //
};
}
});
//
if (
this.noteForm.tags.some(
(tag) => !this.quickTags.find((qt) => qt.id === tag.id)
)
) {
console.warn("部分标签未在标签列表中找到,可能需要刷新标签列表");
}
}
} else {
uni.showToast({
title: res.msg || "获取笔记详情失败",
icon: "none",
});
}
} catch (error) {
console.error("加载笔记详情失败:", error);
uni.showToast({
title: "获取笔记详情失败",
icon: "none",
});
} finally {
uni.hideLoading();
}
},
//
async submitNote(noteData) {
// API
const formData = {
title: noteData.title,
content: noteData.content,
tags: noteData.tags.map((tag) => tag.id).join(","), // ID
coverImage: noteData.images.join(","), // URL
method: "POST",
};
// ID
if (this.isEditMode && this.noteForm.id) {
formData.id = this.noteForm.id;
formData.method = "PUT";
return this.Post(formData, "/framework/note/editNote", "DES");
} else {
return this.Post(formData, "/framework/note/addNote", "DES");
}
},
},
};
</script>
<style lang="scss" scoped>
.publish-container {
min-height: 100vh;
background: #fff;
display: flex;
flex-direction: column;
}
//
.content-scroll {
flex: 1;
padding: 0 30rpx;
}
//
.image-section {
margin: 32rpx 0;
padding-bottom: 32rpx;
border-bottom: 1rpx solid #f0f0f0;
}
//
.title-section {
margin: 32rpx 0;
padding-bottom: 32rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-input {
width: 100%;
min-height: 60rpx;
font-size: 36rpx;
font-weight: 600;
line-height: 1.4;
color: #333;
border: none;
padding: 0;
resize: none;
}
}
//
.content-section {
margin: 32rpx 0;
padding-bottom: 32rpx;
border-bottom: 1rpx solid #f0f0f0;
.content-input {
width: 100%;
min-height: 300rpx;
font-size: 28rpx;
line-height: 1.6;
color: #333;
border: none;
padding: 0;
resize: none;
}
}
//
.quick-tags-section {
margin: 24rpx 0;
.section-title {
font-size: 28rpx;
color: #666;
margin-bottom: 25rpx;
}
.quick-tags-scroll {
width: 100%;
white-space: nowrap;
}
.quick-tags-list {
display: inline-flex;
gap: 16rpx;
padding-right: 32rpx;
width: max-content;
.quick-tag-item {
flex-shrink: 0;
padding: 16rpx 24rpx;
background: #f8f9fa;
border-radius: 32rpx;
font-size: 28rpx;
color: #666;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
&:active {
background: #e9ecef;
transform: scale(0.96);
}
}
}
}
//
.selected-tags-section {
margin: 24rpx 0;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
.selected-tags-scroll {
width: 100%;
white-space: nowrap;
}
.selected-tags-list {
display: inline-flex;
gap: 16rpx;
padding-right: 32rpx;
width: max-content;
.selected-tag-item {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 20rpx;
background: linear-gradient(135deg, #fffdb7e6 0%, #97fffab5 100%);
border-radius: 32rpx;
cursor: pointer;
white-space: nowrap;
.tag-text {
font-size: 26rpx;
color: #333;
}
.tag-close {
font-size: 24rpx;
color: #333;
opacity: 0.8;
margin-left: 8rpx;
}
}
}
}
//
.bottom-publish {
padding: 24rpx 32rpx;
background: #fff;
border-top: 1rpx solid #f0f0f0;
padding-bottom: max(24rpx, env(safe-area-inset-bottom));
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
.publish-btn {
width: 100%;
height: 88rpx;
color: #333333;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 600;
border: none;
transition: all 0.3s ease;
background: linear-gradient(135deg, #fffdb7e6 0%, #97fffab5 100%);
&:disabled {
background: linear-gradient(135deg, #ccc 0%, #ccc 100%);
}
&:not(:disabled):active {
transform: scale(0.98);
}
}
}
.bottom-placeholder {
height: 180rpx;
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
}
</style>

19
pages/stratIndex.vue

@ -1,7 +1,11 @@
<template>
<view class="bg" @click="goIndex()">
<image
style="height: 60rpx;position: absolute;top: 250rpx;z-index: 999;width: 408rpx;right: 171rpx;"
:src="showImg('/uploads/20250909/53b42f2f8deb630dc6f0e505f6be188c.png')"
></image>
<image :src="showImg(screenPng)" class="img"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/startBtn.png" mode="widthFix" class="btn"></image>
<image src="https://des.dayunyuanjian.cn/epicSoul/startBtn.png" mode="widthFix" class="btn"></image>
<MusicControl />
</view>
</template>
@ -14,7 +18,8 @@
},
data() {
return {
screenPng: ""
screenPng: "",
count:1
}
},
@ -22,13 +27,14 @@
},
onReady () {
//
this.Post({
type_id: 3,
position: 5,
}, '/api/adv/getAdv').then(res => {
if(res.data && res.data.length>0) {
this.screenPng = res.data[0].head_img
this.screenPng = '/uploads/20250827/7e760074b1081141e6ffac1351308ae8.gif'
// setTimeout(() => {
// this.goIndex()
@ -40,6 +46,7 @@
const app = getApp();
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
},
methods: {
goIndex () {
@ -52,16 +59,16 @@
onShareAppMessage() {
return {
title: 'Epic Soul 交响',
mpId: 'wx8954209bb3ad489e',
mpId: 'wx9660f8c5776663e0',
path: '/pages/stratIndex',
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/epicShare.png'
imageUrl: 'https://des.dayunyuanjian.cn/epicSoul/epicShare.png'
};
},
onShareTimeline() {
return {
title: 'Epic Soul 交响',
query: '',
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/epicShare.png'
imageUrl: 'https://des.dayunyuanjian.cn/epicSoul/epicShare.png'
};
}
// #endif

2
pig/chapter1/chapter1.vue

@ -104,7 +104,7 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="4" />
<MusicControl />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>

2
pig/chapter2/chapter2.vue

@ -121,7 +121,7 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="5" />
<MusicControl></MusicControl>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>

2
pig/chapter3/art.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter3/brave.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter3/chapter3.vue

@ -158,7 +158,7 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="6" />
<MusicControl></MusicControl>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>

2
pig/chapter3/humble.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter3/sacred.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter3/smart.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter3/stupid.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

6
pig/chapter4/chapter4.vue

@ -412,13 +412,13 @@
<template>
<image
class="bg-image"
src="https://static.ticket.sz-trip.com/epicSoul/footers.png"
src="https://des.dayunyuanjian.cn/epicSoul/footers.png"
:lazy-load="true"
mode="aspectFill"
></image>
<image
class="qrCode-image"
src="https://static.ticket.sz-trip.com/epicSoul/qrCode.png"
src="https://des.dayunyuanjian.cn/epicSoul/qrCode.png"
:lazy-load="true"
mode="widthFix"
:show-menu-by-longpress="true"
@ -430,7 +430,7 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="7" />
<MusicControl></MusicControl>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
<AudioControl audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>

2
pig/chapter4/first.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter4/fourth.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter4/secBuy.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter4/second.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/chapter4/third.vue

@ -4,7 +4,7 @@
<view @click="goBack" class="back-btn">
<image
class="back-icon"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/back.png"
mode="aspectFill"
></image>
</view>

2
pig/components/SideNav.vue

@ -8,7 +8,7 @@
<image
class="nav-icon"
:class="{ rotated: iconRotated, 'bounce-back': iconBounceBack }"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/nav-icon.png"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/nav-icon.png"
mode="aspectFill"
></image>
</view>

480
pig/home/home.vue

@ -1,24 +1,43 @@
<template>
<view>
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange"
:duration="300" :style="{ height: `100vh` }">
<BackButton/>
<swiper
class="main-swiper"
:vertical="true"
:current="currentIndex"
@change="handleSwiperChange"
:duration="300"
:style="{ height: `100vh` }"
>
<swiper-item>
<view class="page-container">
<template v-if="loadedPages[0]">
<image v-show="shouldShowContent(0)" class="bg-image" :src="
<image
v-show="shouldShowContent(0)"
class="bg-image"
:src="
showImg(
'/uploads/20250804/1830fb1be82f44d6405d35cebd70b78a.gif'
)
" :lazy-load="true" mode="aspectFill"></image>
"
:lazy-load="true"
mode="aspectFill"
></image>
<view v-show="shouldShowContent(0)" class="content-layer">
<image class="layer-img" :class="{
<image
class="layer-img"
:class="{
'blur-to-clear': animationStates[0],
hidden: !animationStates[0],
}" :src="
}"
:src="
showImg(
'/uploads/20250806/a0663a49a6037da67fa5cd897311765e.png'
)
" :lazy-load="true" mode="aspectFit">
"
:lazy-load="true"
mode="aspectFit"
>
</image>
</view>
</template>
@ -27,56 +46,98 @@
<swiper-item>
<view class="page-container" style="background-color: #efefef">
<template v-if="loadedPages[1]">
<image v-show="shouldShowContent(1)" class="bg-image" :src="
<image
v-show="shouldShowContent(1)"
class="bg-image"
:src="
showImg(
'/uploads/20250801/9bdda955f86ad07b745c3095431ca38e.png'
)
" :lazy-load="true" mode="aspectFit"></image>
"
:lazy-load="true"
mode="aspectFit"
></image>
</template>
</view>
</swiper-item>
<swiper-item>
<view class="page-container">
<template v-if="loadedPages[2]">
<image @click="nextIndex" v-show="shouldShowContent(2)" class="bg-image" :src="
<image
@click="nextIndex"
v-show="shouldShowContent(2)"
class="bg-image"
:src="
showImg(
'/uploads/20250804/5e9bdcff219f92fd6bc33444d670b807.png'
)
" :lazy-load="true" mode="aspectFill"></image>
"
:lazy-load="true"
mode="aspectFill"
></image>
</template>
</view>
</swiper-item>
<swiper-item>
<template v-if="loadedPages[3]">
<view class="loadedPages-three">
<view class="" style="width: 750rpx; height: 1400rpx; position: relative">
<image style="width: 100%; height: 100%" :src="
<view
class=""
style="width: 750rpx; height: 1400rpx; position: relative"
>
<image
style="width: 100%; height: 100%"
:src="
showImg(
'/uploads/20250802/95daa339dce3ccd25acfc1e0fa031a01.png'
)
" mode="widthFix"></image>
<view class="loadedPages-three-content"
style="position: absolute; top: 375rpx; left: 100rpx">
<image style="width: 590rpx" :src="
"
mode="widthFix"
></image>
<view
class="loadedPages-three-content"
style="position: absolute; top: 375rpx; left: 100rpx"
>
<image
style="width: 590rpx"
:src="
showImg(
'/uploads/20250804/071658650e402f55ac2cbf87cf7ce0e5.png'
)
" mode="widthFix" @click="showPopupImage(1)"></image>
<image style="width: 590rpx; margin-top: 175rpx" :src="
"
mode="widthFix"
@click="showPopupImage(1)"
></image>
<image
style="width: 590rpx; margin-top: 175rpx"
:src="
showImg(
'/uploads/20250804/418af80ee3c09def1768da53b5c8760a.png'
)
" mode="widthFix" @click="showPopupImage(2)"></image>
<image style="width: 590rpx; margin-top: 160rpx" :src="
"
mode="widthFix"
@click="showPopupImage(2)"
></image>
<image
style="width: 590rpx; margin-top: 160rpx"
:src="
showImg(
'/uploads/20250804/c82764802f8022eb14d40d222a29fcd3.png'
)
" mode="widthFix" @click="showPopupImage(3)"></image>
<image style="width: 590rpx; margin-top: 160rpx" :src="
"
mode="widthFix"
@click="showPopupImage(3)"
></image>
<image
style="width: 590rpx; margin-top: 160rpx"
:src="
showImg(
'/uploads/20250804/da9b9a28af4dcaac683d9a5af53a7667.png'
)
" mode="widthFix" @click="showPopupImage(4)"></image>
"
mode="widthFix"
@click="showPopupImage(4)"
></image>
</view>
</view>
</view>
@ -84,77 +145,122 @@
</swiper-item>
<swiper-item>
<view class="page-container">
<image style="width: 100%; height: 100%" :src="
<image
style="width: 100%; height: 100%"
:src="
showImg('/uploads/20250802/2e287bc4a678a5b47565556d23c31f4f.png')
" mode="aspectFill"></image>
<image @click="goChapter" style="
"
mode="aspectFill"
></image>
<image
@click="goChapter"
style="
width: 137rpx;
height: 108rpx;
position: absolute;
bottom: 125rpx;
left: 307rpx;
" :src="
"
:src="
showImg('/uploads/20250802/165e072a1537b0fab14c6ec2f42e7974.png')
" mode="aspectFill"></image>
"
mode="aspectFill"
></image>
</view>
</swiper-item>
<swiper-item>
<view class="page-container">
<image style="width: 100%; height: 100%" :src="
<image
style="width: 100%; height: 100%"
:src="
showImg('/uploads/20250804/d2a7c31d03d3ade96ae6a9abefb78869.png')
" mode="aspectFill"></image>
<image @click="goChapter" style="
"
mode="aspectFill"
></image>
<image
@click="goChapter"
style="
width: 137rpx;
height: 108rpx;
position: absolute;
bottom: 125rpx;
left: 307rpx;
" :src="
"
:src="
showImg('/uploads/20250802/165e072a1537b0fab14c6ec2f42e7974.png')
" mode="aspectFill"></image>
"
mode="aspectFill"
></image>
</view>
</swiper-item>
<swiper-item>
<view class="page-container">
<image style="width: 100%; height: 100%" :src="
<image
style="width: 100%; height: 100%"
:src="
showImg('/uploads/20250808/48c6045b781a8c2a2b8f87a9c79ee4f1.png')
" mode="aspectFill"></image>
<image @click="goChapter" style="
"
mode="aspectFill"
></image>
<image
@click="goChapter"
style="
width: 137rpx;
height: 108rpx;
position: absolute;
bottom: 125rpx;
left: 307rpx;
" :src="
"
:src="
showImg('/uploads/20250802/165e072a1537b0fab14c6ec2f42e7974.png')
" mode="aspectFill"></image>
"
mode="aspectFill"
></image>
</view>
</swiper-item>
<swiper-item>
<view class="page-container">
<image style="width: 100%; height: 100%" :src="
<image
style="width: 100%; height: 100%"
:src="
showImg('/uploads/20250808/9dffa7a9ed0ca7c0a7135e1a28da8011.png')
" mode="aspectFill"></image>
<image @click="goChapter" style="
"
mode="aspectFill"
></image>
<image
@click="goChapter"
style="
width: 137rpx;
height: 108rpx;
position: absolute;
bottom: 125rpx;
left: 307rpx;
" :src="
"
:src="
showImg('/uploads/20250802/165e072a1537b0fab14c6ec2f42e7974.png')
" mode="aspectFill"></image>
"
mode="aspectFill"
></image>
</view>
</swiper-item>
</swiper>
<view class="overlay" v-if="showMenu" @click="closeMenu"></view>
<view class="fixed-nav" :class="{ hidden: showMenu }" @click="showNavMenu">
<image class="nav-icon" :class="{ rotated: iconRotated, 'bounce-back': iconBounceBack }"
src="https://static.ticket.sz-trip.com/epicSoul/taozi/nav-icon.png" mode="aspectFill"></image>
<image
class="nav-icon"
:class="{ rotated: iconRotated, 'bounce-back': iconBounceBack }"
src="https://des.dayunyuanjian.cn/epicSoul/taozi/nav-icon.png"
mode="aspectFill"
></image>
</view>
<view class="nav-menu" :class="{ show: showMenu }">
<view class="nav-item" :class="{ 'item-active': isItemActive(item) }" v-for="item in menuItems"
:key="item.targetIndex" @click="() => jumpToPage(item.targetIndex)">
<view
class="nav-item"
:class="{ 'item-active': isItemActive(item) }"
v-for="item in menuItems"
:key="item.targetIndex"
@click="() => jumpToPage(item.targetIndex)"
>
<view v-if="item.text.includes('#Chapter')" class="chapter-text">
<text class="chapter-title">#Chapter</text>
<text :class="{ active: isItemActive(item) }" class="chapter-number">
@ -166,34 +272,57 @@
}}</text>
</view>
<view class="nav-item" @click="toBuy">
<text>
点击购买
</text>
<text> 点击购买 </text>
</view>
<view class="nav-item" @click="gotoPath('/subPackages/user/gwc')">
<text>购物车</text>
</view>
</view>
<!-- <BuyPeaches /> -->
<!-- <BackgroundMusic /> -->
<MusicControl />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
<AudioControl
audioSrc="https://des.dayunyuanjian.cn/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3"
/>
<!-- 图片弹窗 -->
<view class="image-popup-overlay" v-if="showImagePopup" @click="hidePopupImage">
<view
class="image-popup-overlay"
v-if="showImagePopup"
@click="hidePopupImage"
>
<view class="image-popup-container" @click.stop>
<image class="popup-image" v-if="currentTagImg == 1" :src="
<image
class="popup-image"
v-if="currentTagImg == 1"
:src="
showImg('/uploads/20250802/a2245bbeba155410fd765439a18774f7.png')
" mode="widthFix"></image>
<image class="popup-image" v-if="currentTagImg == 2" :src="
"
mode="widthFix"
></image>
<image
class="popup-image"
v-if="currentTagImg == 2"
:src="
showImg('/uploads/20250802/6a740ec7fc10dd0f6ddcc65faca3b3c8.png')
" mode="widthFix"></image>
<image class="popup-image" v-if="currentTagImg == 3" :src="
"
mode="widthFix"
></image>
<image
class="popup-image"
v-if="currentTagImg == 3"
:src="
showImg('/uploads/20250802/4e89bbb52de311cfae7ef1b59ee2f2c8.png')
" mode="widthFix"></image>
<image v-if="currentTagImg == 4" class="popup-image" :src="
"
mode="widthFix"
></image>
<image
v-if="currentTagImg == 4"
class="popup-image"
:src="
showImg('/uploads/20250802/76b84f3dd91bfebaec35aadc086410b8.png')
" mode="widthFix"></image>
"
mode="widthFix"
></image>
<view class="close-btn" @click="hidePopupImage">×</view>
</view>
</view>
@ -201,15 +330,15 @@
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import BuyPeaches from "@/components/BuyPeaches.vue";
import messagePop from "@/components/messagePop.vue";
// import BackgroundMusic from '@/components/BackgroundMusic.vue';
import messageBoard from "@/components/messageBoard.vue";
import MusicControl from "@/components/MusicControl.vue";
import TitleHeader from "@/components/TitleHeader.vue";
export default {
import AudioControl from "@/components/AudioControl.vue";
import BuyPeaches from "@/components/BuyPeaches.vue";
import messagePop from "@/components/messagePop.vue";
// import BackgroundMusic from '@/components/BackgroundMusic.vue';
import messageBoard from "@/components/messageBoard.vue";
import MusicControl from "@/components/MusicControl.vue";
import TitleHeader from "@/components/TitleHeader.vue";
import BackButton from "@/components/BackButton.vue";
export default {
components: {
BuyPeaches,
messagePop,
@ -218,6 +347,7 @@
messageBoard,
MusicControl,
TitleHeader,
BackButton
},
data() {
return {
@ -254,7 +384,8 @@
iconRotated: false,
iconBounceBack: false,
showImagePopup: false,
menuItems: [{
menuItems: [
{
text: "序章",
targetIndex: 0,
},
@ -298,8 +429,9 @@
watch: {
currentIndex(newIndex) {
for (
let i = Math.max(0, newIndex - this.preloadBuffer); i <= Math.min(10, newIndex + this
.preloadBuffer); i++
let i = Math.max(0, newIndex - this.preloadBuffer);
i <= Math.min(10, newIndex + this.preloadBuffer);
i++
) {
this.loadedPages[i] = true;
}
@ -337,8 +469,8 @@
},
toBuy() {
uni.navigateTo({
url: '/subPackages/techan/detail?id=38'
})
url: "/subPackages/techan/detail?id=38",
});
},
closeMenu() {
this.showMenu = false;
@ -373,7 +505,7 @@
mounted() {
this.titleHeight = uni.getStorageSync("titleHeight");
const app = getApp();
app.updateMusicSrc("https://static.ticket.sz-trip.com/epicSoul/bgm.mp3");
app.updateMusicSrc("https://des.dayunyuanjian.cn/epicSoul/bgm.mp3");
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
for (let i = 0; i <= Math.min(1 + this.preloadBuffer, 6); i++) {
@ -394,8 +526,9 @@
const targetIndex = parseInt(options.targetIndex);
this.currentIndex = targetIndex;
for (
let i = Math.max(0, targetIndex - this.preloadBuffer); i <= Math.min(10, targetIndex + this
.preloadBuffer); i++
let i = Math.max(0, targetIndex - this.preloadBuffer);
i <= Math.min(10, targetIndex + this.preloadBuffer);
i++
) {
this.loadedPages[i] = true;
}
@ -409,34 +542,36 @@
onShareAppMessage() {
return {
title: "猪的美学进化史|「Epic Soul」阅读体 issue04",
mpId: "wx8954209bb3ad489e",
mpId: "wx9660f8c5776663e0",
path: "/pig/home/home",
imageUrl: "https://epic.js-dyyj.com/uploads/20250805/4a90a8f7de19b0c465ff30e1b8e0c35c.png",
imageUrl:
"https://epic.js-dyyj.com/uploads/20250805/4a90a8f7de19b0c465ff30e1b8e0c35c.png",
};
},
onShareTimeline() {
return {
title: "猪的美学进化史|「Epic Soul」阅读体 issue04",
path: "/pig/home/home",
imageUrl: "https://epic.js-dyyj.com/uploads/20250805/4a90a8f7de19b0c465ff30e1b8e0c35c.png",
imageUrl:
"https://epic.js-dyyj.com/uploads/20250805/4a90a8f7de19b0c465ff30e1b8e0c35c.png",
};
},
// #endif
};
};
</script>
<style lang="scss" scoped>
@font-face {
@font-face {
font-family: "SourceHanSerif-Regular";
src: url(/static/fonts/SourceHanSerifSC-Regular.otf);
}
}
.main-swiper {
.main-swiper {
width: 100%;
height: 100vh;
}
}
.page-container {
.page-container {
display: flex;
flex-direction: column;
align-items: center;
@ -444,9 +579,9 @@
height: 100%;
position: relative;
overflow: hidden;
}
}
.loadedPages-three {
.loadedPages-three {
height: 100%;
position: relative;
background: #efefef;
@ -454,9 +589,9 @@
align-items: center;
flex-direction: column;
justify-content: center;
}
}
.loadedPages-three-content {
.loadedPages-three-content {
height: 100%;
.loadedPages-three-title {
@ -541,18 +676,18 @@
color: #4b5563;
}
}
}
}
.bg-image {
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
}
.content-layer {
.content-layer {
position: relative;
z-index: 2;
width: 100%;
@ -560,33 +695,33 @@
display: flex;
align-items: center;
flex-direction: column;
}
}
.content-layer2 {
.content-layer2 {
z-index: 2;
position: absolute;
bottom: 5%;
right: 5%;
}
}
.layer-img {
.layer-img {
width: 650rpx;
height: 100%;
}
}
.arrow-down {
.arrow-down {
width: 100rpx;
height: 40rpx;
animation: bounce 1.5s infinite;
}
}
.layer-icon {
.layer-icon {
width: 100rpx;
height: 100rpx;
animation: bounce 1.5s infinite;
}
}
.overlay {
.overlay {
position: fixed;
top: 0;
left: 0;
@ -594,9 +729,9 @@
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
z-index: 10;
}
}
.fixed-nav {
.fixed-nav {
width: 80rpx;
height: 80rpx;
background-color: rgb(0 0 0 / 0.7);
@ -611,29 +746,29 @@
justify-content: center;
z-index: 9;
transition: transform 0.3s ease, opacity 0.3s ease;
}
}
.fixed-nav.hidden {
.fixed-nav.hidden {
transform: translateX(100%);
opacity: 0;
pointer-events: none;
}
}
.nav-icon {
.nav-icon {
width: 35rpx;
height: 35rpx;
transition: transform 0.3s ease;
}
}
.nav-icon.rotated {
.nav-icon.rotated {
transform: rotate(180deg);
}
}
.nav-icon.bounce-back {
.nav-icon.bounce-back {
animation: bounceRotation 0.5s ease;
}
}
@keyframes bounceRotation {
@keyframes bounceRotation {
0% {
transform: rotate(180deg);
}
@ -649,9 +784,9 @@
100% {
transform: rotate(0deg);
}
}
}
.nav-menu {
.nav-menu {
position: fixed;
top: 50%;
right: 0;
@ -661,13 +796,13 @@
border-radius: 16rpx 0 0 16rpx;
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
}
.nav-menu.show {
.nav-menu.show {
transform: translate(0, -50%);
}
}
.nav-item {
.nav-item {
padding: 20rpx;
text-align: center;
@ -676,44 +811,43 @@
opacity: 0.7;
font-size: 28rpx;
}
}
}
.item-active {
.item-active {
background-color: rgba(0, 0, 0, 0.1);
}
}
.nav-item .active {
.nav-item .active {
color: #333;
opacity: 1;
}
}
.chapter-text {
.chapter-text {
display: flex;
flex-direction: column;
align-items: center;
line-height: 1.3;
}
}
.chapter-title {
.chapter-title {
color: #333;
opacity: 0.7;
font-size: 24rpx;
}
}
.chapter-number {
.chapter-number {
color: #333;
opacity: 0.7;
font-size: 28rpx;
margin-top: 8rpx;
}
}
.item-active .chapter-title,
.item-active .chapter-number.active {
.item-active .chapter-title,
.item-active .chapter-number.active {
opacity: 1;
}
@keyframes bounce {
}
@keyframes bounce {
0%,
20%,
50%,
@ -729,13 +863,13 @@
60% {
transform: translateY(-10rpx);
}
}
}
.blur-to-clear {
.blur-to-clear {
animation: blurToClear 1.5s ease-out forwards;
}
}
@keyframes blurToClear {
@keyframes blurToClear {
0% {
filter: blur(10px);
opacity: 0.3;
@ -745,17 +879,17 @@
filter: blur(0);
opacity: 1;
}
}
}
.hidden {
.hidden {
opacity: 0;
}
}
.bounce-in {
.bounce-in {
animation: bounceIn 1s ease forwards;
}
}
@keyframes bounceIn {
@keyframes bounceIn {
0% {
opacity: 0;
transform: scale(0.3) translateY(100px);
@ -774,13 +908,13 @@
opacity: 1;
transform: scale(1) translateY(0);
}
}
}
.fade-slide-up {
.fade-slide-up {
animation: fadeSlideUp 1s ease-out forwards;
}
}
@keyframes fadeSlideUp {
@keyframes fadeSlideUp {
0% {
opacity: 0;
transform: translateY(30px);
@ -790,9 +924,9 @@
opacity: 1;
transform: translateY(0);
}
}
}
.chapterCover-btn {
.chapterCover-btn {
position: absolute;
left: 50%;
bottom: 10%;
@ -800,31 +934,31 @@
width: 300rpx;
height: 100rpx;
z-index: 2;
}
}
.qrcode-txt {
.qrcode-txt {
width: 30vw;
z-index: 2;
position: fixed;
left: 0;
right: 0;
margin: 100rpx auto 0;
}
}
.qrcode-txts {
.qrcode-txts {
width: 28vw;
z-index: 2;
position: fixed;
left: 0;
right: 0;
margin: 335rpx auto 0;
}
}
.message-board {
.message-board {
width: 100%;
}
}
.qrCode-image {
.qrCode-image {
position: absolute;
left: 0;
right: 0;
@ -832,9 +966,9 @@
margin: 0 auto;
z-index: 2;
width: 30vw;
}
}
.image-popup-overlay {
.image-popup-overlay {
position: fixed;
top: 0;
left: 0;
@ -845,22 +979,22 @@
display: flex;
align-items: center;
justify-content: center;
}
}
.image-popup-container {
.image-popup-container {
position: relative;
max-width: 90vw;
max-height: 90vh;
}
}
.popup-image {
.popup-image {
width: 750rpx;
height: 654rpx;
max-width: 100%;
max-height: 100%;
}
}
.close-btn {
.close-btn {
position: absolute;
top: -20rpx;
right: -20rpx;
@ -874,5 +1008,5 @@
font-size: 40rpx;
color: #333;
cursor: pointer;
}
}
</style>

2
project.config.json

@ -20,6 +20,6 @@
"ignore": [],
"include": []
},
"appid": "wx8954209bb3ad489e",
"appid": "wx9660f8c5776663e0",
"editorSetting": {}
}

22
static/css/icon.scss

@ -0,0 +1,22 @@
@font-face {
font-family: "des"; /* Project id 5022607 */
src: url('//at.alicdn.com/t/c/font_5022607_r74iwur9ql.woff2?t=1757989865177') format('woff2'),
url('//at.alicdn.com/t/c/font_5022607_r74iwur9ql.woff?t=1757989865177') format('woff'),
url('//at.alicdn.com/t/c/font_5022607_r74iwur9ql.ttf?t=1757989865177') format('truetype');
}
.des {
font-family: "des" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconfont-jifen:before {
content: "\e614";
}
.iconfont-jifenduihuan:before {
content: "\e60b";
}

3
static/image/empty-cart.png

@ -0,0 +1,3 @@
# 这是一个空购物车图标的占位文件
# 在实际项目中,请替换为真实的空购物车图标图片文件
# 建议使用简洁的购物车图标,颜色为灰色或浅色

3
static/image/heart.png

@ -0,0 +1,3 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.6 6.6C30.8 6.6 27.4 8.4 24 11.8C20.6 8.4 17.2 6.6 13.4 6.6C6.8 6.6 1.4 12 1.4 18.6C1.4 26.8 8.6 33.4 22.2 45.8L24 47.4L25.8 45.8C39.4 33.4 46.6 26.8 46.6 18.6C46.6 12 41.2 6.6 34.6 6.6Z" fill="#CCCCCC"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

2
static/image/search.png

@ -0,0 +1,2 @@
# 这是一个搜索图标的占位文件
# 在实际项目中,请替换为真实的搜索图标图片文件

BIN
static/image/tabbar/agent.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
static/image/tabbar/agent_select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save