feat(aigc): implement travel AIGC feature and clean up legacy assets
- Replace all local static AIGC assets with OSS hosted URLs to reduce bundle size - Add travelAIGC command to ChatModel and update quick access menu to link to AIGC home - Implement AIGC user consent flow with local storage persistence for compliance - Refactor Discovery page layout, remove unused HeritageDiscoverySection component - Update ChatTopNavBar to support dynamic navigation bar height based on system info - Restructure ChatMainList layout for better discovery panel interaction - Remove deprecated local asset files and clean up unused imports across codebase
This commit is contained in:
16
src/constant/aigc.js
Normal file
16
src/constant/aigc.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { currentClientType } from "@/constant/base";
|
||||
|
||||
const CLIENT_TYPE = currentClientType();
|
||||
const AIGC_CONSENT_AGREED = `${CLIENT_TYPE}_AIGC_CONSENT_AGREED`;
|
||||
|
||||
export const getAigcConsentAgreed = () => {
|
||||
return uni.getStorageSync(AIGC_CONSENT_AGREED);
|
||||
};
|
||||
|
||||
export const setAigcConsentAgreed = (agreed = true) => {
|
||||
return uni.setStorageSync(AIGC_CONSENT_AGREED, agreed);
|
||||
};
|
||||
|
||||
export const removeAigcConsentAgreed = () => {
|
||||
return uni.removeStorageSync(AIGC_CONSENT_AGREED);
|
||||
};
|
||||
@@ -99,4 +99,6 @@ export const Command = {
|
||||
feedbackCard: "Command.feedbackCard",
|
||||
// 生成合成图片
|
||||
aigcPhotoGenerator: "Command.aigcPhotoGenerator",
|
||||
// 旅行AIGC
|
||||
travelAIGC: "Command.travelAIGC",
|
||||
};
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import glassBoatPostcard from "../../home/assets/glass-boat-postcard.gif";
|
||||
|
||||
export const aigcDetailResult = {
|
||||
id: "glass-boat-postcard-result",
|
||||
title: "鸳鸯湖明信片",
|
||||
cover: glassBoatPostcard,
|
||||
cover: "https://oss.nianxx.cn/mp/static/xiaoqi/glass-boat-postcard.gif",
|
||||
accent: "#0a4d46",
|
||||
variantLabel: "图片版",
|
||||
resultLabel: "图片版",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 772 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,8 +1,3 @@
|
||||
import glassBoatPostcard from "../assets/glass-boat-postcard.gif";
|
||||
import bridgeCover from "../assets/card-bridge.png";
|
||||
import waterfallCover from "../assets/card-waterfall.png";
|
||||
import cultureCover from "../assets/card-culture-1.png";
|
||||
|
||||
export const aigcTemplates = [
|
||||
{
|
||||
id: "glass-boat-postcard",
|
||||
@@ -11,7 +6,7 @@ export const aigcTemplates = [
|
||||
note: "图片版100积分 · 动效视频版200积分/5秒内",
|
||||
badge: "同源双版本",
|
||||
buttonText: "选这个模板",
|
||||
cover: glassBoatPostcard,
|
||||
cover: "https://oss.nianxx.cn/mp/static/xiaoqi/glass-boat-postcard.gif",
|
||||
tone: "postcard",
|
||||
accent: "#0a4d46",
|
||||
},
|
||||
@@ -22,7 +17,7 @@ export const aigcTemplates = [
|
||||
note: "图片版100积分 · 动效视频版200积分/5秒内",
|
||||
badge: "同源双版本",
|
||||
buttonText: "选这个模板",
|
||||
cover: bridgeCover,
|
||||
cover: "https://oss.nianxx.cn/mp/static/xiaoqi/card-bridge.png",
|
||||
tone: "emerald",
|
||||
accent: "#0f7069",
|
||||
},
|
||||
@@ -33,7 +28,7 @@ export const aigcTemplates = [
|
||||
note: "图片版100积分 · 动效视频版200积分/5秒内",
|
||||
badge: "同源双版本",
|
||||
buttonText: "选这个模板",
|
||||
cover: waterfallCover,
|
||||
cover: "https://oss.nianxx.cn/mp/static/xiaoqi/card-waterfall.png",
|
||||
tone: "aqua",
|
||||
accent: "#155e75",
|
||||
},
|
||||
@@ -44,7 +39,7 @@ export const aigcTemplates = [
|
||||
note: "图片版100积分 · 动效视频版200积分/5秒内",
|
||||
badge: "同源双版本",
|
||||
buttonText: "选这个模板",
|
||||
cover: cultureCover,
|
||||
cover: "https://oss.nianxx.cn/mp/static/xiaoqi/card-culture-1.png",
|
||||
tone: "forest",
|
||||
accent: "#36563d",
|
||||
},
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
<AigcTopBar :points="pointBalance" @back="handleBack" @history="handleHistory" @recharge="handleRecharge" />
|
||||
|
||||
<view class="aigc-home-browser">
|
||||
<GiftPointsToast
|
||||
:visible="giftToastVisible"
|
||||
@close="handleCloseGiftToast"
|
||||
/>
|
||||
<GiftPointsToast :visible="giftToastVisible" @close="handleCloseGiftToast" />
|
||||
|
||||
<TemplateCarousel v-model="activeIndex" :templates="templates" @select="handleSelectTemplate" />
|
||||
|
||||
@@ -29,13 +26,6 @@ const giftToastVisible = ref(true);
|
||||
const templates = aigcTemplates;
|
||||
const steps = aigcFlowSteps;
|
||||
|
||||
const showPlaceholderToast = (title) => {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon: "none",
|
||||
});
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
@@ -44,11 +34,15 @@ const handleBack = () => {
|
||||
};
|
||||
|
||||
const handleHistory = () => {
|
||||
showPlaceholderToast("最近任务");
|
||||
uni.navigateTo({
|
||||
url: "/pages-aigc/record/record"
|
||||
});
|
||||
};
|
||||
|
||||
const handleRecharge = () => {
|
||||
showPlaceholderToast("积分充值");
|
||||
uni.navigateTo({
|
||||
url: "/pages-aigc/recharge/recharge"
|
||||
});
|
||||
};
|
||||
|
||||
const handleCloseGiftToast = () => {
|
||||
@@ -57,8 +51,7 @@ const handleCloseGiftToast = () => {
|
||||
|
||||
const handleSelectTemplate = (template) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages-aigc/useTemplate/useTemplate?templateId=${encodeURIComponent(template.id || "")}`,
|
||||
fail: () => showPlaceholderToast(template.title),
|
||||
url: `/pages-aigc/useTemplate/useTemplate?templateId=${encodeURIComponent(template.id || "")}`
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import glassBoatPostcard from "@/pages-aigc/home/assets/glass-boat-postcard.gif";
|
||||
|
||||
export const aigcRecords = [
|
||||
{
|
||||
id: "record-lake-postcard",
|
||||
title: "鸳鸯湖明信片",
|
||||
time: "2026-06-29 15:46:12",
|
||||
status: "图片版 · 已完成 · 可升级动效视频",
|
||||
image: glassBoatPostcard,
|
||||
image: "https://oss.nianxx.cn/mp/static/xiaoqi/glass-boat-postcard.gif",
|
||||
},
|
||||
{
|
||||
id: "record-lake-postcard-video",
|
||||
title: "鸳鸯湖明信片",
|
||||
time: "2026-06-29 15:58:40",
|
||||
status: "动效视频版 · 5秒内 · 由图片版升级",
|
||||
image: glassBoatPostcard,
|
||||
image: "https://oss.nianxx.cn/mp/static/xiaoqi/glass-boat-postcard.gif",
|
||||
},
|
||||
];
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 556 KiB After Width: | Height: | Size: 123 KiB |
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<uni-popup
|
||||
ref="popupRef"
|
||||
type="center"
|
||||
background-color="transparent"
|
||||
mask-background-color="rgba(0, 0, 0, 0.58)"
|
||||
:safe-area="false"
|
||||
:is-mask-click="false"
|
||||
@change="handlePopupChange"
|
||||
>
|
||||
<view
|
||||
v-if="visible"
|
||||
class="aigc-consent-overlay"
|
||||
@touchmove.stop.prevent
|
||||
>
|
||||
@@ -36,10 +44,11 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -61,6 +70,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:visible", "cancel", "agree", "ruleClick"]);
|
||||
const popupRef = ref(null);
|
||||
|
||||
const defaultContentPrefix = computed(
|
||||
() =>
|
||||
@@ -71,6 +81,7 @@ const defaultContentSuffix =
|
||||
",您点击“我同意”等进行下一步操作的行为均视为您同意受本规则约束。如您不同意,您可以直接关闭页面。";
|
||||
|
||||
const close = () => {
|
||||
popupRef.value?.close();
|
||||
emit("update:visible", false);
|
||||
};
|
||||
|
||||
@@ -87,6 +98,34 @@ const handleAgree = () => {
|
||||
const handleRuleClick = () => {
|
||||
emit("ruleClick");
|
||||
};
|
||||
|
||||
const syncPopupVisible = async (visible) => {
|
||||
await nextTick();
|
||||
if (visible) {
|
||||
popupRef.value?.open();
|
||||
} else {
|
||||
popupRef.value?.close();
|
||||
}
|
||||
};
|
||||
|
||||
const handlePopupChange = (event) => {
|
||||
const show = event?.show ?? event?.detail?.show;
|
||||
if (show === false && props.visible) {
|
||||
emit("update:visible", false);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
syncPopupVisible(visible);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
syncPopupVisible(props.visible);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
.aigc-consent-overlay {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
padding: 0 28px;
|
||||
background: rgba(0, 0, 0, 0.58);
|
||||
box-sizing: border-box;
|
||||
-webkit-backdrop-filter: blur(1px);
|
||||
backdrop-filter: blur(1px);
|
||||
}
|
||||
|
||||
.aigc-consent-dialog {
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
<view class="flex flex-col h-screen relative overflow-hidden">
|
||||
<!-- 顶部自定义导航栏 -->
|
||||
<view class="header absolute top-0 left-0 w-full z-10" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<ChatTopNavBar ref="topNavBarRef" :mainPageDataModel="mainPageDataModel" @showDrawer="handleShowDrawer" />
|
||||
<ChatTopNavBar ref="topNavBarRef" :mainPageDataModel="mainPageDataModel" :navHeight="navBarHeight"
|
||||
@showDrawer="handleShowDrawer" />
|
||||
</view>
|
||||
|
||||
<view class="home-hero relative flex-shrink-0">
|
||||
<image class="home-hero-bg w-full block" :src="mainPageDataModel?.initPageImages?.backgroundImageUrl"
|
||||
mode="aspectFill" />
|
||||
<!-- <SpriteAnimator
|
||||
v-if="homeSpriteStyle.ipLargeImage"
|
||||
class="home-hero-sprite"
|
||||
:src="homeSpriteStyle.ipLargeImage"
|
||||
:frameWidth="homeSpriteStyle.frameWidth"
|
||||
:frameHeight="homeSpriteStyle.frameHeight"
|
||||
:totalFrames="homeSpriteStyle.totalFrames"
|
||||
:columns="homeSpriteStyle.columns"
|
||||
:displayWidth="homeSpriteStyle.displayWidth"
|
||||
:fps="16"
|
||||
/> -->
|
||||
<SpriteAnimator v-if="homeSpriteStyle.ipLargeImage" class="home-hero-sprite" :src="homeSpriteStyle.ipLargeImage"
|
||||
:frameWidth="homeSpriteStyle.frameWidth" :frameHeight="homeSpriteStyle.frameHeight"
|
||||
:totalFrames="homeSpriteStyle.totalFrames" :columns="homeSpriteStyle.columns"
|
||||
:displayWidth="homeSpriteStyle.displayWidth" :fps="16" />
|
||||
<view class="home-tabs-layer absolute bottom-0 left-0 right-0 flex-full">
|
||||
<view style="margin-bottom: -1px;">
|
||||
<AiTabSwitch v-model="tabIndex" :list="tabList" @change="handleChange" />
|
||||
@@ -26,16 +20,16 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view v-show="tabIndex === 0" class="main-scroll-area flex flex-col flex-full overflow-hidden" scroll-y
|
||||
@scroll="handleDiscoveryScroll"
|
||||
@touchstart.capture="handleScrollAreaTouchStart" @touchstart="handleScrollAreaTouchStart"
|
||||
@touchmove="handleScrollAreaTouchMove">
|
||||
<view class="home-welcome-collapse" :class="{ 'is-collapsed': isHomeWelcomeCollapsed }">
|
||||
<view v-show="tabIndex === 0" class="discovery-shell flex-full" @touchstart.capture="handleDiscoveryPanelTouchStart"
|
||||
@touchstart="handleDiscoveryPanelTouchStart" @touchmove="handleDiscoveryPanelTouchMove">
|
||||
<view class="home-welcome-panel">
|
||||
<HomeWelcome class="flex-shrink-0" :mainPageDataModel="mainPageDataModel" />
|
||||
</view>
|
||||
|
||||
<Discovery />
|
||||
</scroll-view>
|
||||
<view class="discovery-panel" :class="{ 'is-collapsed': isDiscoveryPanelCollapsed }">
|
||||
<Discovery @content-scroll="handleDiscoveryContentScroll" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消息列表(可滚动区域) -->
|
||||
<scroll-view v-show="tabIndex === 1" class="main main-scroll-area flex-full overflow-hidden min-height-0" scroll-y
|
||||
@@ -215,6 +209,7 @@ const emit = defineEmits(["showDrawer"]);
|
||||
const appStore = useAppStore();
|
||||
/// 导航栏相关
|
||||
const statusBarHeight = ref(20);
|
||||
const navBarHeight = ref(44);
|
||||
/// 输入框组件引用
|
||||
const inputAreaRef = ref(null);
|
||||
const topNavBarRef = ref();
|
||||
@@ -230,12 +225,11 @@ const isKeyboardShow = ref(false);
|
||||
|
||||
///(控制滚动位置)
|
||||
const scrollTop = ref(99999);
|
||||
const discoveryScrollTop = ref(0);
|
||||
const lastDiscoveryScrollTop = ref(0);
|
||||
const isHomeWelcomeCollapsed = ref(false);
|
||||
const DISCOVERY_WELCOME_COLLAPSE_TOP = 12;
|
||||
const DISCOVERY_WELCOME_EXPAND_TOP = 4;
|
||||
const DISCOVERY_SCROLL_DELTA = 2;
|
||||
const discoveryInnerScrollTop = ref(0);
|
||||
const discoveryPanelTouchStartY = ref(0);
|
||||
const isDiscoveryPanelCollapsed = ref(false);
|
||||
const DISCOVERY_PANEL_EXPAND_TOP = 4;
|
||||
const DISCOVERY_PANEL_TOUCH_DELTA = 16;
|
||||
|
||||
/// 会话列表
|
||||
const chatMsgList = ref([]);
|
||||
@@ -302,6 +296,9 @@ const homeSpriteStyle = computed(() => {
|
||||
|
||||
const handleChange = (i) => {
|
||||
console.log("切换:", i);
|
||||
if (i === 0) {
|
||||
resetDiscoveryScrollState();
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowDrawer = () => {
|
||||
@@ -389,26 +386,45 @@ const handleScrollAreaTouchMove = () => {
|
||||
hideKeyboardByScroll();
|
||||
};
|
||||
|
||||
const resetDiscoveryScrollState = () => {
|
||||
discoveryInnerScrollTop.value = 0;
|
||||
discoveryPanelTouchStartY.value = 0;
|
||||
isDiscoveryPanelCollapsed.value = false;
|
||||
};
|
||||
|
||||
// 处理用户滚动事件
|
||||
const handleDiscoveryScroll = (e) => {
|
||||
if (Date.now() - lastScrollTouchAt < 1000) {
|
||||
hideKeyboardByScroll();
|
||||
}
|
||||
const getTouchPointY = (e) => {
|
||||
const touch = e?.touches?.[0] || e?.changedTouches?.[0];
|
||||
return Number(touch?.clientY ?? touch?.pageY ?? 0);
|
||||
};
|
||||
|
||||
const currentScrollTop = Number(e.detail?.scrollTop || 0);
|
||||
const scrollDelta = currentScrollTop - lastDiscoveryScrollTop.value;
|
||||
discoveryScrollTop.value = currentScrollTop;
|
||||
const handleDiscoveryPanelTouchStart = (e) => {
|
||||
handleScrollAreaTouchStart();
|
||||
discoveryPanelTouchStartY.value = getTouchPointY(e);
|
||||
};
|
||||
|
||||
if (currentScrollTop <= DISCOVERY_WELCOME_EXPAND_TOP) {
|
||||
isHomeWelcomeCollapsed.value = false;
|
||||
const handleDiscoveryPanelTouchMove = (e) => {
|
||||
handleScrollAreaTouchMove();
|
||||
|
||||
const currentY = getTouchPointY(e);
|
||||
if (!currentY || !discoveryPanelTouchStartY.value) return;
|
||||
|
||||
const touchDelta = currentY - discoveryPanelTouchStartY.value;
|
||||
if (touchDelta < -DISCOVERY_PANEL_TOUCH_DELTA) {
|
||||
isDiscoveryPanelCollapsed.value = true;
|
||||
} else if (
|
||||
currentScrollTop > DISCOVERY_WELCOME_COLLAPSE_TOP ||
|
||||
scrollDelta > DISCOVERY_SCROLL_DELTA
|
||||
touchDelta > DISCOVERY_PANEL_TOUCH_DELTA &&
|
||||
discoveryInnerScrollTop.value <= DISCOVERY_PANEL_EXPAND_TOP
|
||||
) {
|
||||
isHomeWelcomeCollapsed.value = true;
|
||||
isDiscoveryPanelCollapsed.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
lastDiscoveryScrollTop.value = currentScrollTop;
|
||||
const handleDiscoveryContentScroll = ({ scrollTop }) => {
|
||||
discoveryInnerScrollTop.value = Number(scrollTop || 0);
|
||||
if (discoveryInnerScrollTop.value > DISCOVERY_PANEL_TOUCH_DELTA) {
|
||||
isDiscoveryPanelCollapsed.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const welcomeHeight = ref(0);
|
||||
@@ -537,11 +553,19 @@ const addNoticeListener = () => {
|
||||
|
||||
/// =============生命周期函数↓================
|
||||
onLoad(() => {
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
statusBarHeight.value = res.statusBarHeight || 20;
|
||||
},
|
||||
});
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const systemStatusBarHeight = systemInfo.statusBarHeight || 20;
|
||||
statusBarHeight.value = systemStatusBarHeight;
|
||||
navBarHeight.value = systemInfo.platform === "ios" ? 44 : 48;
|
||||
|
||||
if (typeof uni.getMenuButtonBoundingClientRect === "function") {
|
||||
const menuButton = uni.getMenuButtonBoundingClientRect();
|
||||
|
||||
if (menuButton?.height && typeof menuButton.top === "number") {
|
||||
statusBarHeight.value = Math.max(menuButton.top, systemStatusBarHeight);
|
||||
navBarHeight.value = menuButton.height;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onReady(() => {
|
||||
|
||||
@@ -5,23 +5,41 @@
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
.home-welcome-collapse {
|
||||
width: 100%;
|
||||
height: 92px;
|
||||
flex-shrink: 0;
|
||||
.discovery-shell {
|
||||
position: relative;
|
||||
flex-basis: 0;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition:
|
||||
height 240ms ease,
|
||||
opacity 240ms ease,
|
||||
transform 240ms ease;
|
||||
background: $theme-color-50;
|
||||
}
|
||||
|
||||
.home-welcome-collapse.is-collapsed {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
.home-welcome-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 92px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.discovery-panel {
|
||||
position: absolute;
|
||||
top: 92px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: $theme-color-50;
|
||||
transition: top 240ms ease;
|
||||
}
|
||||
|
||||
.discovery-panel.is-collapsed {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.home-hero {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="quick-access flex flex-row ml-12 pt-8 pb-8 scroll-x whitespace-nowrap">
|
||||
<view class="item border-box rounded-50 flex flex-row items-center" v-for="(item, index) in itemList" :key="index"
|
||||
@click="sendReply(item)">
|
||||
@@ -10,13 +11,24 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<AigcConsentDialog
|
||||
v-model:visible="aigcConsentVisible"
|
||||
@agree="handleAigcConsentAgree"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { Command } from "@/model/ChatModel";
|
||||
import { SEND_MESSAGE_COMMAND_TYPE } from "@/constant/constant";
|
||||
import { getAigcConsentAgreed, setAigcConsentAgreed } from "@/constant/aigc";
|
||||
import { checkToken } from "@/hooks/useGoLogin";
|
||||
import AigcConsentDialog from "../AigcConsentDialog/index.vue";
|
||||
|
||||
const AIGC_HOME_URL = "/pages-aigc/home/home";
|
||||
const aigcConsentVisible = ref(false);
|
||||
|
||||
const itemList = ref([
|
||||
{
|
||||
@@ -26,8 +38,8 @@ const itemList = ref([
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
title: "我的订单",
|
||||
type: Command.myOrder,
|
||||
title: "旅行记录",
|
||||
type: Command.travelAIGC,
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
@@ -64,8 +76,29 @@ const sendReply = (item) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 旅行AIGC
|
||||
if (item.type === Command.travelAIGC) {
|
||||
checkToken().then(() => {
|
||||
if (getAigcConsentAgreed()) {
|
||||
navigateToAigcHome();
|
||||
} else {
|
||||
aigcConsentVisible.value = true;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.$emit(SEND_MESSAGE_COMMAND_TYPE, item);
|
||||
};
|
||||
|
||||
const navigateToAigcHome = () => {
|
||||
uni.navigateTo({ url: AIGC_HOME_URL });
|
||||
};
|
||||
|
||||
const handleAigcConsentAgree = () => {
|
||||
setAigcConsentAgreed(true);
|
||||
navigateToAigcHome();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="border-box h-44 flex flex-items-center pl-12 pr-12">
|
||||
<view class="border-box h-44 flex flex-items-center pl-12 pr-12" :style="navBarStyle">
|
||||
<view class="nav-icon-button" @tap.stop="showDrawer">
|
||||
<uni-icons type="bars" size="24" color="#ffffff" />
|
||||
</view>
|
||||
@@ -29,6 +29,10 @@ import { getCurrentConfig } from "@/constant/base";
|
||||
import SpriteAnimator from "@/components/Sprite/SpriteAnimator.vue";
|
||||
|
||||
const props = defineProps({
|
||||
navHeight: {
|
||||
type: Number,
|
||||
default: 44,
|
||||
},
|
||||
mainPageDataModel: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
@@ -60,6 +64,10 @@ const emit = defineEmits(["showDrawer"]);
|
||||
|
||||
const showDrawer = () => emit("showDrawer");
|
||||
|
||||
const navBarStyle = computed(() => ({
|
||||
height: `${props.navHeight}px`,
|
||||
}));
|
||||
|
||||
defineExpose({ show });
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
<template>
|
||||
<view class="heritage-section">
|
||||
<view class="heritage-carousel">
|
||||
<scroll-view
|
||||
class="heritage-scroll"
|
||||
scroll-x
|
||||
show-scrollbar="false"
|
||||
:scroll-left="carouselScrollLeft"
|
||||
>
|
||||
<view class="heritage-track">
|
||||
<view
|
||||
v-for="(item, index) in normalizedCards"
|
||||
:key="getItemKey(item, index)"
|
||||
class="heritage-card"
|
||||
:class="{
|
||||
'is-featured': index === 1,
|
||||
'is-selected': index === 1,
|
||||
}"
|
||||
hover-class="is-pressed"
|
||||
hover-stay-time="80"
|
||||
@tap="handleCardTap(item.raw, index)"
|
||||
>
|
||||
<view class="heritage-image">
|
||||
<image
|
||||
v-if="item.coverImage"
|
||||
class="heritage-cover"
|
||||
:src="item.coverImage"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text class="heritage-badge">{{ item.badge }}</text>
|
||||
</view>
|
||||
|
||||
<view class="heritage-copy">
|
||||
<text class="heritage-tag ellipsis-1">{{ item.tag }}</text>
|
||||
<text class="heritage-title ellipsis-1">{{ item.title }}</text>
|
||||
<text class="heritage-desc ellipsis-1">{{ item.subTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<HeritageQuickPrompts
|
||||
:list="prompts"
|
||||
@didSelectItem="handlePromptTap"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import HeritageQuickPrompts from "../HeritageQuickPrompts/index.vue";
|
||||
|
||||
const props = defineProps({
|
||||
cards: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
prompts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["select-card", "select-prompt"]);
|
||||
|
||||
const carouselScrollLeft = ref(165);
|
||||
|
||||
watch(
|
||||
() => props.cards.length,
|
||||
(length) => {
|
||||
const targetScrollLeft = length > 1 ? 165 : 0;
|
||||
carouselScrollLeft.value = 0;
|
||||
setTimeout(() => {
|
||||
carouselScrollLeft.value = targetScrollLeft;
|
||||
}, 0);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const getBadgeText = (item) => {
|
||||
const source = item.jumpDesc || item.tag || item.title || "";
|
||||
return source.slice(0, 2) || "推荐";
|
||||
};
|
||||
|
||||
const normalizedCards = computed(() =>
|
||||
props.cards.map((item) => ({
|
||||
raw: item,
|
||||
id: item.id ?? item.tabContentId,
|
||||
title: item.title || "",
|
||||
subTitle: item.subTitle || "",
|
||||
tag: item.tag || item.jumpDesc || "推荐",
|
||||
badge: getBadgeText(item),
|
||||
coverImage: item.coverImage || "",
|
||||
}))
|
||||
);
|
||||
|
||||
const getItemKey = (item, index) => item.id ?? item.title ?? index;
|
||||
|
||||
const handleCardTap = (item, index) => {
|
||||
emit("select-card", item, index);
|
||||
};
|
||||
|
||||
const handlePromptTap = (item, index) => {
|
||||
emit("select-prompt", item, index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
@@ -1,137 +0,0 @@
|
||||
.heritage-section {
|
||||
width: 100%;
|
||||
height: 386px;
|
||||
overflow: hidden;
|
||||
background: $theme-color-50;
|
||||
}
|
||||
|
||||
.heritage-carousel,
|
||||
.heritage-scroll {
|
||||
width: 100%;
|
||||
height: 268px;
|
||||
overflow: hidden;
|
||||
background: $theme-color-50;
|
||||
}
|
||||
|
||||
.heritage-track {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
min-width: 100%;
|
||||
height: 268px;
|
||||
padding: 8px 69px 26px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.heritage-card {
|
||||
flex: 0 0 189px;
|
||||
width: 189px;
|
||||
height: 227px;
|
||||
margin-top: 5px;
|
||||
margin-right: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
border-radius: 28px;
|
||||
border: 1px solid #f1f5f9;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.heritage-card:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.heritage-card.is-featured {
|
||||
flex-basis: 237px;
|
||||
width: 237px;
|
||||
height: 234px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.heritage-card.is-selected {
|
||||
border-color: rgba(12, 205, 88, 0.55);
|
||||
box-shadow: 0 8px 18px rgba(12, 205, 88, 0.12);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.heritage-card.is-pressed {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.heritage-image {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 138px;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
.heritage-card.is-featured .heritage-image {
|
||||
height: 145px;
|
||||
}
|
||||
|
||||
.heritage-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.heritage-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 34px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
color: $theme-color-500;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.heritage-copy {
|
||||
min-width: 0;
|
||||
margin-top: 8px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.heritage-tag {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
height: 18px;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
border-radius: 4px;
|
||||
background: #fffbeb;
|
||||
color: #d97706;
|
||||
font-size: 9px;
|
||||
line-height: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.heritage-title {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin: 4px 0;
|
||||
color: #1e293b;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.heritage-desc {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
color: #94a3b8;
|
||||
font-size: 10px;
|
||||
line-height: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -4,17 +4,18 @@
|
||||
<FindTabs v-if="discoveryTabs.length > 0" v-model="activeIndex" :tabs="discoveryTabs" @change="handleTabChange" />
|
||||
</view>
|
||||
|
||||
<HeritageDiscoverySection :cards="discoveryCards" :prompts="discoveryQuickQuestions" @select-card="handleCardClick"
|
||||
@select-prompt="handleQuickQuestionClick" />
|
||||
|
||||
<scroll-view class="discovery-scroll" scroll-y @scroll="handleContentScroll">
|
||||
<view class="discovery-body">
|
||||
<ExploreCards v-if="discoveryCards.length > 0" :list="discoveryCards" @didSelectItem="handleCardClick" />
|
||||
|
||||
<QuickQuestions v-if="discoveryQuickQuestions.length > 0" :list="discoveryQuickQuestions"
|
||||
@didSelectItem="handleQuickQuestionClick" />
|
||||
|
||||
<DiscoveryPhotoToolCard @didSelectItem="handleToolCardClick" @didSelectOption="handleToolOptionClick" />
|
||||
<!-- <DiscoveryPhotoToolCard @didSelectItem="handleToolCardClick" @didSelectOption="handleToolOptionClick" />
|
||||
<DiscoveryAudioToolCard @didSelectItem="handleToolCardClick" />
|
||||
<DiscoveryGuideMapToolCard @didSelectItem="handleToolCardClick" />
|
||||
<DiscoveryGuideMapToolCard @didSelectItem="handleToolCardClick" /> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +27,6 @@ import { getAccessToken } from "@/constant/token";
|
||||
|
||||
import FindTabs from "./components/FindTabs/index.vue";
|
||||
import ExploreCards from "./components/ExploreCards/index.vue";
|
||||
import HeritageDiscoverySection from "./components/HeritageDiscoverySection/index.vue";
|
||||
import QuickQuestions from "./components/QuickQuestions/index.vue";
|
||||
import DiscoveryPhotoToolCard from "./components/DiscoveryPhotoToolCard/index.vue";
|
||||
import DiscoveryAudioToolCard from "./components/DiscoveryAudioToolCard/index.vue";
|
||||
@@ -45,7 +45,13 @@ const activeIndex = ref(0);
|
||||
const discoveryTabs = ref([]);
|
||||
const discoveryCards = ref([]);
|
||||
const discoveryQuickQuestions = ref([]);
|
||||
const emit = defineEmits(["scroll-touch-start", "scroll-touch"]);
|
||||
const emit = defineEmits(["content-scroll"]);
|
||||
|
||||
const handleContentScroll = (e) => {
|
||||
emit("content-scroll", {
|
||||
scrollTop: Number(e.detail?.scrollTop || 0),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/// tabs 切换事件
|
||||
@@ -208,16 +214,30 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.discovery-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
background: $theme-color-50;
|
||||
}
|
||||
|
||||
.discovery-tabs-sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
flex-shrink: 0;
|
||||
z-index: 12;
|
||||
width: 100%;
|
||||
background: $theme-color-50;
|
||||
}
|
||||
|
||||
.discovery-scroll {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.discovery-body {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user