pages.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
pages/home/home.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
static/common/item-films.png | 补丁 | 查看 | 原始文档 | blame | 历史 | |
static/common/item-map.png | 补丁 | 查看 | 原始文档 | blame | 历史 | |
static/common/item-route.png | 补丁 | 查看 | 原始文档 | blame | 历史 | |
sub-pages/hot-city/hot-city-detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
sub-pages/hot-city/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
pages.json
@@ -86,6 +86,25 @@ } } ] }, { "root": "sub-pages/hot-city", "pages": [ { "path": "index", "style": { "navigationBarTitleText": "城市", "enablePullDownRefresh": true } } ,{ "path": "hot-city-detail", "style": { "navigationBarTitleText": "城市详情", "enablePullDownRefresh": true } } ] } ,{ "root": "sub-pages/mine", pages/home/home.vue
@@ -23,7 +23,7 @@ </view> <view class="trip-card-swiper"> <swiper :current="currentPage" @change="onSwiperChange" circular style="min-height: 1850rpx;"> <swiper :current="currentPage" @change="onSwiperChange" circular style="min-height: 1650rpx;"> <swiper-item v-for="(group, pageIndex) in pagedTripCards" :key="pageIndex"> <TripCard v-for="(item, index) in group" :key="index" :tag="item.tag" :title="item.coverTitle" :subtitle="item.coverAlt" :score="item.collectCount" :imageUrl="item.coverUrl" @@ -32,8 +32,34 @@ </swiper> </view> <!-- <SectionTitle title="全球影视地标" optitle="查看全部" goUrl="/pages/home/home-more" />--> <SectionTitle title="全球影视地标" optitle="查看全部" goUrl="/pages/home/home-more" /> <!-- <GlobalGeo />--> <view class="continent-section"> <view class="continent-item all" @click="navigateToAll">全部</view> <view class="continents-container"> <view class="continent-row" v-for="(row, index) in continentRows" :key="index"> <view class="continent-item continent" v-for="(continent, colIndex) in row" :key="colIndex" @click="navigateToDetail(continent)" > {{ continent.name }} </view> <!-- 添加占位元素保持布局 --> <view v-for="n in (3 - row.length)" :key="'placeholder'+n" class="continent-placeholder" ></view> </view> </view> <view class="continent-item separator"></view> <view class="continent-item nearby" @click="navigateToNearby"> <view>附</view> <view>近</view> </view> </view> <SectionTitle title="场景博物馆" optitle="查看全部" goUrl="/sub-pages/hot-spot/index" /> <SceneMuseumCard v-for="(item, index) in cardList" :key="index" :image="item.image" :title="item.title" @@ -57,7 +83,6 @@ import HomeMain from './home-main.vue' import TripCard from './trip-card.vue' import GlobalGeo from './global-geo.vue' import SceneMuseumCard from './scene-museum-card.vue' import Community from './community.vue' import { SwiperChangeEvent } from '@dcloudio/uni-app' @@ -70,6 +95,54 @@ // 主题 const theme = ref('light') const continents = ref([]) const getContinents = async () => { try { const {code, data} = await $http.request('get', '/api/code/value?type=CONTINENT_TYPE') if (code == 0 && data) { continents.value = data.map(item => ( { id: item.id, name: item.label }) ) } } catch (error) { console.log('获取洲数据失败', error) $message.showToast('获取洲数据失败') } } const continentRows = computed(() => { const rows = []; const continentsList = [...continents.value]; // 将洲分成三行 for (let i = 0; i < 3; i++) { const start = i * 3; const end = start + 3; rows.push(continentsList.slice(start, end)); } return rows; }); const navigateToDetail = (continent) => { //跳转到具体页面 uni.navigateTo({ url: `/sub-pages/hot-city/index?continentId=${continent.id}` }) } const navigateToNearby = () => { //附近功能跳转 uni.navigateTo({ url: '/pages/nearby/nearby' }) } // 当前页数 @@ -115,6 +188,7 @@ onMounted(() => { const localTheme = uni.getStorageSync('theme') || 'light' theme.value = localTheme getContinents() }) onShow(() => { @@ -255,6 +329,82 @@ margin-top: 20rpx; margin-bottom: 20rpx; } .continent-section { display: flex; align-items: stretch; border-top: 1px dashed #ccc; border-bottom: 1px dashed #ccc; padding: 10rpx 0; height: 220rpx; /* 固定高度容纳三行 */ } .all, .nearby { flex-shrink: 0; width: 15%; display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 30rpx; font-weight: bold; } .continents-container { flex: 1; display: flex; flex-direction: column; justify-content: space-between; } .continent-row { display: flex; justify-content: flex-start; /* 改为左对齐 */ height: 33.33%; /* 每行高度占三分之一 */ padding: 5rpx 0; } .continent { flex: 0 0 30%; /* 固定宽度30% */ display: flex; justify-content: center; align-items: center; text-align: center; font-size: 28rpx; padding: 5rpx 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 5%; /* 元素间间距 */ } /* 最后一个元素不需要右边距 */ .continent:last-child { margin-right: 0; } .continent-placeholder { flex: 0 0 30%; /* 占位元素宽度 */ margin-right: 5%; /* 保持与正常元素相同的间距 */ } /* 最后一个占位元素不需要右边距 */ .continent-placeholder:last-child { margin-right: 0; } .separator { width: 1px; background-color: #ccc; margin: 0 10rpx; align-self: center; height: 80%; /* 分隔线高度为父容器的80% */ } .nearby { flex-direction: column; font-size: 30rpx; } </style> <style scoped lang="scss"> static/common/item-films.png
static/common/item-map.png
static/common/item-route.png
sub-pages/hot-city/hot-city-detail.vue
对比新文件 @@ -0,0 +1,375 @@ <template> <view class="container"> <view class="header-info"> <image src="/static/common/earth.png" class="earth-icon"></image> <view class="city-info"> <text class="city-name">{{cityInfo.cityName}}</text> <text class="city-pinyin">{{cityInfo.cityPinyin}}</text> <text class="city-continent">{{cityInfo.cityCountry}} {{cityInfo.cityContinent}}</text> </view> </view> <view class="main-image"> <image :src="cityInfo.mainImage" mode="aspectFit" class="main-img" /> </view> <view class="main-text"> <text class="info-title1">{{cityInfo.infoTitle1}}</text> <text class="info-title2"> {{cityInfo.infoTitle2}} </text> </view> <!-- 添加三个可点击块 --> <view class="tab-container"> <view class="tab-item" @click="activeTab = 'films'"> <image src="/static/common/item-films.png" class="tab-icon"></image> <text class="tab-text">影片</text> </view> <view class="tab-item" @click="activeTab = 'map'"> <image src="/static/common/item-map.png" class="tab-icon"></image> <text class="tab-text">地图</text> </view> <view class="tab-item" @click="activeTab = 'route'"> <image src="/static/common/item-route.png" class="tab-icon"></image> <text class="tab-text">路线</text> </view> </view> <!-- 根据 activeTab 展示不同内容 --> <view v-if="activeTab === 'films'" class="content-container"> <text class="content-title">影片列表</text> <!-- 影片列表内容 --> <view v-for="(film, index) in films" :key="index" class="film-item" @click="goToFilmDetail(film.id)"> <image :src="film.coverUrl" class="film-poster"></image> <text class="film-name">{{ film.nameCn }}</text> </view> </view> <view v-if="activeTab === 'map'" class="content-container"> <text class="content-title">景点列表和地图</text> <!-- 地图内容 --> <map class="map-content" :latitude="mapCenter.latitude" :longitude="mapCenter.longitude" :scale="mapCenter.scale" :markers="mapMarkers" ></map> <view v-for="(spot, index) in spots" :key="index" class="spot-item" @click="goToSpotDetail(spot.id)"> <text class="spot-name">{{ spot.locationName }}</text> <text class="spot-desc">{{ spot.landmarkDesc }}</text> </view> </view> <view v-if="activeTab === 'route'" class="content-container"> <text class="content-title">路线</text> <!-- 路线内容(暂时为空) --> <text>暂无内容</text> </view> </view> </template> <script setup lang="ts"> import { ref } from 'vue'; import { onLoad } from '@dcloudio/uni-app' import { useGlobal } from '@/composables/useGlobal' const { $http, $message } = useGlobal() import {FilmWorks,FilmLocationVO} from "@/types/index"; // 定义 activeTab 并设置默认值为 'films' const activeTab = ref('films'); const films = ref<FilmWorks[]>(); const cityInfo = ref({ cityName: '北京', cityPinyin: 'Beijing', cityCountry: '中国', cityContinent: 'Asia', mainImage: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', infoTitle1: '', infoTitle2: '', }) onLoad(async (options: any) => { const name = options.name const pinyin = options.pinyin const country = options.country const continent = options.continent // 修复后的代码 - 确保所有必需的属性都被设置 cityInfo.value = { cityName: name || '北京', // 提供默认值 cityPinyin: pinyin || '', cityCountry: country || '中国', cityContinent: continent || 'Asia', mainImage: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', infoTitle1: '', infoTitle2: '' } if (name) { await getRelatedFilms(name) await getLocations(name) } }) const getRelatedFilms = async (name: string) => { const { code, data } = await $http.request('get', '/api/filmLocation/city', { params: { name } }) if (code === 0 && Array.isArray(data)) { films.value = data } else { films.value = [] } } const getLocations = async (name: string) => { const { code, data } = await $http.request('get', '/api/filmLocation/location', { params: { name } }) if (code === 0 && Array.isArray(data)) { spots.value = data if (data.length > 0) { // 使用第一个景点的数据更新cityInfo const firstSpot = data[0]; // 更新主图,如果没有locationUrl则使用默认图 cityInfo.value.mainImage = firstSpot.locationUrl || cityInfo.value.mainImage; // 更新infoTitle1,使用locationName if (firstSpot.locationName) { cityInfo.value.infoTitle1 = firstSpot.locationName; } // 更新infoTitle2,组合address、sceneType、classicScene和landmarkDesc let infoTitle2Content = ''; if (firstSpot.address) { infoTitle2Content += firstSpot.address; } if (firstSpot.sceneType) { infoTitle2Content += (infoTitle2Content ? ' ' : '') + firstSpot.sceneType; } if (firstSpot.classicScene) { infoTitle2Content += (infoTitle2Content ? ' ' : '') + firstSpot.classicScene; } if (firstSpot.landmarkDesc) { infoTitle2Content += (infoTitle2Content ? ' ' : '') + firstSpot.landmarkDesc; } if (infoTitle2Content) { cityInfo.value.infoTitle2 = infoTitle2Content; } // 设置中心点为第一个景点的位置 mapCenter.value.latitude = data[0].gpsLat || 39.9042 mapCenter.value.longitude = data[0].gpsLng || 116.4074 mapCenter.value.scale = 12 // 创建标记点数组 mapMarkers.value = data.map((spot, index) => ({ id: index, latitude: spot.gpsLat, longitude: spot.gpsLng, title: spot.locationName, iconPath: '/static/common/marker.png', // 可以替换为实际的标记图标路径 width: 30, height: 30 })).filter(marker => marker.latitude && marker.longitude) // 过滤掉没有坐标的标记 } } else { spots.value = [] } } const spots = ref<FilmLocationVO[]>(); // 添加地图中心点和标记数据 const mapCenter = ref({ latitude: 39.9042, longitude: 116.4074, scale: 10 }); const mapMarkers = ref([]); // 跳转到影片详情页 const goToFilmDetail = (id: number) => { uni.navigateTo({ url: `/sub-pages/film-list/film-detail?id=${id}` }); }; // 跳转到景点详情页 const goToSpotDetail = (id: number) => { uni.navigateTo({ url: `/sub-pages/hot-spot/spot-detail?id=${id}` }); }; </script> <style scoped> .container { padding: 20rpx; } .header-info { display: flex; align-items: center; margin-bottom: 30rpx; padding: 20rpx; background-color: #f8f8f8; border-radius: 16rpx; } .earth-icon{ width: 180rpx; height: 180rpx; margin-right: 30rpx; } .city-info { display: flex; flex-direction: column; } .city-name { font-size: 40rpx; font-weight: bold; margin-bottom: 10rpx; } .city-pinyin { font-size: 32rpx; color: #666; margin-bottom: 10rpx; } .city-continent { font-size: 28rpx; color: #999; } .main-image { width: 100%; height: 400rpx; margin-bottom: 30rpx; border-radius: 16rpx; overflow: hidden; } .main-img { width: 100%; height: 100%; } .main-text { padding: 20rpx; margin-bottom: 30rpx; background-color: #f8f8f8; border-radius: 16rpx; } .info-title1 { font-size: 32rpx; font-weight: bold; margin-bottom: 15rpx; display: block; } .info-title2 { font-size: 28rpx; line-height: 1.6; color: #333; } .tab-container { display: flex; justify-content: space-around; padding: 20rpx 0; margin-bottom: 30rpx; border-top: 1rpx solid #eee; border-bottom: 1rpx solid #eee; } .tab-item { display: flex; flex-direction: column; align-items: center; padding: 20rpx; } .tab-icon { width: 60rpx; height: 60rpx; margin-bottom: 10rpx; } .tab-text { font-size: 28rpx; } .content-container { padding: 20rpx; background-color: #f8f8f8; border-radius: 16rpx; margin-bottom: 20rpx; } .content-title { font-size: 36rpx; font-weight: bold; margin-bottom: 30rpx; display: block; } .film-item { display: flex; align-items: center; margin-bottom: 30rpx; padding: 20rpx; background-color: #fff; border-radius: 12rpx; } .film-poster { width: 120rpx; height: 160rpx; margin-right: 20rpx; border-radius: 8rpx; } .film-name { font-size: 32rpx; } .map-content { width: 100%; height: 400rpx; margin-bottom: 30rpx; border-radius: 16rpx; } .spot-item { margin-bottom: 30rpx; padding: 20rpx; background-color: #fff; border-radius: 12rpx; } .spot-name { font-size: 32rpx; font-weight: bold; margin-bottom: 10rpx; display: block; } .spot-desc { font-size: 28rpx; color: #666; } </style> sub-pages/hot-city/index.vue
对比新文件 @@ -0,0 +1,381 @@ <template> <view :class="['app', theme]"> <!-- 搜索框部分 --> <view style="padding-top: 10rpx;"> <up-search height="70rpx" placeholder="搜索城市" search-icon-size="40rpx" margin="0rpx 30rpx" v-model="search" :show-action="true" @search="onSearch" @custom="onSearch" @click-icon="onSearch" /> </view> <!-- 标签切换部分 --> <up-tabs :list="tabList" @click="click"> <template #right> <view style="padding-left: 4px;" @tap="() => showToast('插槽被点击')"> <up-icon name="list" size="40rpx" bold></up-icon> </view> </template> </up-tabs> <!-- 城市列表 --> <view class="city-items"> <!-- 搜索状态下的列表 --> <template v-if="search"> <view class="city-item" v-for="city in displayedCities" :key="city.id" @click="handleCityClick(city)" > {{ city.name }} </view> </template> <!-- 非搜索状态下的列表 --> <template v-else> <!-- 热门城市部分 --> <view class="section" v-if="filteredHotCities.length"> <view class="section-title">热门城市</view> <view class="hot-city-list"> <view class="hot-city-item" v-for="city in filteredHotCities" :key="'hot-'+city.id" @click="handleCityClick(city)" > {{ city.name }} </view> </view> </view> <!-- A-Z城市列表 --> <view class="section" v-for="group in groupedCities" :key="group.letter"> <view class="section-title">{{ group.letter }}</view> <view class="city-detail-item" v-for="city in group.cities" :key="'az-'+city.id" @click="handleCityClick(city)" > <view class="city-name">{{ city.name }}</view> <view class="city-sub-info"> <text class="city-pinyin">{{ city.pinyin }}</text> <text class="city-country"> · {{ city.country }}</text> </view> </view> </view> </template> </view> </view> </template> <script setup lang="ts"> import { ref, computed, onMounted, watch } from 'vue' import { onLoad } from '@dcloudio/uni-app' import { useGlobal } from '@/composables/useGlobal' const { $http, $message, $store } = useGlobal() const theme = ref('light') const search = ref('') // 响应式数据 const tabList = ref([]) const currentTab = ref('Asia') // 更新热门城市数据结构,添加国家字段 // const hotCities = ref([ // { id: 1, name: '北京市', pinyin: 'Beijing', country: '中国', continent: 'asia' }, // { id: 2, name: '上海市', pinyin: 'Shanghai', country: '中国', continent: 'asia' }, // { id: 3, name: '纽约', pinyin: 'New York', country: '美国', continent: 'northAmerica' }, // { id: 4, name: '伦敦', pinyin: 'London', country: '英国', continent: 'europe' }, // ]) const hotCities = ref([]) // 更新所有城市数据结构,添加国家字段 const allCities = ref([]) // 当前洲的热门城市 const filteredHotCities = computed(() => { if (currentTab.value === 'all') { return hotCities.value; } return hotCities.value.filter(city => city.continent === currentTab.value); }); // 当前洲的所有城市(按拼音分组) const groupedCities = computed(() => { // 获取当前洲的城市列表 let cities = []; if (currentTab.value === 'all') { cities = [...allCities.value]; } else { cities = allCities.value.filter(city => city.continent === currentTab.value); } // 按拼音首字母分组 const groups: Record<string, any[]> = {}; cities.forEach(city => { const firstLetter = city.pinyin.charAt(0).toUpperCase(); if (!groups[firstLetter]) { groups[firstLetter] = []; } groups[firstLetter].push(city); }); // 转换为数组并按字母排序 return Object.keys(groups) .sort() .map(letter => ({ letter, cities: groups[letter].sort((a, b) => a.pinyin.localeCompare(b.pinyin)) })); }); // 根据当前标签显示对应的城市(用于搜索状态) const displayedCities = computed(() => { let cities = []; if (currentTab.value === 'all') { cities = [...hotCities.value, ...allCities.value]; } else { cities = allCities.value.filter(city => city.continent === currentTab.value); } // 添加搜索过滤功能 if (search.value) { return cities.filter(city => city.name?.includes(search.value) || city.pinyin?.toLowerCase().includes(search.value.toLowerCase()) ) } return cities }) // 标签点击事件 // const click = (item: { name: string; value: string }) => { // currentTab.value = item.value // console.log('currentTab:', currentTab.value) // } const click = async (item: { name: string; value: string }) => { currentTab.value = item.value; console.log('currentTab:', currentTab.value); // 显示加载状态 uni.showLoading({ title: '加载中...' }); try { await getHotCities(); await getAllCities(); } finally { uni.hideLoading(); } }; // 搜索事件 const onSearch = (value: string) => { const searchValue = typeof value === 'string' ? value : search.value search.value = searchValue } const handleCityClick = (city: any) => { console.log('City clicked:', city) // 实际应用中这里可以添加选择城市后的逻辑 uni.navigateTo({ url: `/sub-pages/hot-city/hot-city-detail?name=${city.name}` }) } const showToast = (msg: string) => { uni.showToast({ title: msg, icon: 'none' }) } onLoad(async(options) => { const storedTheme = uni.getStorageSync('theme') || 'light' theme.value = storedTheme console.log("options", options) if (options && options.continentId) { // 等待获取 tab 列表完成 await getTabList() const continentId = options.continentId.toString() console.log('continentId:', continentId); console.log("tabList.value", tabList.value) const tab = tabList.value.find(t => { console.log(`Comparing "${t.id}" with "${continentId}"`); return t.id.toString() === continentId.toString(); }); console.log("tab", tab) if (tab) { console.log(' currentTab.value ', currentTab.value ) currentTab.value = tab.value console.log(' currentTab.value ', currentTab.value ) console.log('Tab found and set:', tab) // 关键:在设置完 currentTab 后,立即获取对应的数据 await getHotCities(); await getAllCities(); } else { console.log('Tab not found for continentId:', options.continentId) console.log('Available tabs:', tabList.value) } } else { // 如果没有传参,正常获取 tab 列表和数据 await getTabList() await getHotCities(); await getAllCities(); } }) // onMounted(() => { // // 初始化数据 // // getTabList(); // getHotCities(); // getAllCities(); // }) // 监听currentTab变化,重新获取数据 // 确保 watch 能够深度监听并正确处理异步操作 watch(currentTab, async (newVal, oldVal) => { console.log('Tab changed from', oldVal, 'to', newVal); await getHotCities(); await getAllCities(); }, { flush: 'post' }); const getAllCities = async () => { // 获取所有城市 try{ const { code, data } = await $http.request('get', '/api/filmLocation/info',{ params: { continent: currentTab.value, } }) if (code === 0 && data) { allCities.value = data.map(item => ({ id: item.id, name: item.city, pinyin: item.pinyin, country: item.country, continent: item.continent })) } }catch (e) { console.log("获取热门城市失败", e) $message.showToast("获取热门城市失败") } } const getHotCities = async () => { //获取热门城市 try{ const { code, data } = await $http.request('get', '/api/filmHotCity/list',{ params: { size: 10, continent: currentTab.value=='all'?'':currentTab.value, current: 1 } }) if (code === 0 && data) { hotCities.value = data.records.map(item => ({ id: item.id, name: item.name, country: item.country, continent: item.continent })) } }catch (e) { console.log("获取热门城市失败", e) $message.showToast("获取热门城市失败") } } const getTabList = async () => { // 获取洲列表 try { const {code, data} = await $http.request('get', '/api/code/value?type=CONTINENT_TYPE') if (code == 0 && data) { tabList.value = data.map((item: any) => ({ id: item.id, name: item.label, value: item.value // 确保这里返回洲的标识符 })) } } catch (e) { console.log("获取洲数据失败", e) $message.showToast("获取洲数据失败") } } </script> <style scoped> .city-items { padding: 0 30rpx; } .section { margin-bottom: 30rpx; } .section-title { font-size: 28rpx; color: #666; padding: 20rpx 0; border-bottom: 1rpx solid #eee; margin-bottom: 15rpx; } .hot-city-list { display: flex; flex-wrap: wrap; gap: 20rpx; } .hot-city-item { background-color: #f5f5f5; padding: 15rpx 25rpx; border-radius: 10rpx; font-size: 28rpx; } .city-detail-item { padding: 25rpx 0; border-bottom: 1rpx solid #f5f5f5; } .city-name { font-size: 32rpx; font-weight: 500; margin-bottom: 8rpx; } .city-sub-info { display: flex; font-size: 26rpx; color: #888; } .city-pinyin { font-style: italic; } .city-country { margin-left: 5rpx; } /* 搜索状态下的简单列表样式 */ .city-item { padding: 25rpx 0; border-bottom: 1rpx solid #f5f5f5; font-size: 32rpx; } </style>