feat(Webview): 添加Webview页面及相关测试功能
refactor(BottomNav): 替换图标为SVG并添加样式过滤 style(Activities): 更新页面标题和导航栏样式 docs: 添加文件描述注释和最后编辑信息
Showing
12 changed files
with
615 additions
and
113 deletions
| ... | @@ -11,10 +11,8 @@ declare module 'vue' { | ... | @@ -11,10 +11,8 @@ declare module 'vue' { |
| 11 | BottomNav: typeof import('./src/components/BottomNav.vue')['default'] | 11 | BottomNav: typeof import('./src/components/BottomNav.vue')['default'] |
| 12 | GlassCard: typeof import('./src/components/GlassCard.vue')['default'] | 12 | GlassCard: typeof import('./src/components/GlassCard.vue')['default'] |
| 13 | NavBar: typeof import('./src/components/navBar.vue')['default'] | 13 | NavBar: typeof import('./src/components/navBar.vue')['default'] |
| 14 | - NutImage: typeof import('@nutui/nutui-taro')['Image'] | 14 | + NutButton: typeof import('@nutui/nutui-taro')['Button'] |
| 15 | NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview'] | 15 | NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview'] |
| 16 | - NutInput: typeof import('@nutui/nutui-taro')['Input'] | ||
| 17 | - NutToast: typeof import('@nutui/nutui-taro')['Toast'] | ||
| 18 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] | 16 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] |
| 19 | PointsCollector: typeof import('./src/components/PointsCollector.vue')['default'] | 17 | PointsCollector: typeof import('./src/components/PointsCollector.vue')['default'] |
| 20 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] | 18 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] | ... | ... |
src/assets/images/icon/activities.svg
0 → 100644
| 1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756303912062" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21734" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m0 73.142857C323.486476 170.666667 170.666667 323.486476 170.666667 512s152.81981 341.333333 341.333333 341.333333 341.333333-152.81981 341.333333-341.333333S700.513524 170.666667 512 170.666667z m190.854095 160.572952l-86.186666 275.334095L341.333333 692.760381l86.211048-275.334095 275.309714-86.186667z m-216.941714 144.579048l-33.01181 105.374476 105.398858-32.987429-72.411429-72.411428z" p-id="21735"></path></svg> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/assets/images/icon/home.svg
0 → 100644
| 1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756302772088" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8533" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M549.61981 133.022476l319.683047 203.605334A70.851048 70.851048 0 0 1 902.095238 396.361143v434.883047A70.89981 70.89981 0 0 1 831.146667 902.095238h-282.819048l0.024381-218.112h-71.826286v218.087619L192.853333 902.095238A70.89981 70.89981 0 0 1 121.904762 831.24419V390.241524c0-24.527238 12.678095-47.299048 33.54819-60.220953l318.659048-197.485714a70.972952 70.972952 0 0 1 75.50781 0.487619zM828.952381 828.952381V397.214476L511.488 195.047619 195.047619 391.119238V828.952381h211.309714v-216.551619h212.187429v216.527238L828.952381 828.952381z" p-id="8534"></path></svg> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/assets/images/icon/me.svg
0 → 100644
| 1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756304253215" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16498" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M853.333333 896a42.666667 42.666667 0 0 1-42.666666-42.666667 298.666667 298.666667 0 0 0-6.186667-60.16 289.066667 289.066667 0 0 0-17.28-56.106666 292.266667 292.266667 0 0 0-27.52-50.773334 313.386667 313.386667 0 0 0-36.48-44.16 301.653333 301.653333 0 0 0-44.16-36.48 42.666667 42.666667 0 1 1 47.573333-70.613333 375.253333 375.253333 0 0 1 56.96 46.72 395.52 395.52 0 0 1 46.72 56.746667A379.733333 379.733333 0 0 1 865.706667 704 386.773333 386.773333 0 0 1 896 853.333333a42.666667 42.666667 0 0 1-42.666667 42.666667zM741.973333 272.64a228.48 228.48 0 0 0-13.866666-43.946667 225.28 225.28 0 0 0-21.333334-39.893333 236.16 236.16 0 0 0-28.586666-34.773333 234.666667 234.666667 0 0 0-34.773334-28.586667 234.666667 234.666667 0 0 0-178.346666-35.413333 240.213333 240.213333 0 0 0-84.053334 35.413333 234.666667 234.666667 0 0 0-34.773333 28.586667 236.16 236.16 0 0 0-28.586667 34.773333 241.28 241.28 0 0 0-21.333333 39.893333 243.84 243.84 0 0 0-13.653333 43.946667 241.28 241.28 0 0 0 0 94.72 228.48 228.48 0 0 0 13.866666 43.946667 225.28 225.28 0 0 0 21.333334 39.893333 42.666667 42.666667 0 0 0 70.826666-47.786667 131.84 131.84 0 0 1-13.866666-25.173333 149.333333 149.333333 0 0 1-8.746667-28.16 152.96 152.96 0 0 1 0-60.16 141.653333 141.653333 0 0 1 8.746667-27.946667 138.24 138.24 0 0 1 13.653333-25.386666 166.186667 166.186667 0 0 1 18.346667-21.333334 151.253333 151.253333 0 0 1 21.333333-18.346666 138.24 138.24 0 0 1 25.386667-13.653334 141.653333 141.653333 0 0 1 27.946666-8.746666 149.333333 149.333333 0 0 1 60.16 0 141.653333 141.653333 0 0 1 27.946667 8.746666 138.24 138.24 0 0 1 25.386667 13.653334 147.84 147.84 0 0 1 40.32 40.533333 131.84 131.84 0 0 1 13.866666 25.173333 149.333333 149.333333 0 0 1 8.746667 28.16 152.96 152.96 0 0 1 0 60.16 141.653333 141.653333 0 0 1-8.746667 27.946667 138.24 138.24 0 0 1-13.653333 25.386667A166.186667 166.186667 0 0 1 618.666667 426.666667a151.253333 151.253333 0 0 1-21.333334 18.346666 138.24 138.24 0 0 1-25.386666 13.653334 141.653333 141.653333 0 0 1-27.946667 8.746666A156.373333 156.373333 0 0 1 512 469.333333a386.773333 386.773333 0 0 0-149.333333 30.293334 384 384 0 0 0-64 35.413333 375.253333 375.253333 0 0 0-56.96 46.72 395.52 395.52 0 0 0-46.72 56.746667A379.733333 379.733333 0 0 0 158.293333 704 386.773333 386.773333 0 0 0 128 853.333333a42.666667 42.666667 0 0 0 85.333333 0 298.666667 298.666667 0 0 1 6.186667-60.16 289.066667 289.066667 0 0 1 17.28-56.106666 292.266667 292.266667 0 0 1 27.52-50.773334 313.386667 313.386667 0 0 1 36.48-44.16 301.653333 301.653333 0 0 1 44.16-36.48 292.266667 292.266667 0 0 1 50.773333-27.52 305.28 305.28 0 0 1 56.106667-17.493333A314.453333 314.453333 0 0 1 512 554.666667a248.746667 248.746667 0 0 0 47.146667-4.693334 240.213333 240.213333 0 0 0 84.053333-35.413333 234.666667 234.666667 0 0 0 34.773333-28.586667 236.16 236.16 0 0 0 28.586667-34.773333 241.28 241.28 0 0 0 21.333333-39.893333 243.84 243.84 0 0 0 13.653334-43.946667 241.28 241.28 0 0 0 0-94.72z" fill="#333333" p-id="16499"></path></svg> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/assets/images/icon/rewards.svg
0 → 100644
| 1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756303617166" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8875" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M584.655238 97.52381l7.753143 0.121904a127.024762 127.024762 0 0 1 109.592381 181.052953h152.064a73.142857 73.142857 0 0 1 73.142857 73.142857v86.625524a73.142857 73.142857 0 0 1-73.142857 73.142857h-4.510476v340.967619a73.142857 73.142857 0 0 1-73.142857 73.142857H223.963429a73.142857 73.142857 0 0 1-73.142858-73.142857l-0.024381-340.967619H146.285714a73.142857 73.142857 0 0 1-73.142857-73.142857v-86.625524a73.142857 73.142857 0 0 1 73.142857-73.142857h139.897905a127.024762 127.024762 0 0 1 109.568-181.077334L403.504762 97.52381a128.975238 128.975238 0 0 1 90.599619 37.010285A128.926476 128.926476 0 0 1 584.655238 97.52381zM223.963429 852.577524l237.397333-0.024381V511.609905H223.914667v340.967619z m552.472381-340.967619h-237.421715v340.943238h237.446095l-0.02438-340.943238zM461.336381 351.817143H146.285714v86.649905h4.486096v-2.218667l310.564571-0.024381V351.817143z m392.728381 0H538.989714v84.406857l310.588953 0.024381-0.024381 2.218667h4.510476v-86.625524zM403.504762 170.666667h-2.364952a53.881905 53.881905 0 0 0 0 107.763809h54.125714V226.937905c0-6.339048 0.463238-12.55619 1.340952-18.627048A56.246857 56.246857 0 0 0 403.504762 170.666667z m183.539809 0h-2.364952a56.295619 56.295619 0 0 0-53.126095 37.668571c0.902095 6.046476 1.365333 12.263619 1.365333 18.602667l-0.024381 51.492571h54.150095a53.881905 53.881905 0 0 0 53.76-50.029714l0.121905-3.852191c0-29.744762-24.137143-53.881905-53.881905-53.881904z" p-id="8876"></path></svg> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-08-27 17:44:10 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-08-27 23:10:28 | ||
| 5 | + * @FilePath: /lls_program/src/components/BottomNav.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 1 | <template> | 8 | <template> |
| 2 | <view class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 flex justify-around py-2 z-50"> | 9 | <view class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 flex justify-around py-2 z-50"> |
| 3 | <view | 10 | <view |
| ... | @@ -6,9 +13,9 @@ | ... | @@ -6,9 +13,9 @@ |
| 6 | @click="navigate(item.path)" | 13 | @click="navigate(item.path)" |
| 7 | :class="['flex flex-col items-center py-2 px-5', isActive(item.path) ? 'text-blue-600' : 'text-gray-500']" | 14 | :class="['flex flex-col items-center py-2 px-5', isActive(item.path) ? 'text-blue-600' : 'text-gray-500']" |
| 8 | > | 15 | > |
| 9 | - <component :is="item.icon" size="24" /> | 16 | + <image :src="item.icon" :class="['w-6 h-6', isActive(item.path) ? 'filter-blue' : 'filter-gray']" /> |
| 10 | <span class="text-xs mt-1">{{ item.label }}</span> | 17 | <span class="text-xs mt-1">{{ item.label }}</span> |
| 11 | - <view v-if="isActive(item.path)" class="absolute bottom-0 w-10 h-1 bg-blue-600 rounded-t-full"></view> | 18 | + <!-- <view v-if="isActive(item.path)" class="absolute bottom-0 w-10 h-1 bg-blue-600 rounded-t-full"></view> --> |
| 12 | </view> | 19 | </view> |
| 13 | </view> | 20 | </view> |
| 14 | </template> | 21 | </template> |
| ... | @@ -16,13 +23,16 @@ | ... | @@ -16,13 +23,16 @@ |
| 16 | <script setup> | 23 | <script setup> |
| 17 | import { computed, shallowRef } from 'vue'; | 24 | import { computed, shallowRef } from 'vue'; |
| 18 | import Taro from '@tarojs/taro'; | 25 | import Taro from '@tarojs/taro'; |
| 19 | -import { Home, Find, Shop, My } from '@nutui/icons-vue-taro'; | 26 | +import homeIcon from '@/assets/images/icon/home.svg'; |
| 27 | +import rewardsIcon from '@/assets/images/icon/rewards.svg'; | ||
| 28 | +import activitiesIcon from '@/assets/images/icon/activities.svg'; | ||
| 29 | +import meIcon from '@/assets/images/icon/me.svg'; | ||
| 20 | 30 | ||
| 21 | const navItems = shallowRef([ | 31 | const navItems = shallowRef([ |
| 22 | - { path: '/pages/Dashboard/index', icon: Home, label: '首页' }, | 32 | + { path: '/pages/Dashboard/index', icon: homeIcon, label: '首页' }, |
| 23 | - { path: '/pages/Activities/index', icon: Find, label: '活动' }, | 33 | + { path: '/pages/Activities/index', icon: activitiesIcon, label: '活动' }, |
| 24 | - { path: '/pages/Rewards/index', icon: Shop, label: '兑换' }, | 34 | + { path: '/pages/Rewards/index', icon: rewardsIcon, label: '兑换' }, |
| 25 | - { path: '/pages/Profile/index', icon: My, label: '我的' }, | 35 | + { path: '/pages/Profile/index', icon: meIcon, label: '我的' }, |
| 26 | ]); | 36 | ]); |
| 27 | 37 | ||
| 28 | const currentPage = computed(() => { | 38 | const currentPage = computed(() => { |
| ... | @@ -38,3 +48,13 @@ const navigate = (path) => { | ... | @@ -38,3 +48,13 @@ const navigate = (path) => { |
| 38 | Taro.reLaunch({ url: path }); | 48 | Taro.reLaunch({ url: path }); |
| 39 | }; | 49 | }; |
| 40 | </script> | 50 | </script> |
| 51 | + | ||
| 52 | +<style> | ||
| 53 | +.filter-blue { | ||
| 54 | + filter: brightness(0) saturate(100%) invert(22%) sepia(100%) saturate(3441%) hue-rotate(229deg) brightness(101%) contrast(101%); | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +.filter-gray { | ||
| 58 | + filter: brightness(0) saturate(100%) invert(91%) sepia(6%) saturate(394%) hue-rotate(201deg) brightness(50%) contrast(92%); | ||
| 59 | +} | ||
| 60 | +</style> | ... | ... |
| 1 | +/* | ||
| 2 | + * @Date: 2025-08-27 18:25:06 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-08-27 21:00:06 | ||
| 5 | + * @FilePath: /lls_program/src/pages/Activities/index.config.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 1 | export default { | 8 | export default { |
| 2 | - navigationBarTitleText: '首页' | 9 | + navigationBarTitleText: '活动' |
| 3 | } | 10 | } | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | - <view class="min-h-screen flex flex-col bg-white"> | 2 | + <view class="activities-container"> |
| 3 | - <AppHeader title="城市漫步" :showBack="false" /> | 3 | + <!-- WebView内容 --> |
| 4 | - <!-- Map container --> | 4 | + <web-view |
| 5 | - <view class="flex-1 relative"> | 5 | + v-if="webUrl" |
| 6 | - <Map | 6 | + :src="webUrl" |
| 7 | - class="w-full h-full" | 7 | + class="web-view" |
| 8 | - :longitude="121.47" | 8 | + @message="handleMessage" |
| 9 | - :latitude="31.23" | 9 | + @load="handleLoad" |
| 10 | - :scale="15" | 10 | + @error="handleError" |
| 11 | - :markers="markers" | 11 | + ></web-view> |
| 12 | - @markertap="onMarkerTap" | 12 | + |
| 13 | - /> | 13 | + <!-- 加载状态 --> |
| 14 | - </view> | 14 | + <view v-if="loading" class="loading-container"> |
| 15 | - <!-- Check-in point detail modal --> | 15 | + <view class="loading-spinner">⏳</view> |
| 16 | - <view v-if="showModal && selectedPoint" class="fixed inset-0 bg-black bg-opacity-50 flex items-end justify-center z-20"> | 16 | + <view class="loading-text">加载中...</view> |
| 17 | - <view class="bg-white rounded-t-2xl w-full max-h-[80%] overflow-y-auto"> | ||
| 18 | - <view class="relative h-64 overflow-hidden"> | ||
| 19 | - <image :src="selectedPoint.image" :alt="selectedPoint.name" class="w-full h-full object-cover" /> | ||
| 20 | - <button class="absolute top-4 right-4 bg-white rounded-full p-2" @click="showModal = false"> | ||
| 21 | - <Failure size="24" /> | ||
| 22 | - </button> | ||
| 23 | </view> | 17 | </view> |
| 24 | - <view class="p-4"> | 18 | + |
| 25 | - <h2 class="text-2xl font-bold mb-2">{{ selectedPoint.name }}</h2> | 19 | + <!---but态 --> |
| 26 | - <p class="text-gray-600 mb-6">{{ selectedPoint.description }}</p> | 20 | + <view v-if="error" class="error-container"> |
| 27 | - <button @click="handleCheckIn" :disabled="selectedPoint.isCheckedIn" :class="['w-full py-3 rounded-full text-white font-medium', selectedPoint.isCheckedIn ? 'bg-gray-400' : 'bg-blue-500']"> | 21 | + <view class="error-icon">⚠️</view> |
| 28 | - {{ selectedPoint.isCheckedIn ? '已打卡' : '打卡' }} | 22 | + <view class="error-text">页面加载失败</view> |
| 29 | - </button> | 23 | + <nut-button type="primary" size="small" @click="handleRetry">重试</nut-button> |
| 30 | </view> | 24 | </view> |
| 31 | - </view> | 25 | + |
| 32 | - </view> | 26 | + <!-- 底部导航 --> |
| 33 | - <!-- Check-in success message --> | 27 | + <BottomNav /> |
| 34 | - <view v-if="showSuccess" class="fixed inset-0 flex items-center justify-center z-30 pointer-events-none"> | ||
| 35 | - <view class="bg-black bg-opacity-70 text-white px-6 py-4 rounded-lg flex items-center"> | ||
| 36 | - <Check size="24" class="text-green-400 mr-2" /> | ||
| 37 | - <span>打卡成功!</span> | ||
| 38 | - </view> | ||
| 39 | </view> | 28 | </view> |
| 40 | - <BottomNav /> | ||
| 41 | - </view> | ||
| 42 | </template> | 29 | </template> |
| 43 | 30 | ||
| 44 | <script setup> | 31 | <script setup> |
| 45 | -import { ref, computed } from 'vue'; | 32 | +import { ref, onMounted } from 'vue' |
| 46 | -import AppHeader from '../../components/AppHeader.vue'; | 33 | +import Taro from '@tarojs/taro' |
| 47 | -import BottomNav from '../../components/BottomNav.vue'; | 34 | +import BottomNav from '../../components/BottomNav.vue' |
| 48 | -import { Failure, Check } from '@nutui/icons-vue-taro'; | 35 | + |
| 49 | - | 36 | +/** |
| 50 | -const selectedPoint = ref(null); | 37 | + * 活动页面WebView组件 |
| 51 | -const showModal = ref(false); | 38 | + * 用于显示活动相关的外部网页内容 |
| 52 | -const showSuccess = ref(false); | 39 | + */ |
| 53 | - | 40 | + |
| 54 | -const checkInPoints = ref([ | 41 | +// 页面状态 |
| 55 | - { | 42 | +const webUrl = ref('https://oa.onwall.cn/f/map/#/checkin/?id=2400107') // TODO: 临时设置一个URL |
| 56 | - id: 'yuyuan', | 43 | +const loading = ref(true) |
| 57 | - name: '豫园', | 44 | +const error = ref(false) |
| 58 | - description: '豫园始建于1559年,是明代著名的私家园林,现为国家重点文物保护单位。园内亭台楼阁布局精巧,景色优美,是上海著名的旅游景点。', | 45 | + |
| 59 | - latitude: 31.2271, | 46 | +/** |
| 60 | - longitude: 121.4875, | 47 | + * 处理WebView加载完成 |
| 61 | - iconPath: '/static/images/activities/marker.png', | 48 | + */ |
| 62 | - width: 30, | 49 | +const handleLoad = (e) => { |
| 63 | - height: 30, | 50 | + console.log('WebView加载完成:', e) |
| 64 | - image: "/static/images/activities/citywalk2_1.png", | 51 | + loading.value = false |
| 65 | - isCheckedIn: false | 52 | + error.value = false |
| 66 | - }, | 53 | +} |
| 67 | - { | 54 | + |
| 68 | - id: 'jiuquqiao', | 55 | +/** |
| 69 | - name: '九曲桥', | 56 | + * 处理WebView加载错误 |
| 70 | - description: '豫园九曲桥位于上海城隍庙豫园内,是上海的标志性建筑之一。桥因始建于明代嘉靖、万历年间,九曲桥与荷花池在那时就已存在,最初为木桥。清朝隆年间进行过修建,上世纪20年代因火灾改建为水泥桥,解放后又恢复了石桥形式。', | 57 | + */ |
| 71 | - latitude: 31.2275, | 58 | +const handleError = (e) => { |
| 72 | - longitude: 121.4895, | 59 | + console.error('WebView加载错误:', e) |
| 73 | - iconPath: '/static/images/activities/marker.png', | 60 | + loading.value = false |
| 74 | - width: 30, | 61 | + error.value = true |
| 75 | - height: 30, | 62 | + |
| 76 | - image: "/static/images/activities/citywalk2_1.png", | 63 | + Taro.showToast({ |
| 77 | - isCheckedIn: false | 64 | + title: '页面加载失败', |
| 78 | - } | 65 | + icon: 'none' |
| 79 | -]); | 66 | + }) |
| 80 | - | 67 | +} |
| 81 | -const markers = computed(() => checkInPoints.value.map(p => ({ | 68 | + |
| 82 | - id: p.id, | 69 | +/** |
| 83 | - latitude: p.latitude, | 70 | + * 处理WebView消息 |
| 84 | - longitude: p.longitude, | 71 | + */ |
| 85 | - iconPath: p.iconPath, | 72 | +const handleMessage = (e) => { |
| 86 | - width: p.width, | 73 | + console.log('WebView消息:', e) |
| 87 | - height: p.height, | 74 | + // 可以在这里处理来自WebView的消息 |
| 88 | -}))); | 75 | +} |
| 89 | - | 76 | + |
| 90 | -const onMarkerTap = (e) => { | 77 | +/** |
| 91 | - const point = checkInPoints.value.find(p => p.id === e.detail.markerId); | 78 | + * 重试加载 |
| 92 | - if (point) { | 79 | + */ |
| 93 | - selectedPoint.value = point; | 80 | +const handleRetry = () => { |
| 94 | - showModal.value = true; | 81 | + loading.value = true |
| 95 | - } | 82 | + error.value = false |
| 96 | -}; | 83 | + // 重新设置URL触发重新加载 |
| 97 | - | 84 | + const currentUrl = webUrl.value |
| 98 | -const handleCheckIn = () => { | 85 | + webUrl.value = '' |
| 99 | - if (selectedPoint.value) { | ||
| 100 | - const index = checkInPoints.value.findIndex(p => p.id === selectedPoint.value.id); | ||
| 101 | - if (index !== -1) { | ||
| 102 | - checkInPoints.value[index].isCheckedIn = true; | ||
| 103 | - } | ||
| 104 | - showModal.value = false; | ||
| 105 | - showSuccess.value = true; | ||
| 106 | setTimeout(() => { | 86 | setTimeout(() => { |
| 107 | - showSuccess.value = false; | 87 | + webUrl.value = currentUrl |
| 108 | - }, 2000); | 88 | + }, 100) |
| 109 | - } | 89 | +} |
| 110 | -}; | 90 | + |
| 91 | +// 页面挂载时初始化 | ||
| 92 | +onMounted(() => { | ||
| 93 | + // 这里可以根据需要动态设置URL | ||
| 94 | + console.log('活动页面WebView初始化') | ||
| 95 | +}) | ||
| 111 | </script> | 96 | </script> |
| 97 | + | ||
| 98 | +<style lang="less"> | ||
| 99 | +.activities-container { | ||
| 100 | + width: 100%; | ||
| 101 | + height: 100vh; | ||
| 102 | + display: flex; | ||
| 103 | + flex-direction: column; | ||
| 104 | + background-color: #fff; | ||
| 105 | + position: relative; | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +.web-view { | ||
| 109 | + flex: 1; | ||
| 110 | + width: 100%; | ||
| 111 | + // 确保webview高度在底部导航以上 | ||
| 112 | + height: calc(100vh - 120rpx); // 减去底部导航的高度 | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +.loading-container { | ||
| 116 | + position: absolute; | ||
| 117 | + top: 50%; | ||
| 118 | + left: 50%; | ||
| 119 | + transform: translate(-50%, -50%); | ||
| 120 | + display: flex; | ||
| 121 | + flex-direction: column; | ||
| 122 | + align-items: center; | ||
| 123 | + justify-content: center; | ||
| 124 | + z-index: 10; | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +.loading-spinner { | ||
| 128 | + font-size: 48rpx; | ||
| 129 | + margin-bottom: 16rpx; | ||
| 130 | + animation: spin 1s linear infinite; | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +.loading-text { | ||
| 134 | + font-size: 28rpx; | ||
| 135 | + color: #666; | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +@keyframes spin { | ||
| 139 | + 0% { transform: rotate(0deg); } | ||
| 140 | + 100% { transform: rotate(360deg); } | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +.error-container { | ||
| 144 | + position: absolute; | ||
| 145 | + top: 50%; | ||
| 146 | + left: 50%; | ||
| 147 | + transform: translate(-50%, -50%); | ||
| 148 | + display: flex; | ||
| 149 | + flex-direction: column; | ||
| 150 | + align-items: center; | ||
| 151 | + justify-content: center; | ||
| 152 | + text-align: center; | ||
| 153 | + z-index: 10; | ||
| 154 | +} | ||
| 155 | + | ||
| 156 | +.error-icon { | ||
| 157 | + font-size: 80rpx; | ||
| 158 | + margin-bottom: 24rpx; | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +.error-text { | ||
| 162 | + font-size: 28rpx; | ||
| 163 | + color: #666; | ||
| 164 | + margin-bottom: 32rpx; | ||
| 165 | +} | ||
| 166 | +</style> | ... | ... |
src/pages/Webview/index.config.js
0 → 100644
src/pages/Webview/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="webview-container"> | ||
| 3 | + <!-- 导航栏 --> | ||
| 4 | + <view class="nav-bar"> | ||
| 5 | + <view class="nav-left" @click="handleBack"> | ||
| 6 | + <view class="back-icon">←</view> | ||
| 7 | + <text class="back-text">返回</text> | ||
| 8 | + </view> | ||
| 9 | + <view class="nav-title">{{ pageTitle }}</view> | ||
| 10 | + <view class="nav-right"></view> | ||
| 11 | + </view> | ||
| 12 | + | ||
| 13 | + <!-- WebView内容 --> | ||
| 14 | + <web-view | ||
| 15 | + v-if="webUrl" | ||
| 16 | + :src="webUrl" | ||
| 17 | + class="web-view" | ||
| 18 | + @message="handleMessage" | ||
| 19 | + @load="handleLoad" | ||
| 20 | + @error="handleError" | ||
| 21 | + ></web-view> | ||
| 22 | + | ||
| 23 | + <!-- 加载状态 --> | ||
| 24 | + <view v-if="loading" class="loading-container"> | ||
| 25 | + <view class="loading-spinner">⏳</view> | ||
| 26 | + <view class="loading-text">加载中...</view> | ||
| 27 | + </view> | ||
| 28 | + | ||
| 29 | + <!-- 错误状态 --> | ||
| 30 | + <view v-if="error" class="error-container"> | ||
| 31 | + <view class="error-icon">⚠️</view> | ||
| 32 | + <view class="error-text">页面加载失败</view> | ||
| 33 | + <nut-button type="primary" size="small" @click="handleRetry">重试</nut-button> | ||
| 34 | + </view> | ||
| 35 | + </view> | ||
| 36 | +</template> | ||
| 37 | + | ||
| 38 | +<script setup> | ||
| 39 | +import { ref, onMounted } from 'vue' | ||
| 40 | +import Taro from '@tarojs/taro' | ||
| 41 | + | ||
| 42 | +/** | ||
| 43 | + * WebView页面组件 | ||
| 44 | + * 用于预览外部网页链接 | ||
| 45 | + */ | ||
| 46 | + | ||
| 47 | +// 页面状态 | ||
| 48 | +const webUrl = ref('') | ||
| 49 | +const pageTitle = ref('网页预览') | ||
| 50 | +const loading = ref(true) | ||
| 51 | +const error = ref(false) | ||
| 52 | + | ||
| 53 | +/** | ||
| 54 | + * 获取页面参数 | ||
| 55 | + */ | ||
| 56 | +const getPageParams = () => { | ||
| 57 | + const instance = Taro.getCurrentInstance() | ||
| 58 | + const params = instance.router?.params || {} | ||
| 59 | + | ||
| 60 | + if (params.url) { | ||
| 61 | + webUrl.value = decodeURIComponent(params.url) | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + if (params.title) { | ||
| 65 | + pageTitle.value = decodeURIComponent(params.title) | ||
| 66 | + } | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +/** | ||
| 70 | + * 处理返回按钮点击 | ||
| 71 | + */ | ||
| 72 | +const handleBack = () => { | ||
| 73 | + Taro.navigateBack() | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +/** | ||
| 77 | + * 处理WebView加载完成 | ||
| 78 | + */ | ||
| 79 | +const handleLoad = (e) => { | ||
| 80 | + console.log('WebView加载完成:', e) | ||
| 81 | + loading.value = false | ||
| 82 | + error.value = false | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +/** | ||
| 86 | + * 处理WebView加载错误 | ||
| 87 | + */ | ||
| 88 | +const handleError = (e) => { | ||
| 89 | + console.error('WebView加载错误:', e) | ||
| 90 | + loading.value = false | ||
| 91 | + error.value = true | ||
| 92 | + | ||
| 93 | + Taro.showToast({ | ||
| 94 | + title: '页面加载失败', | ||
| 95 | + icon: 'none' | ||
| 96 | + }) | ||
| 97 | +} | ||
| 98 | + | ||
| 99 | +/** | ||
| 100 | + * 处理WebView消息 | ||
| 101 | + */ | ||
| 102 | +const handleMessage = (e) => { | ||
| 103 | + console.log('WebView消息:', e) | ||
| 104 | + // 可以在这里处理来自WebView的消息 | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +/** | ||
| 108 | + * 重试加载 | ||
| 109 | + */ | ||
| 110 | +const handleRetry = () => { | ||
| 111 | + loading.value = true | ||
| 112 | + error.value = false | ||
| 113 | + // 重新设置URL触发重新加载 | ||
| 114 | + const currentUrl = webUrl.value | ||
| 115 | + webUrl.value = '' | ||
| 116 | + setTimeout(() => { | ||
| 117 | + webUrl.value = currentUrl | ||
| 118 | + }, 100) | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +// 页面挂载时获取参数 | ||
| 122 | +onMounted(() => { | ||
| 123 | + getPageParams() | ||
| 124 | + | ||
| 125 | + // 如果没有URL参数,显示错误 | ||
| 126 | + if (!webUrl.value) { | ||
| 127 | + loading.value = false | ||
| 128 | + error.value = true | ||
| 129 | + Taro.showToast({ | ||
| 130 | + title: '缺少URL参数', | ||
| 131 | + icon: 'none' | ||
| 132 | + }) | ||
| 133 | + } | ||
| 134 | +}) | ||
| 135 | +</script> | ||
| 136 | + | ||
| 137 | +<script> | ||
| 138 | +export default { | ||
| 139 | + name: 'WebViewPage' | ||
| 140 | +} | ||
| 141 | +</script> | ||
| 142 | + | ||
| 143 | +<style lang="less"> | ||
| 144 | +.webview-container { | ||
| 145 | + width: 100%; | ||
| 146 | + height: 100vh; | ||
| 147 | + display: flex; | ||
| 148 | + flex-direction: column; | ||
| 149 | + background-color: #fff; | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +.nav-bar { | ||
| 153 | + display: flex; | ||
| 154 | + align-items: center; | ||
| 155 | + justify-content: space-between; | ||
| 156 | + height: 88rpx; | ||
| 157 | + padding: 0 32rpx; | ||
| 158 | + background-color: #fff; | ||
| 159 | + border-bottom: 1rpx solid #eee; | ||
| 160 | + position: sticky; | ||
| 161 | + top: 0; | ||
| 162 | + z-index: 100; | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +.nav-left { | ||
| 166 | + display: flex; | ||
| 167 | + align-items: center; | ||
| 168 | + cursor: pointer; | ||
| 169 | + | ||
| 170 | + .back-icon { | ||
| 171 | + font-size: 32rpx; | ||
| 172 | + color: #333; | ||
| 173 | + font-weight: bold; | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + .back-text { | ||
| 177 | + margin-left: 8rpx; | ||
| 178 | + font-size: 28rpx; | ||
| 179 | + color: #333; | ||
| 180 | + } | ||
| 181 | +} | ||
| 182 | + | ||
| 183 | +.nav-title { | ||
| 184 | + font-size: 32rpx; | ||
| 185 | + font-weight: 600; | ||
| 186 | + color: #333; | ||
| 187 | + text-align: center; | ||
| 188 | + flex: 1; | ||
| 189 | +} | ||
| 190 | + | ||
| 191 | +.nav-right { | ||
| 192 | + width: 80rpx; | ||
| 193 | +} | ||
| 194 | + | ||
| 195 | +.web-view { | ||
| 196 | + flex: 1; | ||
| 197 | + width: 100%; | ||
| 198 | +} | ||
| 199 | + | ||
| 200 | +.loading-container { | ||
| 201 | + position: absolute; | ||
| 202 | + top: 50%; | ||
| 203 | + left: 50%; | ||
| 204 | + transform: translate(-50%, -50%); | ||
| 205 | + display: flex; | ||
| 206 | + flex-direction: column; | ||
| 207 | + align-items: center; | ||
| 208 | + justify-content: center; | ||
| 209 | +} | ||
| 210 | + | ||
| 211 | +.loading-spinner { | ||
| 212 | + font-size: 48rpx; | ||
| 213 | + margin-bottom: 16rpx; | ||
| 214 | + animation: spin 1s linear infinite; | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +.loading-text { | ||
| 218 | + font-size: 28rpx; | ||
| 219 | + color: #666; | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +@keyframes spin { | ||
| 223 | + 0% { transform: rotate(0deg); } | ||
| 224 | + 100% { transform: rotate(360deg); } | ||
| 225 | +} | ||
| 226 | + | ||
| 227 | +.error-container { | ||
| 228 | + position: absolute; | ||
| 229 | + top: 50%; | ||
| 230 | + left: 50%; | ||
| 231 | + transform: translate(-50%, -50%); | ||
| 232 | + display: flex; | ||
| 233 | + flex-direction: column; | ||
| 234 | + align-items: center; | ||
| 235 | + justify-content: center; | ||
| 236 | + text-align: center; | ||
| 237 | +} | ||
| 238 | + | ||
| 239 | +.error-icon { | ||
| 240 | + font-size: 80rpx; | ||
| 241 | + margin-bottom: 24rpx; | ||
| 242 | +} | ||
| 243 | + | ||
| 244 | +.error-text { | ||
| 245 | + font-size: 28rpx; | ||
| 246 | + color: #666; | ||
| 247 | + margin-bottom: 32rpx; | ||
| 248 | +} | ||
| 249 | +</style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/pages/Webview/test.config.js
0 → 100644
src/pages/Webview/test.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="test-container"> | ||
| 3 | + <view class="test-header"> | ||
| 4 | + <text class="test-title">WebView页面测试</text> | ||
| 5 | + </view> | ||
| 6 | + | ||
| 7 | + <view class="test-content"> | ||
| 8 | + <view class="test-section"> | ||
| 9 | + <text class="section-title">测试链接:</text> | ||
| 10 | + <nut-button | ||
| 11 | + type="primary" | ||
| 12 | + size="small" | ||
| 13 | + @click="testWebView('https://www.baidu.com', '百度搜索')" | ||
| 14 | + class="test-btn" | ||
| 15 | + > | ||
| 16 | + 测试百度 | ||
| 17 | + </nut-button> | ||
| 18 | + | ||
| 19 | + <nut-button | ||
| 20 | + type="success" | ||
| 21 | + size="small" | ||
| 22 | + @click="testWebView('https://m.taobao.com', '淘宝网')" | ||
| 23 | + class="test-btn" | ||
| 24 | + > | ||
| 25 | + 测试淘宝 | ||
| 26 | + </nut-button> | ||
| 27 | + | ||
| 28 | + <nut-button | ||
| 29 | + type="warning" | ||
| 30 | + size="small" | ||
| 31 | + @click="testWebView('https://github.com', 'GitHub')" | ||
| 32 | + class="test-btn" | ||
| 33 | + > | ||
| 34 | + 测试GitHub | ||
| 35 | + </nut-button> | ||
| 36 | + </view> | ||
| 37 | + | ||
| 38 | + <view class="test-section"> | ||
| 39 | + <text class="section-title">自定义URL测试:</text> | ||
| 40 | + <nut-input | ||
| 41 | + v-model="customUrl" | ||
| 42 | + placeholder="请输入要测试的URL" | ||
| 43 | + class="url-input" | ||
| 44 | + /> | ||
| 45 | + <nut-input | ||
| 46 | + v-model="customTitle" | ||
| 47 | + placeholder="请输入页面标题" | ||
| 48 | + class="title-input" | ||
| 49 | + /> | ||
| 50 | + <nut-button | ||
| 51 | + type="primary" | ||
| 52 | + @click="testCustomUrl" | ||
| 53 | + class="test-btn" | ||
| 54 | + > | ||
| 55 | + 测试自定义URL | ||
| 56 | + </nut-button> | ||
| 57 | + </view> | ||
| 58 | + </view> | ||
| 59 | + </view> | ||
| 60 | +</template> | ||
| 61 | + | ||
| 62 | +<script setup> | ||
| 63 | +import { ref } from 'vue' | ||
| 64 | +import Taro from '@tarojs/taro' | ||
| 65 | + | ||
| 66 | +/** | ||
| 67 | + * WebView测试页面 | ||
| 68 | + */ | ||
| 69 | + | ||
| 70 | +const customUrl = ref('') | ||
| 71 | +const customTitle = ref('') | ||
| 72 | + | ||
| 73 | +/** | ||
| 74 | + * 测试WebView页面 | ||
| 75 | + * @param {string} url - 要测试的URL | ||
| 76 | + * @param {string} title - 页面标题 | ||
| 77 | + */ | ||
| 78 | +const testWebView = (url, title) => { | ||
| 79 | + Taro.navigateTo({ | ||
| 80 | + url: `/pages/webview/index?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}` | ||
| 81 | + }) | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +/** | ||
| 85 | + * 测试自定义URL | ||
| 86 | + */ | ||
| 87 | +const testCustomUrl = () => { | ||
| 88 | + if (!customUrl.value) { | ||
| 89 | + Taro.showToast({ | ||
| 90 | + title: '请输入URL', | ||
| 91 | + icon: 'none' | ||
| 92 | + }) | ||
| 93 | + return | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + const url = customUrl.value.startsWith('http') ? customUrl.value : `https://${customUrl.value}` | ||
| 97 | + const title = customTitle.value || '自定义页面' | ||
| 98 | + | ||
| 99 | + testWebView(url, title) | ||
| 100 | +} | ||
| 101 | +</script> | ||
| 102 | + | ||
| 103 | +<script> | ||
| 104 | +export default { | ||
| 105 | + name: 'WebViewTest' | ||
| 106 | +} | ||
| 107 | +</script> | ||
| 108 | + | ||
| 109 | +<style lang="less"> | ||
| 110 | +.test-container { | ||
| 111 | + padding: 32rpx; | ||
| 112 | + background-color: #f5f5f5; | ||
| 113 | + min-height: 100vh; | ||
| 114 | +} | ||
| 115 | + | ||
| 116 | +.test-header { | ||
| 117 | + text-align: center; | ||
| 118 | + margin-bottom: 48rpx; | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +.test-title { | ||
| 122 | + font-size: 36rpx; | ||
| 123 | + font-weight: 600; | ||
| 124 | + color: #333; | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +.test-content { | ||
| 128 | + background-color: #fff; | ||
| 129 | + border-radius: 16rpx; | ||
| 130 | + padding: 32rpx; | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +.test-section { | ||
| 134 | + margin-bottom: 48rpx; | ||
| 135 | + | ||
| 136 | + &:last-child { | ||
| 137 | + margin-bottom: 0; | ||
| 138 | + } | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +.section-title { | ||
| 142 | + display: block; | ||
| 143 | + font-size: 28rpx; | ||
| 144 | + font-weight: 600; | ||
| 145 | + color: #333; | ||
| 146 | + margin-bottom: 24rpx; | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +.test-btn { | ||
| 150 | + margin-right: 16rpx; | ||
| 151 | + margin-bottom: 16rpx; | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +.url-input, | ||
| 155 | +.title-input { | ||
| 156 | + margin-bottom: 16rpx; | ||
| 157 | +} | ||
| 158 | +</style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment