feat: add discovery tool cards, refactor AiTabSwitch and update client configs
- Update xiaoqi client configuration: adjust clientId, sync appid across config files and switch to production build by setting developVersion to false - Refactor AiTabSwitch component to use background images instead of inline image tags, clean up redundant SCSS styles and replace tab icon assets - Add three new Discovery page tool components: audio guide, map guide and photo generator cards - Clean up unused code in Discovery page and ChatMainList component, remove unused scroll view and computed property - Fix regex pattern and formatting issues in update-appid.js script - Comment out unused SpriteAnimator component in ChatMainList
@@ -34,7 +34,7 @@
|
||||
}
|
||||
},
|
||||
"xiaoqi": {
|
||||
"clientId": "9",
|
||||
"clientId": "6",
|
||||
"appId": "wx8531ca7339685eca",
|
||||
"name": "小七",
|
||||
"theme": {
|
||||
|
||||
@@ -102,7 +102,7 @@ function updateBaseJs(clientName) {
|
||||
content = content.replace(regex, newExport);
|
||||
fs.writeFileSync(basePath, content, "utf8");
|
||||
console.log(
|
||||
`✅ base.js 更新成功,切换到 ${CLIENT_CONFIGS[clientName].name} 配置`
|
||||
`✅ base.js 更新成功,切换到 ${CLIENT_CONFIGS[clientName].name} 配置`,
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
@@ -125,12 +125,10 @@ function updateManifestJson(newAppId) {
|
||||
|
||||
// 由于manifest.json包含注释,需要特殊处理
|
||||
// 使用正则表达式替换mp-weixin中的appid
|
||||
const regex = /"mp-weixin":\s*{[^}]*"appid":\s*"[^"]*"/;
|
||||
const regex = /("mp-weixin"\s*:\s*\{[\s\S]*?"appid"\s*:\s*")[^"]*(")/;
|
||||
|
||||
if (regex.test(content)) {
|
||||
content = content.replace(regex, (match) => {
|
||||
return match.replace(/"appid":\s*"[^"]*"/, `"appid": "${newAppId}"`);
|
||||
});
|
||||
content = content.replace(regex, `$1${newAppId}$2`);
|
||||
fs.writeFileSync(manifestPath, content, "utf8");
|
||||
console.log("✅ manifest.json 更新成功");
|
||||
} else {
|
||||
@@ -158,7 +156,7 @@ function updateProjectConfigJson(newAppId) {
|
||||
fs.writeFileSync(
|
||||
projectConfigPath,
|
||||
JSON.stringify(config, null, 2),
|
||||
"utf8"
|
||||
"utf8",
|
||||
);
|
||||
console.log("✅ project.config.json 更新成功");
|
||||
console.log(` 旧appid: ${oldAppId}`);
|
||||
@@ -210,11 +208,11 @@ function main() {
|
||||
clientName = input;
|
||||
newAppId = CLIENT_CONFIGS[input].appId;
|
||||
console.log(
|
||||
`🎯 检测到客户端配置: ${CLIENT_CONFIGS[input].name} (${clientName})`
|
||||
`🎯 检测到客户端配置: ${CLIENT_CONFIGS[input].name} (${clientName})`,
|
||||
);
|
||||
|
||||
console.log(
|
||||
`目标客户端: ${CLIENT_CONFIGS[clientName].name} (${clientName})`
|
||||
`目标客户端: ${CLIENT_CONFIGS[clientName].name} (${clientName})`,
|
||||
);
|
||||
console.log(`目标appid: ${newAppId}\n`);
|
||||
|
||||
@@ -233,7 +231,9 @@ function main() {
|
||||
console.log("\n🎉 所有文件更新完成!");
|
||||
console.log(`💡 已切换到 ${CLIENT_CONFIGS[clientName].name} 客户端配置`);
|
||||
console.log("💡 提示: 请检查文件内容确认更新正确");
|
||||
console.log("\n💡 版本升级: 请到request/api/config.js文件中更新versionValue版本号\n");
|
||||
console.log(
|
||||
"\n💡 版本升级: 请到request/api/config.js文件中更新versionValue版本号\n",
|
||||
);
|
||||
} else {
|
||||
console.log("\n❌ 部分文件更新失败,请检查错误信息");
|
||||
process.exit(1);
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 7.9 KiB |
@@ -1,51 +1,23 @@
|
||||
<template>
|
||||
<view class="ai-tab-wrapper">
|
||||
<view class="tab-container">
|
||||
<view
|
||||
class="tab-item is-left"
|
||||
:class="{ active: modelValue === 0 }"
|
||||
@tap="handleSwitch(0)"
|
||||
>
|
||||
<view class="tab-content">
|
||||
<image
|
||||
v-if="leftSelected || leftUnselected"
|
||||
:src="modelValue === 0 ? (leftSelected || leftUnselected) : (leftUnselected || leftSelected)"
|
||||
class="tab-image"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<view class="tab-label">
|
||||
<text class="tab-text">探索发现</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tab-item is-left" :class="{ active: modelValue === 0 }" :style="leftBackgroundStyle"
|
||||
@tap="handleSwitch(0)">
|
||||
<text class="tab-text">探索发现</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tab-item is-right"
|
||||
:class="{ active: modelValue === 1 }"
|
||||
@tap="handleSwitch(1)"
|
||||
>
|
||||
<view class="tab-content">
|
||||
<image
|
||||
v-if="rightSelected || rightUnselected"
|
||||
:src="modelValue === 1 ? (rightSelected || rightUnselected) : (rightUnselected || rightSelected)"
|
||||
class="tab-image"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<view class="tab-label">
|
||||
<text class="tab-text">AI伴游</text>
|
||||
<view
|
||||
v-if="showDot"
|
||||
:class="['status-dot', modelValue === 1 ? 'status-dot--active' : 'status-dot--inactive']"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tab-item is-right" :class="{ active: modelValue === 1 }" :style="rightBackgroundStyle"
|
||||
@tap="handleSwitch(1)">
|
||||
<text class="tab-text">AI伴游</text>
|
||||
<text v-if="showDot"
|
||||
:class="['status-dot', modelValue === 1 ? 'status-dot--active' : 'status-dot--inactive']" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { computed } from "vue";
|
||||
import leftSelected from "./images/L_02.png";
|
||||
import leftUnselected from "./images/L_01.png";
|
||||
import rightSelected from "./images/R_02.png";
|
||||
@@ -55,11 +27,30 @@ const props = defineProps({
|
||||
modelValue: { type: Number, default: 0 },
|
||||
showDot: { type: Boolean, default: true },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const handleSwitch = (i) => {
|
||||
emit("update:modelValue", i);
|
||||
emit("change", i);
|
||||
const getBackgroundStyle = (selectedImage, unselectedImage, active) => {
|
||||
const image = active
|
||||
? (selectedImage || unselectedImage)
|
||||
: (unselectedImage || selectedImage);
|
||||
|
||||
return {
|
||||
backgroundImage: image ? `url(${image})` : "none",
|
||||
};
|
||||
};
|
||||
|
||||
const leftBackgroundStyle = computed(() =>
|
||||
getBackgroundStyle(leftSelected, leftUnselected, props.modelValue === 0)
|
||||
);
|
||||
|
||||
const rightBackgroundStyle = computed(() =>
|
||||
getBackgroundStyle(rightSelected, rightUnselected, props.modelValue === 1)
|
||||
);
|
||||
|
||||
const handleSwitch = (index) => {
|
||||
emit("update:modelValue", index);
|
||||
emit("change", index);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,89 +7,47 @@
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
transition: background 0.3s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
.tab-image {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: block;
|
||||
vertical-align: bottom;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #2e312f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.is-left {
|
||||
margin-right: -24px;
|
||||
}
|
||||
|
||||
.is-left .tab-label {
|
||||
.is-left .tab-text {
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
.is-right {
|
||||
margin-left: -24px;
|
||||
}
|
||||
|
||||
.is-right .tab-label {
|
||||
.is-right .tab-text {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
margin-left: 26px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
z-index: 22;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"quickapp" : {},
|
||||
"mp-weixin" : {
|
||||
"appid" : "wx5e79df5996572539",
|
||||
"appid" : "wx8531ca7339685eca",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"minified" : true
|
||||
|
||||
@@ -2,20 +2,13 @@
|
||||
<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" @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
|
||||
<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"
|
||||
@@ -25,7 +18,7 @@
|
||||
: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" />
|
||||
@@ -33,114 +26,60 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-show="tabIndex === 0"
|
||||
class="main-scroll-area flex flex-col flex-full overflow-hidden min-height-0"
|
||||
@touchstart.capture="handleScrollAreaTouchStart"
|
||||
@touchstart="handleScrollAreaTouchStart"
|
||||
@touchmove="handleScrollAreaTouchMove"
|
||||
>
|
||||
<HomeWelcome
|
||||
class="flex-shrink-0"
|
||||
:mainPageDataModel="mainPageDataModel"
|
||||
/>
|
||||
<view class="flex-full min-height-0 overflow-hidden">
|
||||
<Discovery
|
||||
@scroll-touch-start="handleScrollAreaTouchStart"
|
||||
@scroll-touch="handleScrollAreaTouchMove"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view v-show="tabIndex === 0" class="main-scroll-area flex flex-col flex-full overflow-hidden" scroll-y
|
||||
@touchstart.capture="handleScrollAreaTouchStart" @touchstart="handleScrollAreaTouchStart"
|
||||
@touchmove="handleScrollAreaTouchMove">
|
||||
<HomeWelcome class="flex-shrink-0" :mainPageDataModel="mainPageDataModel" />
|
||||
|
||||
<Discovery />
|
||||
</scroll-view>
|
||||
|
||||
<!-- 消息列表(可滚动区域) -->
|
||||
<scroll-view
|
||||
v-show="tabIndex === 1"
|
||||
class="main main-scroll-area flex-full overflow-hidden min-height-0"
|
||||
scroll-y
|
||||
:scroll-top="scrollTop"
|
||||
:scroll-with-animation="true"
|
||||
@scroll="handleScroll"
|
||||
@scrolltolower="handleScrollToLower"
|
||||
@touchstart.capture="handleScrollAreaTouchStart"
|
||||
@touchstart="handleScrollAreaTouchStart"
|
||||
@touchmove="handleScrollAreaTouchMove"
|
||||
>
|
||||
|
||||
<view
|
||||
class="area-msg-list-content"
|
||||
v-for="item in chatMsgList"
|
||||
:key="item.msgId"
|
||||
:id="item.msgId"
|
||||
>
|
||||
<scroll-view v-show="tabIndex === 1" class="main main-scroll-area flex-full overflow-hidden min-height-0" scroll-y
|
||||
:scroll-top="scrollTop" :scroll-with-animation="true" @scroll="handleScroll" @scrolltolower="handleScrollToLower"
|
||||
@touchstart.capture="handleScrollAreaTouchStart" @touchstart="handleScrollAreaTouchStart"
|
||||
@touchmove="handleScrollAreaTouchMove">
|
||||
|
||||
<view class="area-msg-list-content" v-for="item in chatMsgList" :key="item.msgId" :id="item.msgId">
|
||||
<template v-if="item.msgType === MessageRole.AI">
|
||||
<ChatCardAI
|
||||
class="flex flex-justify-start"
|
||||
:key="`ai-${item.msgId}-${item.msg ? item.msg.length : 0}`"
|
||||
:text="
|
||||
item.componentName && isLongTextCard(item.componentName)
|
||||
? ''
|
||||
: item.msg || ''
|
||||
"
|
||||
:isLoading="item.isLoading"
|
||||
>
|
||||
<template
|
||||
#content
|
||||
v-if="
|
||||
item.toolCall ||
|
||||
(item.componentName && isLongTextCard(item.componentName))
|
||||
"
|
||||
>
|
||||
<AnswerComponent
|
||||
v-if="isLongTextCard(item.componentName) && !isLongTextCardPreView(item.componentName)"
|
||||
:content="item.componentMsg || ''"
|
||||
:longTextData="item.longTextData"
|
||||
:finish="item.finish"
|
||||
/>
|
||||
<ChatCardAI class="flex flex-justify-start" :key="`ai-${item.msgId}-${item.msg ? item.msg.length : 0}`" :text="item.componentName && isLongTextCard(item.componentName)
|
||||
? ''
|
||||
: item.msg || ''
|
||||
" :isLoading="item.isLoading">
|
||||
<template #content v-if="
|
||||
item.toolCall ||
|
||||
(item.componentName && isLongTextCard(item.componentName))
|
||||
">
|
||||
<AnswerComponent v-if="isLongTextCard(item.componentName) && !isLongTextCardPreView(item.componentName)"
|
||||
:content="item.componentMsg || ''" :longTextData="item.longTextData" :finish="item.finish" />
|
||||
|
||||
<LongTextGuideCardPreview
|
||||
v-if="item.toolCall && isLongTextCardPreView(item.toolCall.componentName)"
|
||||
:componentName="item.toolCall.componentName"
|
||||
/>
|
||||
<LongTextGuideCardPreview v-if="item.toolCall && isLongTextCardPreView(item.toolCall.componentName)"
|
||||
:componentName="item.toolCall.componentName" />
|
||||
|
||||
<QuickBookingComponent
|
||||
v-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.quickBookingCard
|
||||
"
|
||||
/>
|
||||
<DiscoveryCardComponent
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.discoveryCard
|
||||
"
|
||||
/>
|
||||
<CreateServiceOrder
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.callServiceCard
|
||||
"
|
||||
:toolCall="item.toolCall"
|
||||
/>
|
||||
<OpenMapComponent
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.mapCard
|
||||
"
|
||||
/>
|
||||
<GeneratorPhotoComponent
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.aigcPhotoGeneratorCard
|
||||
"
|
||||
:toolCall="item.toolCall"
|
||||
/>
|
||||
<AigcPhotoCard
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.videoCard
|
||||
"
|
||||
:toolCall="item.toolCall"
|
||||
/>
|
||||
<QuickBookingComponent v-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.quickBookingCard
|
||||
" />
|
||||
<DiscoveryCardComponent v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.discoveryCard
|
||||
" />
|
||||
<CreateServiceOrder v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.callServiceCard
|
||||
" :toolCall="item.toolCall" />
|
||||
<OpenMapComponent v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.mapCard
|
||||
" />
|
||||
<GeneratorPhotoComponent v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.aigcPhotoGeneratorCard
|
||||
" :toolCall="item.toolCall" />
|
||||
<AigcPhotoCard v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.videoCard
|
||||
" :toolCall="item.toolCall" />
|
||||
|
||||
<!-- <ZModuleC01
|
||||
v-else-if="
|
||||
@@ -148,45 +87,30 @@
|
||||
item.toolCall.componentName === CompName.aigcPhotoGeneratorCard
|
||||
"
|
||||
/> -->
|
||||
|
||||
<Feedback
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.feedbackCard
|
||||
"
|
||||
:toolCall="item.toolCall"
|
||||
/>
|
||||
<DetailCardCompontent
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName ===
|
||||
CompName.pictureAndCommodityCard
|
||||
"
|
||||
:toolCall="item.toolCall"
|
||||
/>
|
||||
<AddCarCrad
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.enterLicensePlateCard
|
||||
"
|
||||
:toolCall="item.toolCall"
|
||||
/>
|
||||
<SurveyQuestionnaire
|
||||
v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName ===
|
||||
CompName.callSurveyQuestionnaire
|
||||
"
|
||||
:toolCall="item.toolCall"
|
||||
/>
|
||||
|
||||
<Feedback v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.feedbackCard
|
||||
" :toolCall="item.toolCall" />
|
||||
<DetailCardCompontent v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName ===
|
||||
CompName.pictureAndCommodityCard
|
||||
" :toolCall="item.toolCall" />
|
||||
<AddCarCrad v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName === CompName.enterLicensePlateCard
|
||||
" :toolCall="item.toolCall" />
|
||||
<SurveyQuestionnaire v-else-if="
|
||||
item.toolCall &&
|
||||
item.toolCall.componentName ===
|
||||
CompName.callSurveyQuestionnaire
|
||||
" :toolCall="item.toolCall" />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<!-- 这个是底部 -->
|
||||
<AttachListComponent
|
||||
v-if="item.question"
|
||||
:question="item.question"
|
||||
/>
|
||||
<AttachListComponent v-if="item.question" :question="item.question" />
|
||||
</template>
|
||||
</ChatCardAI>
|
||||
</template>
|
||||
@@ -198,23 +122,17 @@
|
||||
<template v-else>
|
||||
<ChatCardOther class="flex flex-justify-center" :text="item.msg">
|
||||
<ChatGuide v-if="chatMsgList.length < 2" />
|
||||
<ActivityListComponent
|
||||
v-if="
|
||||
mainPageDataModel.activityList &&
|
||||
mainPageDataModel.activityList.length > 0
|
||||
"
|
||||
:activityList="mainPageDataModel.activityList"
|
||||
/>
|
||||
<ActivityListComponent v-if="
|
||||
mainPageDataModel.activityList &&
|
||||
mainPageDataModel.activityList.length > 0
|
||||
" :activityList="mainPageDataModel.activityList" />
|
||||
|
||||
<!-- 先不展示了,等后续有需求再加回来 false -->
|
||||
<RecommendPostsComponent
|
||||
v-if="
|
||||
false &&
|
||||
mainPageDataModel.recommendTheme &&
|
||||
mainPageDataModel.recommendTheme.length > 0
|
||||
"
|
||||
:recommendThemeList="mainPageDataModel.recommendTheme"
|
||||
/>
|
||||
<RecommendPostsComponent v-if="
|
||||
false &&
|
||||
mainPageDataModel.recommendTheme &&
|
||||
mainPageDataModel.recommendTheme.length > 0
|
||||
" :recommendThemeList="mainPageDataModel.recommendTheme" />
|
||||
</ChatCardOther>
|
||||
</template>
|
||||
</view>
|
||||
@@ -223,17 +141,9 @@
|
||||
<!-- 输入框区域 -->
|
||||
<view class="pb-safe-area">
|
||||
<ChatQuickAccess />
|
||||
<ChatInputArea
|
||||
ref="inputAreaRef"
|
||||
v-model="inputMessage"
|
||||
:holdKeyboard="holdKeyboard"
|
||||
:is-session-active="isSessionActive"
|
||||
:stop-request="stopRequest"
|
||||
@send="sendMessageAction"
|
||||
@noHideKeyboard="handleNoHideKeyboard"
|
||||
@keyboardShow="handleKeyboardShow"
|
||||
@keyboardHide="handleKeyboardHide"
|
||||
/>
|
||||
<ChatInputArea ref="inputAreaRef" v-model="inputMessage" :holdKeyboard="holdKeyboard"
|
||||
:is-session-active="isSessionActive" :stop-request="stopRequest" @send="sendMessageAction"
|
||||
@noHideKeyboard="handleNoHideKeyboard" @keyboardShow="handleKeyboardShow" @keyboardHide="handleKeyboardHide" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<view
|
||||
class="discovery-audio-tool-card"
|
||||
hover-class="is-pressed"
|
||||
hover-stay-time="80"
|
||||
@tap="handleSelect"
|
||||
>
|
||||
<view class="audio-visual">
|
||||
<image
|
||||
v-if="normalizedItem.coverImage"
|
||||
class="audio-cover"
|
||||
:src="normalizedItem.coverImage"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-else class="audio-cover audio-cover-fallback" />
|
||||
<view class="audio-play">
|
||||
<view class="audio-play-triangle" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="audio-content">
|
||||
<text class="audio-title ellipsis-1">{{ normalizedItem.title }}</text>
|
||||
<text class="audio-subtitle ellipsis-1">{{ normalizedItem.subTitle }}</text>
|
||||
|
||||
<view class="audio-progress-row">
|
||||
<view class="audio-progress-track">
|
||||
<view
|
||||
class="audio-progress-fill"
|
||||
:style="{ width: `${normalizedItem.progress}%` }"
|
||||
/>
|
||||
</view>
|
||||
<text class="audio-time ellipsis-1">
|
||||
{{ normalizedItem.currentTime }} / {{ normalizedItem.duration }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const DEFAULT_ITEM = {
|
||||
id: "audio-guide",
|
||||
title: "卧龙潭语音讲解",
|
||||
subTitle: "解说:响水洞瀑布",
|
||||
progress: 31,
|
||||
currentTime: "02:15",
|
||||
duration: "05:40",
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["didSelectItem"]);
|
||||
|
||||
const clampProgress = (value) => {
|
||||
const progress = Number(value);
|
||||
if (Number.isNaN(progress)) return 0;
|
||||
return Math.min(Math.max(progress, 0), 100);
|
||||
};
|
||||
|
||||
const normalizedItem = computed(() => {
|
||||
const source = Object.keys(props.item).length > 0 ? props.item : DEFAULT_ITEM;
|
||||
|
||||
return {
|
||||
raw: source,
|
||||
id: source.id ?? source.tabContentId ?? DEFAULT_ITEM.id,
|
||||
title: source.title || DEFAULT_ITEM.title,
|
||||
subTitle: source.subTitle || source.desc || DEFAULT_ITEM.subTitle,
|
||||
coverImage: source.coverImage || source.image || DEFAULT_ITEM.coverImage || "",
|
||||
progress: clampProgress(source.progress ?? DEFAULT_ITEM.progress),
|
||||
currentTime: source.currentTime || DEFAULT_ITEM.currentTime,
|
||||
duration: source.duration || DEFAULT_ITEM.duration,
|
||||
};
|
||||
});
|
||||
|
||||
const handleSelect = () => {
|
||||
emit("didSelectItem", normalizedItem.value.raw);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
.discovery-audio-tool-card {
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.discovery-audio-tool-card.is-pressed {
|
||||
opacity: 0.94;
|
||||
}
|
||||
|
||||
.audio-visual {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 0 0 88px;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
overflow: hidden;
|
||||
border-radius: 28px;
|
||||
background: #c8f4ab;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.audio-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 88px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 18px 12px 14px;
|
||||
border-radius: 28px;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.audio-title {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
color: #123737;
|
||||
font-size: 19px;
|
||||
line-height: 25px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.audio-subtitle {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin-top: 2px;
|
||||
color: #9aa4b4;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.audio-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.audio-cover-fallback {
|
||||
background:
|
||||
radial-gradient(circle at 58% 12%, rgba(255, 255, 255, 0.38) 0 14px, transparent 15px),
|
||||
linear-gradient(115deg, rgba(11, 41, 26, 0.12), rgba(14, 55, 36, 0.42)),
|
||||
linear-gradient(145deg, #578a3e 0%, #99bb67 36%, #eff7ff 37%, #cadbcb 44%, #1a6d58 45%, #2c7966 100%);
|
||||
}
|
||||
|
||||
.audio-play {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: rgba(24, 31, 43, 0.38);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.audio-play-triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-left: 4px;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-left: 15px solid #ffffff;
|
||||
}
|
||||
|
||||
.audio-progress-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.audio-progress-track {
|
||||
flex: 1 1 auto;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: #edf2f7;
|
||||
}
|
||||
|
||||
.audio-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, $theme-color-500 0%, #12b981 100%);
|
||||
}
|
||||
|
||||
.audio-time {
|
||||
flex: 0 0 90px;
|
||||
max-width: 90px;
|
||||
margin-left: 12px;
|
||||
color: #94a0b2;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<view
|
||||
class="discovery-guide-map-tool-card"
|
||||
hover-class="is-pressed"
|
||||
hover-stay-time="80"
|
||||
@tap="handleSelect"
|
||||
>
|
||||
<view class="guide-map-visual">
|
||||
<view class="guidepost-illustration">
|
||||
<view class="guidepost-sign is-pink">
|
||||
<view class="guidepost-line" />
|
||||
</view>
|
||||
<view class="guidepost-sign is-yellow">
|
||||
<view class="guidepost-line" />
|
||||
</view>
|
||||
<view class="guidepost-pole" />
|
||||
<view class="guidepost-base">
|
||||
<view class="guidepost-eye is-left" />
|
||||
<view class="guidepost-eye is-right" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="guide-map-content">
|
||||
<text class="guide-map-title ellipsis-1">{{ normalizedItem.title }}</text>
|
||||
<text class="guide-map-subtitle ellipsis-1">{{ normalizedItem.subTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const DEFAULT_ITEM = {
|
||||
id: "guide-map",
|
||||
title: "卧龙潭电子导览图",
|
||||
subTitle: "查看景区平面图与设施位置",
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["didSelectItem"]);
|
||||
|
||||
const normalizedItem = computed(() => {
|
||||
const source = Object.keys(props.item).length > 0 ? props.item : DEFAULT_ITEM;
|
||||
|
||||
return {
|
||||
raw: source,
|
||||
id: source.id ?? source.tabContentId ?? DEFAULT_ITEM.id,
|
||||
title: source.title || DEFAULT_ITEM.title,
|
||||
subTitle: source.subTitle || source.desc || DEFAULT_ITEM.subTitle,
|
||||
};
|
||||
});
|
||||
|
||||
const handleSelect = () => {
|
||||
emit("didSelectItem", normalizedItem.value.raw);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,189 @@
|
||||
.discovery-guide-map-tool-card {
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.discovery-guide-map-tool-card.is-pressed {
|
||||
opacity: 0.94;
|
||||
}
|
||||
|
||||
.guide-map-visual {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 0 0 88px;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
overflow: hidden;
|
||||
border-radius: 28px;
|
||||
background: #c8f4ab;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.guide-map-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 88px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 18px 12px 14px;
|
||||
border-radius: 28px;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.guide-map-title {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
color: #123737;
|
||||
font-size: 19px;
|
||||
line-height: 25px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.guide-map-subtitle {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin-top: 2px;
|
||||
color: #9aa4b4;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.guidepost-illustration {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 20px;
|
||||
width: 56px;
|
||||
height: 58px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.guidepost-sign {
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
width: 46px;
|
||||
height: 18px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.guidepost-sign::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -6px;
|
||||
top: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 6px 0 0 6px;
|
||||
transform: skewX(-26deg);
|
||||
}
|
||||
|
||||
.guidepost-sign.is-pink {
|
||||
top: 0;
|
||||
background: #f27591;
|
||||
}
|
||||
|
||||
.guidepost-sign.is-pink::before {
|
||||
background: #f27591;
|
||||
}
|
||||
|
||||
.guidepost-sign.is-yellow {
|
||||
top: 23px;
|
||||
left: 14px;
|
||||
background: #ffd65f;
|
||||
}
|
||||
|
||||
.guidepost-sign.is-yellow::before {
|
||||
background: #ffd65f;
|
||||
}
|
||||
|
||||
.guidepost-sign.is-yellow::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 0 7px 7px 0;
|
||||
background: #ffd65f;
|
||||
transform: skewX(-28deg);
|
||||
}
|
||||
|
||||
.guidepost-line {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
top: 7px;
|
||||
width: 17px;
|
||||
height: 3px;
|
||||
border-radius: 999px;
|
||||
background: rgba(210, 66, 91, 0.55);
|
||||
}
|
||||
|
||||
.guidepost-sign.is-yellow .guidepost-line {
|
||||
background: #ff9e05;
|
||||
}
|
||||
|
||||
.guidepost-pole {
|
||||
position: absolute;
|
||||
left: 27px;
|
||||
top: 39px;
|
||||
width: 12px;
|
||||
height: 16px;
|
||||
background: #b98969;
|
||||
}
|
||||
|
||||
.guidepost-base {
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
bottom: 0;
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
border-radius: 9px 9px 8px 8px;
|
||||
background: #66d9bf;
|
||||
}
|
||||
|
||||
.guidepost-base::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -5px;
|
||||
top: -2px;
|
||||
width: 18px;
|
||||
height: 13px;
|
||||
border-radius: 8px 0 8px 0;
|
||||
background: #66d9bf;
|
||||
transform: rotate(-14deg);
|
||||
}
|
||||
|
||||
.guidepost-eye {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #463b36;
|
||||
}
|
||||
|
||||
.guidepost-eye::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
top: 7px;
|
||||
width: 6px;
|
||||
height: 3px;
|
||||
border-radius: 999px;
|
||||
background: #ff9ab0;
|
||||
}
|
||||
|
||||
.guidepost-eye.is-left {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
.guidepost-eye.is-right {
|
||||
right: 8px;
|
||||
}
|
||||
116
src/pages/Discovery/components/DiscoveryPhotoToolCard/index.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<view
|
||||
class="discovery-photo-tool-card"
|
||||
hover-class="is-pressed"
|
||||
hover-stay-time="80"
|
||||
@tap="handleSelect"
|
||||
>
|
||||
<view class="photo-visual">
|
||||
<view class="camera-illustration">
|
||||
<view class="camera-top" />
|
||||
<view class="camera-flash" />
|
||||
<view class="camera-body">
|
||||
<view class="camera-lens">
|
||||
<view class="camera-lens-inner" />
|
||||
</view>
|
||||
<view class="camera-dot is-large" />
|
||||
<view class="camera-dot is-small" />
|
||||
</view>
|
||||
</view>
|
||||
<text class="photo-badge ellipsis-1">{{ normalizedItem.badge }}</text>
|
||||
</view>
|
||||
|
||||
<view class="photo-content">
|
||||
<text class="photo-title ellipsis-1">{{ normalizedItem.title }}</text>
|
||||
<text class="photo-subtitle ellipsis-1">{{ normalizedItem.subTitle }}</text>
|
||||
|
||||
<view class="photo-options">
|
||||
<view
|
||||
v-for="(option, index) in normalizedItem.options"
|
||||
:key="getOptionKey(option, index)"
|
||||
class="photo-option"
|
||||
hover-class="is-option-pressed"
|
||||
hover-stay-time="80"
|
||||
@tap.stop="handleOptionSelect(option, index)"
|
||||
>
|
||||
<view class="photo-option-thumb">
|
||||
<image
|
||||
v-if="option.coverImage"
|
||||
class="photo-option-image"
|
||||
:src="option.coverImage"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view
|
||||
v-else
|
||||
class="photo-option-placeholder"
|
||||
:class="`is-${index}`"
|
||||
/>
|
||||
</view>
|
||||
<text class="photo-option-label ellipsis-1">{{ option.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const DEFAULT_OPTIONS = [
|
||||
{ id: "check-in", label: "景点打卡" },
|
||||
{ id: "companion", label: "小七陪伴" },
|
||||
{ id: "memory", label: "风格纪念" },
|
||||
];
|
||||
|
||||
const DEFAULT_ITEM = {
|
||||
id: "travel-photo",
|
||||
title: "在卧龙潭和小七合影",
|
||||
subTitle: "生成你在卧龙潭的专属纪念照",
|
||||
badge: "旅行纪念照",
|
||||
options: DEFAULT_OPTIONS,
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["didSelectItem", "didSelectOption"]);
|
||||
|
||||
const normalizeOption = (option, index) => ({
|
||||
raw: option,
|
||||
id: option.id ?? option.tabContentId ?? index,
|
||||
label: option.label || option.title || DEFAULT_OPTIONS[index]?.label || "",
|
||||
coverImage: option.coverImage || option.image || option.url || "",
|
||||
});
|
||||
|
||||
const normalizedItem = computed(() => {
|
||||
const source = Object.keys(props.item).length > 0 ? props.item : DEFAULT_ITEM;
|
||||
const options = source.options || source.children || source.items || DEFAULT_OPTIONS;
|
||||
|
||||
return {
|
||||
raw: source,
|
||||
id: source.id ?? source.tabContentId ?? DEFAULT_ITEM.id,
|
||||
title: source.title || DEFAULT_ITEM.title,
|
||||
subTitle: source.subTitle || source.desc || DEFAULT_ITEM.subTitle,
|
||||
badge: source.badge || source.tag || DEFAULT_ITEM.badge,
|
||||
options: options.slice(0, 3).map(normalizeOption),
|
||||
};
|
||||
});
|
||||
|
||||
const getOptionKey = (option, index) => option.id ?? option.label ?? index;
|
||||
|
||||
const handleSelect = () => {
|
||||
emit("didSelectItem", normalizedItem.value.raw);
|
||||
};
|
||||
|
||||
const handleOptionSelect = (option, index) => {
|
||||
emit("didSelectOption", option.raw, normalizedItem.value.raw, index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,259 @@
|
||||
.discovery-photo-tool-card {
|
||||
width: 100%;
|
||||
height: 142px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.discovery-photo-tool-card.is-pressed {
|
||||
opacity: 0.94;
|
||||
}
|
||||
|
||||
.photo-visual {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 0 0 88px;
|
||||
width: 88px;
|
||||
height: 142px;
|
||||
overflow: hidden;
|
||||
border-radius: 28px;
|
||||
background: #c8f4ab;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.photo-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 142px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px 12px 12px;
|
||||
border-radius: 28px;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.photo-title {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
color: #142034;
|
||||
font-size: 19px;
|
||||
line-height: 25px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.photo-subtitle {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin-top: 2px;
|
||||
color: #9aa4b4;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.camera-illustration {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 34px;
|
||||
width: 66px;
|
||||
height: 58px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.camera-top {
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
top: 0;
|
||||
width: 28px;
|
||||
height: 30px;
|
||||
border-radius: 9px 9px 0 0;
|
||||
background: #5b6d84;
|
||||
}
|
||||
|
||||
.camera-top::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 9px;
|
||||
width: 18px;
|
||||
height: 12px;
|
||||
border-radius: 4px;
|
||||
background: #ffe97b;
|
||||
}
|
||||
|
||||
.camera-flash {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -10px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 9px;
|
||||
background: #ffd85c;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.camera-body {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 66px;
|
||||
height: 42px;
|
||||
border-radius: 12px;
|
||||
background: #526376;
|
||||
}
|
||||
|
||||
.camera-body::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 0;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 4px;
|
||||
background: #f2768d;
|
||||
}
|
||||
|
||||
.camera-lens {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 5px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #f8fbff;
|
||||
box-shadow: -6px 7px 0 rgba(35, 48, 67, 0.4);
|
||||
}
|
||||
|
||||
.camera-lens-inner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #6fd7ee;
|
||||
}
|
||||
|
||||
.camera-dot {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
border-radius: 50%;
|
||||
background: #7f91a5;
|
||||
}
|
||||
|
||||
.camera-dot.is-large {
|
||||
top: 7px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.camera-dot.is-small {
|
||||
top: 15px;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.photo-badge {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
right: 9px;
|
||||
bottom: 15px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #142034;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.photo-badge::before,
|
||||
.photo-badge::after {
|
||||
content: "";
|
||||
flex: 0 0 4px;
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
margin: 0 4px;
|
||||
border-top: 4px solid #142034;
|
||||
border-bottom: 4px solid #142034;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.photo-badge::before {
|
||||
border-left: 4px solid #142034;
|
||||
}
|
||||
|
||||
.photo-badge::after {
|
||||
border-right: 4px solid #142034;
|
||||
}
|
||||
|
||||
.photo-options {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.photo-option {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.photo-option.is-option-pressed {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.photo-option-thumb {
|
||||
width: 70px;
|
||||
height: 48px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #dff5ef;
|
||||
}
|
||||
|
||||
.photo-option-image,
|
||||
.photo-option-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.photo-option-placeholder {
|
||||
background:
|
||||
radial-gradient(circle at 28% 68%, rgba(255, 255, 255, 0.9) 0 5px, transparent 6px),
|
||||
linear-gradient(145deg, rgba(9, 83, 68, 0.75), rgba(14, 170, 112, 0.2)),
|
||||
linear-gradient(135deg, #0c5f58 0%, #4ecf9d 100%);
|
||||
}
|
||||
|
||||
.photo-option-placeholder.is-1 {
|
||||
background:
|
||||
radial-gradient(circle at 50% 24%, rgba(255, 255, 255, 0.82) 0 5px, transparent 6px),
|
||||
linear-gradient(180deg, rgba(6, 87, 73, 0.18), rgba(11, 91, 76, 0.7)),
|
||||
linear-gradient(135deg, #196d63 0%, #50c6b3 100%);
|
||||
}
|
||||
|
||||
.photo-option-placeholder.is-2 {
|
||||
background:
|
||||
radial-gradient(circle at 70% 48%, rgba(255, 217, 91, 0.8) 0 5px, transparent 6px),
|
||||
linear-gradient(140deg, rgba(14, 50, 37, 0.68), rgba(67, 149, 83, 0.3)),
|
||||
linear-gradient(135deg, #28583b 0%, #9ac978 100%);
|
||||
}
|
||||
|
||||
.photo-option-label {
|
||||
display: block;
|
||||
max-width: 76px;
|
||||
margin: 5px auto 0;
|
||||
color: $theme-color-500;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
@@ -1,43 +1,26 @@
|
||||
<template>
|
||||
<view class="flex flex-col h-full overflow-hidden min-height-0">
|
||||
<view class="flex-shrink-0">
|
||||
<FindTabs
|
||||
v-if="discoveryTabs.length > 0"
|
||||
v-model="activeIndex"
|
||||
:tabs="discoveryTabs"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
<FindTabs v-if="discoveryTabs.length > 0" v-model="activeIndex" :tabs="discoveryTabs" @change="handleTabChange" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="discovery-scroll flex-full border-box min-height-0"
|
||||
scroll-y
|
||||
show-scrollbar="false"
|
||||
@touchstart="emitScrollTouchStart"
|
||||
@touchmove="emitScrollTouch"
|
||||
@scroll="emitScrollTouch"
|
||||
>
|
||||
<HeritageDiscoverySection
|
||||
v-if="isHeritageTab"
|
||||
:cards="discoveryCards"
|
||||
:prompts="discoveryQuickQuestions"
|
||||
@select-card="handleCardClick"
|
||||
@select-prompt="handleQuickQuestionClick"
|
||||
/>
|
||||
<HeritageDiscoverySection :cards="discoveryCards" :prompts="discoveryQuickQuestions" @select-card="handleCardClick"
|
||||
@select-prompt="handleQuickQuestionClick" />
|
||||
|
||||
<template v-else>
|
||||
<ExploreCards v-if="discoveryCards.length > 0" :list="discoveryCards" @didSelectItem="handleCardClick" />
|
||||
<ExploreCards v-if="discoveryCards.length > 0" :list="discoveryCards" @didSelectItem="handleCardClick" />
|
||||
|
||||
<QuickQuestions v-if="discoveryQuickQuestions.length > 0" :list="discoveryQuickQuestions" @didSelectItem="handleQuickQuestionClick" />
|
||||
</template>
|
||||
<QuickQuestions v-if="discoveryQuickQuestions.length > 0" :list="discoveryQuickQuestions"
|
||||
@didSelectItem="handleQuickQuestionClick" />
|
||||
|
||||
</scroll-view>
|
||||
<DiscoveryPhotoToolCard @didSelectItem="handleToolCardClick" @didSelectOption="handleToolOptionClick" />
|
||||
<DiscoveryAudioToolCard @didSelectItem="handleToolCardClick" />
|
||||
<DiscoveryGuideMapToolCard @didSelectItem="handleToolCardClick" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { SEND_MESSAGE_CONTENT_TEXT, SEND_MESSAGE_COMMAND_TYPE, SWITCH_TO_COMPANION_TAB } from "@/constant/constant";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { SEND_MESSAGE_CONTENT_TEXT, SEND_MESSAGE_COMMAND_TYPE } from "@/constant/constant";
|
||||
import { navigateTo } from "@/router";
|
||||
import { getAccessToken } from "@/constant/token";
|
||||
|
||||
@@ -45,6 +28,9 @@ 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";
|
||||
import DiscoveryGuideMapToolCard from "./components/DiscoveryGuideMapToolCard/index.vue";
|
||||
|
||||
import { homeTabsData, getNearbyTags, homeTabContentData, homeQuickQuestionData } from "../../request/api/MainPageDataApi";
|
||||
import { useAppStore, useLocationStore } from "@/store";
|
||||
@@ -52,7 +38,7 @@ import { JumpType } from "../../model/ChatModel";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const locationStore = useLocationStore();
|
||||
/// 从个渠道获取如二维码
|
||||
/// 从个渠道获取如二维码
|
||||
const sceneId = appStore.sceneId || "";
|
||||
|
||||
const activeIndex = ref(0);
|
||||
@@ -61,15 +47,6 @@ const discoveryCards = ref([]);
|
||||
const discoveryQuickQuestions = ref([]);
|
||||
const emit = defineEmits(["scroll-touch-start", "scroll-touch"]);
|
||||
|
||||
const isHeritageTab = computed(() => discoveryTabs.value[activeIndex.value]?.label === "小七孔古桥");
|
||||
|
||||
const emitScrollTouchStart = () => {
|
||||
emit("scroll-touch-start");
|
||||
};
|
||||
|
||||
const emitScrollTouch = () => {
|
||||
emit("scroll-touch");
|
||||
};
|
||||
|
||||
/// tabs 切换事件
|
||||
const handleTabChange = ({ tab, idx }) => {
|
||||
@@ -131,7 +108,7 @@ const queryQuickQuestionData = async (tabId) => {
|
||||
}
|
||||
|
||||
/// 统一处理接口返回数据结构
|
||||
const configDataList = (data) => {
|
||||
const configDataList = (data) => {
|
||||
return data.map((item) => ({
|
||||
id: item.id,
|
||||
title: item.tabContent.mainTitle,
|
||||
@@ -155,6 +132,20 @@ const handleQuickQuestionClick = (item) => {
|
||||
handleClick(item);
|
||||
};
|
||||
|
||||
const handleToolCardClick = (item) => {
|
||||
if (item?.jumpType !== undefined) {
|
||||
handleClick(item);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToolOptionClick = (option, parentItem) => {
|
||||
if (option?.jumpType !== undefined) {
|
||||
handleClick(option);
|
||||
} else {
|
||||
handleToolCardClick(parentItem);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = async (item) => {
|
||||
console.log(`执行点击事件: ${item.jumpType},参数:${JSON.stringify(item.jumpContent)}`);
|
||||
/// 商品
|
||||
@@ -183,7 +174,7 @@ const handleClick = async (item) => {
|
||||
/// 获取位置信息
|
||||
const getLocation = () => {
|
||||
/// 已经有sceneId了,说明之前已经获取过位置信息了,就不需要再获取一次了
|
||||
if (sceneId) return;
|
||||
if (sceneId) return;
|
||||
uni.getLocation({
|
||||
type: 'wgs84',
|
||||
success: function (res) {
|
||||
@@ -215,11 +206,4 @@ onMounted(() => {
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.discovery-scroll {
|
||||
flex-basis: 0;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getServiceUrl } from "../api/GetServiceUrlApi";
|
||||
const versionValue = "1.1.3";
|
||||
|
||||
/// 是否是测试版本, 测试版本为true, 发布版本为false
|
||||
const developVersion = true;
|
||||
const developVersion = false;
|
||||
|
||||
// 获取服务地址
|
||||
const getEvnUrl = async () => {
|
||||
|
||||