hookehuyr

feat(Webview): 添加Webview页面及相关测试功能

refactor(BottomNav): 替换图标为SVG并添加样式过滤
style(Activities): 更新页面标题和导航栏样式
docs: 添加文件描述注释和最后编辑信息
...@@ -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']
......
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
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
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
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>
24 - <view class="p-4">
25 - <h2 class="text-2xl font-bold mb-2">{{ selectedPoint.name }}</h2>
26 - <p class="text-gray-600 mb-6">{{ selectedPoint.description }}</p>
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']">
28 - {{ selectedPoint.isCheckedIn ? '已打卡' : '打卡' }}
29 - </button>
30 - </view>
31 - </view>
32 - </view>
33 - <!-- Check-in success message -->
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> 17 </view>
18 +
19 + <!---but态 -->
20 + <view v-if="error" class="error-container">
21 + <view class="error-icon">⚠️</view>
22 + <view class="error-text">页面加载失败</view>
23 + <nut-button type="primary" size="small" @click="handleRetry">重试</nut-button>
39 </view> 24 </view>
25 +
26 + <!-- 底部导航 -->
40 <BottomNav /> 27 <BottomNav />
41 </view> 28 </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>
......
1 +export default {
2 + navigationBarTitleText: '网页预览',
3 + navigationStyle: 'custom',
4 + enablePullDownRefresh: false,
5 + backgroundTextStyle: 'dark'
6 +}
...\ No newline at end of file ...\ No newline at end of file
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
1 +export default {
2 + navigationBarTitleText: 'WebView测试页面',
3 + enablePullDownRefresh: false,
4 + backgroundTextStyle: 'dark'
5 +}
...\ No newline at end of file ...\ No newline at end of file
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