14 Commits

16 changed files with 318 additions and 181 deletions

View File

@@ -7,13 +7,15 @@
"loginDesc": "您好,欢迎来到智念科技", "loginDesc": "您好,欢迎来到智念科技",
"logo": "https://oss.nianxx.cn/mp/static/version_101/login/dh_logo.png", "logo": "https://oss.nianxx.cn/mp/static/version_101/login/dh_logo.png",
"ipLargeImage": "https://oss.nianxx.cn/mp/static/version_101/zn/zn_large.png", "ipLargeImage": "https://oss.nianxx.cn/mp/static/version_101/zn/zn_large.png",
"ipLargeImageWidth": 395,
"ipLargeImageHeight": 335,
"ipLargeTotalFrames": 71,
"ipLargeColumns": 1,
"ipSmallImage": "https://oss.nianxx.cn/mp/static/version_101/dh/dh_small.png", "ipSmallImage": "https://oss.nianxx.cn/mp/static/version_101/dh/dh_small.png",
"ipLargeImageHeight": 19687, "ipSmallImageWidth": 128,
"ipSmallImageHeight": 3744, "ipSmallImageHeight": 128,
"ipLargeImageStep": 147, "ipSmallTotalFrames": 117,
"ipSmallImageStep": 117, "ipSmallColumns": 1
"ipLargeTime": 4,
"ipSmallTime": 4
}, },
"duohua": { "duohua": {
"clientId": "2", "clientId": "2",
@@ -23,13 +25,15 @@
"loginDesc": "您好,欢迎来到朵花温泉", "loginDesc": "您好,欢迎来到朵花温泉",
"logo": "https://oss.nianxx.cn/mp/static/version_101/login/dh_logo.png", "logo": "https://oss.nianxx.cn/mp/static/version_101/login/dh_logo.png",
"ipLargeImage": "https://oss.nianxx.cn/mp/static/version_101/dh/dh_large.png", "ipLargeImage": "https://oss.nianxx.cn/mp/static/version_101/dh/dh_large.png",
"ipLargeImageWidth": 263,
"ipLargeImageHeight": 210,
"ipLargeTotalFrames": 147,
"ipLargeColumns": 1,
"ipSmallImage": "https://oss.nianxx.cn/mp/static/version_101/dh/dh_small.png", "ipSmallImage": "https://oss.nianxx.cn/mp/static/version_101/dh/dh_small.png",
"ipLargeImageHeight": 19687, "ipSmallImageWidth": 128,
"ipSmallImageHeight": 3744, "ipSmallImageHeight": 128,
"ipLargeImageStep": 147, "ipSmallTotalFrames": 117,
"ipSmallImageStep": 117, "ipSmallColumns": 1
"ipLargeTime": 4,
"ipSmallTime": 4
}, },
"tianmu": { "tianmu": {
"clientId": "4", "clientId": "4",
@@ -39,12 +43,14 @@
"loginDesc": "您好,欢迎来到天沐温泉", "loginDesc": "您好,欢迎来到天沐温泉",
"logo": "https://oss.nianxx.cn/mp/static/version_101/login/tm_logo.png", "logo": "https://oss.nianxx.cn/mp/static/version_101/login/tm_logo.png",
"ipLargeImage": "https://oss.nianxx.cn/mp/static/version_101/tm/tm_large.png", "ipLargeImage": "https://oss.nianxx.cn/mp/static/version_101/tm/tm_large.png",
"ipLargeImageWidth": 395,
"ipLargeImageHeight": 335,
"ipLargeTotalFrames": 71,
"ipLargeColumns": 1,
"ipSmallImage": "https://oss.nianxx.cn/mp/static/version_101/tm/tm_small.png", "ipSmallImage": "https://oss.nianxx.cn/mp/static/version_101/tm/tm_small.png",
"ipLargeImageHeight": 9514, "ipSmallImageWidth": 128,
"ipSmallImageHeight": 4736, "ipSmallImageHeight": 128,
"ipLargeImageStep": 71, "ipSmallTotalFrames": 148,
"ipSmallImageStep": 148, "ipSmallColumns": 1
"ipLargeTime": 4,
"ipSmallTime": 6
} }
} }

View File

@@ -0,0 +1,112 @@
<template>
<view class="sprite-animator" :style="spriteStyle" />
</template>
<script setup lang="ts">
import { computed, ref, onMounted, onUnmounted } from 'vue'
const props = withDefaults(defineProps<{
src: string
frameWidth: number
frameHeight: number
totalFrames: number
columns: number
displayWidth: number
fps?: number
autoplay?: boolean
loop?: boolean
}>(), {
fps: 12,
autoplay: true,
loop: true,
})
/* JS 精确逐帧方案(使用 setInterval确保整数偏移、无插值 */
const currentFrame = ref(0)
let intervalId: number | null = null
const play = () => {
stop()
const safeFps = Math.max(1, Math.floor(props.fps || 12))
const durationPerFrame = 1000 / safeFps
// 强制整数像素偏移,使用 setInterval 在各运行时更可靠
const setter = (globalThis as any).setInterval || setInterval
intervalId = setter(() => {
const safeTotal = Math.max(1, Math.floor(props.totalFrames || 0))
if (currentFrame.value >= safeTotal - 1) {
if (props.loop) {
currentFrame.value = 0
} else {
stop()
return
}
} else {
currentFrame.value++
}
}, durationPerFrame) as unknown as number
}
const stop = () => {
if (intervalId) {
clearInterval(intervalId)
intervalId = null
}
}
onMounted(() => {
console.log('[SpriteAnimator] mounted', { autoplay: props.autoplay, fps: props.fps })
if (props.autoplay) play()
})
onUnmounted(() => stop())
const spriteStyle = computed(() => {
const {
src,
frameWidth: rawFrameWidth,
frameHeight: rawFrameHeight,
totalFrames: rawTotalFrames,
columns: rawColumns,
} = props
const frameWidth = Math.max(1, Math.floor(rawFrameWidth || 0))
const frameHeight = Math.max(1, Math.floor(rawFrameHeight || 0))
const totalFrames = Math.max(1, Math.floor(rawTotalFrames || 0))
let columns = Math.floor(Number(rawColumns) || 0)
if (!columns || columns <= 0) columns = Math.min(1, totalFrames)
columns = Math.min(columns, totalFrames)
const displayWidth = props.displayWidth || frameWidth
const rows = Math.ceil(totalFrames / columns)
const imageWidth = columns * frameWidth
const imageHeight = rows * frameHeight
const scale = displayWidth / frameWidth
const displayHeight = Math.round(frameHeight * scale)
const scaledImageWidth = Math.round(imageWidth * scale)
const scaledImageHeight = Math.round(imageHeight * scale)
if (currentFrame.value < 0) currentFrame.value = 0
if (currentFrame.value >= totalFrames) currentFrame.value = totalFrames - 1
const col = currentFrame.value % columns
const row = Math.floor(currentFrame.value / columns)
const offsetX = Math.round(col * frameWidth * scale)
const offsetY = Math.round(row * frameHeight * scale)
const styleStr = `width: ${Math.round(displayWidth)}px; height: ${displayHeight}px; background-image: url("${src}"); background-repeat: no-repeat; background-size: ${scaledImageWidth}px ${scaledImageHeight}px; background-position: -${offsetX}px -${offsetY}px;`
return styleStr as any
})
// 对外暴露
defineExpose({ play, stop, currentFrame })
</script>
<style scoped>
.sprite-animator {
display: block;
will-change: background-position;
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<view class="survey-questionnaire w-vw-24">
<view class="bg-white border-box border-ff overflow-hidden rounded-20">
<view class="border-box flex flex-items-center flex-justify-between bg-EEF8FF">
<text class="font-size-18 font-500 color-171717 text-left ml-12">
调查问卷
</text>
<image class="w-102 h-72" :src="surveyData.logoUrl" mode="widthFix" />
</view>
<image class="w-full" :src="surveyData.bannerUrl" mode="widthFix" />
<view class="h-44 m-12 rounded-50 bg-button color-white flex flex-items-center flex-justify-center"
@click="handleCall">
前往填写
</view>
</view>
</view>
</template>
<script setup>
import { getAccessToken } from "@/constant/token";
import { defineProps, computed } from "vue";
import { navigateTo } from "../../router";
const props = defineProps({
toolCall: {
type: Object,
default: {},
},
});
const surveyData = computed(() => {
if (props.toolCall?.data) {
return JSON.parse(props.toolCall?.data);
} else {
return {};
}
});
const handleCall = () => {
const token = getAccessToken();
navigateTo(surveyData.value.jumpUrl, { token: token });
};
</script>
<style lang="scss" scoped></style>

View File

@@ -21,6 +21,8 @@ export const CompName = {
pictureAndCommodityCard: "pictureAndCommodityCard", pictureAndCommodityCard: "pictureAndCommodityCard",
// 输入车牌卡片 // 输入车牌卡片
enterLicensePlateCard: "enterLicensePlateCard", enterLicensePlateCard: "enterLicensePlateCard",
// 调查问卷卡片
callSurveyQuestionnaire: "callSurveyQuestionnaire",
}; };
/// 发送的指令类型 /// 发送的指令类型

View File

@@ -23,6 +23,15 @@
"style": { "style": {
"navigationStyle": "custom" "navigationStyle": "custom"
} }
},
{
"path": "pages/webview/index",
"style": {
"navigationStyle": "custom",
"backgroundColor": "#FFFFFF",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black"
}
} }
], ],
"subPackages": [ "subPackages": [

View File

@@ -33,6 +33,9 @@
<AddCarCrad v-else-if=" <AddCarCrad v-else-if="
item.toolCall.componentName === CompName.enterLicensePlateCard item.toolCall.componentName === CompName.enterLicensePlateCard
" :toolCall="item.toolCall" /> " :toolCall="item.toolCall" />
<SurveyQuestionnaire v-else-if="
item.toolCall.componentName === CompName.callSurveyQuestionnaire
" :toolCall="item.toolCall" />
</template> </template>
<template #footer> <template #footer>
@@ -99,6 +102,7 @@ import DetailCardCompontent from "../../module/DetailCardCompontent/index.vue";
import CreateServiceOrder from "@/components/CreateServiceOrder/index.vue"; import CreateServiceOrder from "@/components/CreateServiceOrder/index.vue";
import Feedback from "@/components/Feedback/index.vue"; import Feedback from "@/components/Feedback/index.vue";
import AddCarCrad from "@/components/AddCarCrad/index.vue"; import AddCarCrad from "@/components/AddCarCrad/index.vue";
import SurveyQuestionnaire from "@/components/SurveyQuestionnaire/index.vue";
import { mainPageData } from "@/request/api/MainPageDataApi"; import { mainPageData } from "@/request/api/MainPageDataApi";
import { import {
conversationMsgList, conversationMsgList,
@@ -144,7 +148,7 @@ const mainPageDataModel = ref({});
// 会话进行中标志 // 会话进行中标志
const isSessionActive = ref(false); const isSessionActive = ref(false);
/// 指令 /// 指令
let commonType = ""; let messageCommonType = "";
// WebSocket 相关 // WebSocket 相关
let webSocketManager = null; let webSocketManager = null;
@@ -234,7 +238,7 @@ const handleReplyText = (text) => {
const handleReplyInstruct = async (item) => { const handleReplyInstruct = async (item) => {
await checkToken(); await checkToken();
commonType = item.type; messageCommonType = item.type;
// 重置消息状态准备接收新的AI回复 // 重置消息状态准备接收新的AI回复
resetMessageState(); resetMessageState();
sendMessage(item.title, true); sendMessage(item.title, true);
@@ -658,31 +662,31 @@ const sendWebSocketMessage = async (messageType, messageContent, options = {}) =
try { try {
const raw = webSocketManager.sendMessage(args); const raw = webSocketManager.sendMessage(args);
// 兼容可能返回同步布尔或 Promise 的实现 // 兼容可能返回同步布尔或 Promise 的实现
const result = await Promise.resolve(raw); const result = await Promise.resolve(raw);
if (result) { if (result) {
console.log(`WebSocket消息已发送 [类型:${messageType}]:`, args); console.log(`WebSocket消息已发送 [类型:${messageType}]:`, args);
return true; return true;
} }
// 若返回 false消息可能已经被 manager 入队并触发连接流程。 // 若返回 false消息可能已经被 manager 入队并触发连接流程。
// 在这种情况下避免立即当作失败处理,而是等待短暂时间以观察连接是否建立并由 manager 发送队列。 // 在这种情况下避免立即当作失败处理,而是等待短暂时间以观察连接是否建立并由 manager 发送队列。
console.warn('webSocketManager.sendMessage 返回 false等待连接或队列发送...', { attempt, args }); console.warn('webSocketManager.sendMessage 返回 false等待连接或队列发送...', { attempt, args });
const waitForConnectMs = typeof options.waitForConnectMs === 'number' ? options.waitForConnectMs : 5000; const waitForConnectMs = typeof options.waitForConnectMs === 'number' ? options.waitForConnectMs : 5000;
if (webSocketManager && typeof webSocketManager.isConnected === 'function' && !webSocketManager.isConnected()) { if (webSocketManager && typeof webSocketManager.isConnected === 'function' && !webSocketManager.isConnected()) {
const startTs = Date.now(); const startTs = Date.now();
while (Date.now() - startTs < waitForConnectMs) { while (Date.now() - startTs < waitForConnectMs) {
await sleep(200); await sleep(200);
if (webSocketManager.isConnected()) { if (webSocketManager.isConnected()) {
// 给 manager 一点时间处理队列并发送 // 给 manager 一点时间处理队列并发送
await sleep(150); await sleep(150);
console.log('检测到 manager 已连接,假定队列消息已发送', args); console.log('检测到 manager 已连接,假定队列消息已发送', args);
return true; return true;
}
} }
console.warn('等待 manager 建连超时,进入重试逻辑', { waitForConnectMs, args });
} else {
console.warn('sendMessage 返回 false 但 manager 看起来已连接或不可用,继续重试', { args });
} }
console.warn('等待 manager 建连超时,进入重试逻辑', { waitForConnectMs, args });
} else {
console.warn('sendMessage 返回 false 但 manager 看起来已连接或不可用,继续重试', { args });
}
} catch (error) { } catch (error) {
console.error('发送WebSocket消息异常:', error, args); console.error('发送WebSocket消息异常:', error, args);
} }
@@ -730,7 +734,7 @@ const sendChat = async (message, isInstruct = false) => {
} }
const messageType = isInstruct ? 1 : 0; const messageType = isInstruct ? 1 : 0;
const messageContent = isInstruct ? commonType : message; const messageContent = isInstruct ? messageCommonType : message;
// 生成 messageId 并保存到当前会话变量stopRequest 可能使用) // 生成 messageId 并保存到当前会话变量stopRequest 可能使用)
currentSessionMessageId = IdUtils.generateMessageId(); currentSessionMessageId = IdUtils.generateMessageId();
@@ -802,11 +806,11 @@ const stopRequest = async () => {
} }
if (chatMsgList.value[aiMsgIndex] && if (chatMsgList.value[aiMsgIndex] &&
chatMsgList.value[aiMsgIndex].msgType === MessageRole.AI) { chatMsgList.value[aiMsgIndex].msgType === MessageRole.AI) {
chatMsgList.value[aiMsgIndex].isLoading = false; chatMsgList.value[aiMsgIndex].isLoading = false;
if (chatMsgList.value[aiMsgIndex].msg && if (chatMsgList.value[aiMsgIndex].msg &&
chatMsgList.value[aiMsgIndex].msg.trim() && chatMsgList.value[aiMsgIndex].msg.trim() &&
!chatMsgList.value[aiMsgIndex].msg.startsWith("加载中")) { !chatMsgList.value[aiMsgIndex].msg.startsWith("加载中")) {
// 保留已显示内容 // 保留已显示内容
} else { } else {
chatMsgList.value[aiMsgIndex].msg = "请求已停止"; chatMsgList.value[aiMsgIndex].msg = "请求已停止";

View File

@@ -4,8 +4,11 @@
<!-- 隐藏 --> <!-- 隐藏 -->
<view class="flex-full h-full flex flex-items-center flex-justify-center"> <view class="flex-full h-full flex flex-items-center flex-justify-center">
<!-- ChatTopWelcome不在可视区显示并添加动画在可视区隐藏 --> <!-- ChatTopWelcome不在可视区显示并添加动画在可视区隐藏 -->
<view v-show="show" :class="['w-32 h-32', { 'image-animated': show }]" :style="getStyle"></view> <SpriteAnimator v-show="show" class="image-animated" :src="spriteStyle.ipSmallImage"
:frameWidth="spriteStyle.frameWidth" :frameHeight="spriteStyle.frameHeight"
:totalFrames="spriteStyle.totalFrames" :columns="spriteStyle.columns" :displayWidth="spriteStyle.displayWidth"
:fps="16" />
<text v-show="show" :class="[ <text v-show="show" :class="[
'font-size-14 font-500 color-171717 ml-10', 'font-size-14 font-500 color-171717 ml-10',
{ 'text-animated': show }, { 'text-animated': show },
@@ -21,6 +24,7 @@
<script setup> <script setup>
import { ref, defineProps, computed, defineExpose } from "vue"; import { ref, defineProps, computed, defineExpose } from "vue";
import { getCurrentConfig } from "@/constant/base"; import { getCurrentConfig } from "@/constant/base";
import SpriteAnimator from "@/components/Sprite/SpriteAnimator.vue";
const props = defineProps({ const props = defineProps({
mainPageDataModel: { mainPageDataModel: {
@@ -37,19 +41,17 @@ const initPageImages = computed(() => {
const show = ref(false); const show = ref(false);
const config = getCurrentConfig(); const config = getCurrentConfig();
const getStyle = computed(() => {
const spriteStyle = computed(() => {
const images = initPageImages.value; const images = initPageImages.value;
const style = { return {
"--ipSmallImageStep": images.ipSmallImageStep ?? config.ipSmallImageStep, ipSmallImage: images.ipSmallImage ?? config.ipSmallImage,
"--ipSmallImageHeight": images.ipSmallImageHeight ?? config.ipSmallImageHeight, frameWidth: images.ipSmallImageWidth ?? config.ipSmallImageWidth,
"--ipSmallTime": images.ipSmallTime ?? config.ipSmallTime, frameHeight: images.ipSmallImageHeight ?? config.ipSmallImageHeight,
backgroundImage: `url(${images.ipSmallImage ?? config.ipSmallImage})`, totalFrames: images.ipSmallTotalFrames ?? config.ipSmallTotalFrames,
backgroundRepeat: "no-repeat", columns: images.ipSmallColumns ?? config.ipSmallColumns,
backgroundSize: "32px auto", displayWidth: 32,
backgroundPosition: "0 0",
}; };
console.log("top nav bar image style:", style);
return style;
}); });
const showDrawer = () => uni.$emit("SHOW_DRAWER"); const showDrawer = () => uni.$emit("SHOW_DRAWER");

View File

@@ -1,30 +1,18 @@
// 图片从0%到100%动画 // 图片从0%到100%动画
.image-animated { .image-animated {
animation: logo-scale 0.3s ease-in-out, animation: logo-scale 0.3s ease-in-out;
sprite-play calc(var(--ipSmallTime) * 1s) steps(var(--ipSmallImageStep))
infinite;
} }
@keyframes logo-scale { @keyframes logo-scale {
0% { 0% {
transform: scale(0); transform: scale(0);
} }
100% { 100% {
transform: scale(1); transform: scale(1);
} }
} }
@keyframes sprite-play {
0% {
background-position: 0 0;
}
100% {
/* 117 帧 × 每帧高度约 32px终点应为 -(117-1)*32 = -5421px */
background-position: 0 calc(var(--ipSmallImageHeight) * -1px);
}
}
// 文字从0%到100%动画,从左到右 // 文字从0%到100%动画,从左到右
.text-animated { .text-animated {
animation: text-fade-in 0.3s ease-in-out; animation: text-fade-in 0.3s ease-in-out;
@@ -35,6 +23,7 @@
opacity: 0; opacity: 0;
transform: translateX(-20px); transform: translateX(-20px);
} }
100% { 100% {
opacity: 1; opacity: 1;
transform: translateX(0); transform: translateX(0);

View File

@@ -3,7 +3,9 @@ v
<view class="welcome-content border-box p-12"> <view class="welcome-content border-box p-12">
<view class="wrap rounded-20"> <view class="wrap rounded-20">
<view class="flex flex-items-center flex-justify-between border-box pl-12 pr-12"> <view class="flex flex-items-center flex-justify-between border-box pl-12 pr-12">
<view class="ip" :style="getStyle"></view> <SpriteAnimator :src="spriteStyle.ipLargeImage" :frameWidth="spriteStyle.frameWidth"
:frameHeight="spriteStyle.frameHeight" :totalFrames="spriteStyle.totalFrames" :columns="spriteStyle.columns"
:displayWidth="spriteStyle.displayWidth" :fps="16" />
<view class="welcome-text font-size-14 font-500 font-family-misans-vf color-171717 line-height-24"> <view class="welcome-text font-size-14 font-500 font-family-misans-vf color-171717 line-height-24">
{{ welcomeContent }} {{ welcomeContent }}
</view> </view>
@@ -18,6 +20,7 @@ v
import { defineProps, computed, getCurrentInstance, defineExpose } from "vue"; import { defineProps, computed, getCurrentInstance, defineExpose } from "vue";
import { getCurrentConfig } from "@/constant/base"; import { getCurrentConfig } from "@/constant/base";
import ChatMoreTips from "../ChatMoreTips/index.vue"; import ChatMoreTips from "../ChatMoreTips/index.vue";
import SpriteAnimator from "@/components/Sprite/SpriteAnimator.vue";
const props = defineProps({ const props = defineProps({
mainPageDataModel: { mainPageDataModel: {
@@ -51,19 +54,16 @@ const initPageImages = computed(() => {
const config = getCurrentConfig(); const config = getCurrentConfig();
const getStyle = computed(() => { const spriteStyle = computed(() => {
const images = initPageImages.value; const images = initPageImages.value;
const style = { return {
"--ipLargeImageStep": images.ipLargeImageStep ?? config.ipLargeImageStep, ipLargeImage: images.ipLargeImage ?? config.ipLargeImage,
"--ipLargeImageHeight": images.ipLargeImageHeight ?? config.ipLargeImageHeight, frameWidth: images.ipLargeImageWidth ?? config.ipLargeImageWidth,
"--ipLargeTime": images.ipLargeTime ?? config.ipLargeTime, frameHeight: images.ipLargeImageHeight ?? config.ipLargeImageHeight,
backgroundImage: `url(${images.ipLargeImage || config.ipLargeImage})`, totalFrames: images.ipLargeTotalFrames ?? config.ipLargeTotalFrames,
backgroundRepeat: "no-repeat", columns: images.ipLargeColumns ?? config.ipLargeColumns,
backgroundSize: "158px auto", displayWidth: 158,
backgroundPosition: "0 0",
}; };
console.log("welcome image style:", style);
return style;
}); });
const welcomeContent = computed(() => props.mainPageDataModel.welcomeContent); const welcomeContent = computed(() => props.mainPageDataModel.welcomeContent);

View File

@@ -1,32 +1,3 @@
.wrap { .wrap {
background-color: rgba(255, 255, 255, 0.5); background-color: rgba(255, 255, 255, 0.5);
} }
.ip {
position: relative;
flex: 0 0 158px;
width: 158px;
height: 134px;
animation: sprite-play calc(var(--ipLargeTime) * 1s)
steps(var(--ipLargeImageStep)) infinite;
&::before {
content: "";
position: absolute;
background-color: #f9fcfd;
top: 0;
left: 0;
right: 0;
height: 3px;
}
}
@keyframes sprite-play {
0% {
background-position: 0 0;
}
100% {
/* 40 帧 × 每帧高度约 139px终点应为 -(40-1)*139 = -5421px */
background-position: 0 calc(var(--ipLargeImageHeight) * -1px);
}
}

View File

@@ -2,16 +2,9 @@
<view class="container"> <view class="container">
<ModuleTitle :title="recommendTheme.themeName" /> <ModuleTitle :title="recommendTheme.themeName" />
<view class="container-scroll"> <view class="container-scroll">
<view <view v-for="(item, index) in recommendTheme.recommendPostsList" :key="index">
v-for="(item, index) in recommendTheme.recommendPostsList"
:key="index"
>
<view class="mk-card-item" @click="sendReply(item)"> <view class="mk-card-item" @click="sendReply(item)">
<image <image class="card-img" :src="item.coverPhoto" mode="widthFix"></image>
class="card-img"
:src="item.coverPhoto"
mode="widthFix"
></image>
<text class="card-text">{{ item.topic }}</text> <text class="card-text">{{ item.topic }}</text>
</view> </view>
</view> </view>
@@ -20,7 +13,7 @@
</template> </template>
<script setup> <script setup>
import { SEND_MESSAGE_CONTENT_TEXT } from "@/constant/constant"; import { SEND_MESSAGE_CONTENT_TEXT, SEND_MESSAGE_COMMAND_TYPE } from "@/constant/constant";
import { defineProps } from "vue"; import { defineProps } from "vue";
import ModuleTitle from "@/components/ModuleTitle/index.vue"; import ModuleTitle from "@/components/ModuleTitle/index.vue";
@@ -32,8 +25,12 @@ const props = defineProps({
}); });
const sendReply = (item) => { const sendReply = (item) => {
const topic = item.userInputContent || item.topic.replace(/^#/, ""); if (item.userInputContentType && item.userInputContentType === '1') {
uni.$emit(SEND_MESSAGE_CONTENT_TEXT, topic); const commonItem = { type: item.userInputContent, title: item.topic }
uni.$emit(SEND_MESSAGE_COMMAND_TYPE, commonItem);
return;
}
uni.$emit(SEND_MESSAGE_CONTENT_TEXT, item.userInputContent);
}; };
</script> </script>

View File

@@ -3,29 +3,18 @@
<ModuleTitle :title="recommendTheme.themeName" /> <ModuleTitle :title="recommendTheme.themeName" />
<view class="container-scroll font-size-0 scroll-x whitespace-nowrap"> <view class="container-scroll font-size-0 scroll-x whitespace-nowrap">
<view <view class="card-item bg-white inline-block rounded-20 mr-8"
class="card-item bg-white inline-block rounded-20 mr-8" v-for="(item, index) in recommendTheme.recommendPostsList" :key="index" @click="sendReply(item)">
v-for="(item, index) in recommendTheme.recommendPostsList"
:key="index"
@click="sendReply(item)"
>
<view class="m-4 relative"> <view class="m-4 relative">
<image <image class="card-img rounded-16 relative z-10" :src="item.coverPhoto" mode="aspectFill" />
class="card-img rounded-16 relative z-10"
:src="item.coverPhoto"
mode="aspectFill"
/>
<view class="shadow absolute rounded-16"></view> <view class="shadow absolute rounded-16"></view>
</view> </view>
<view class="card-text border-box"> <view class="card-text border-box">
<view class="color-171717 font-size-14 line-height-20 ellipsis-1"> <view class="font-size-11 color-99A0AE ellipsis-1">
{{ item.topic }} {{ item.topic }}
</view> </view>
<view class="font-size-11 color-99A0AE">
{{ item.userInputContent }}
</view>
</view> </view>
</view> </view>
</view> </view>
@@ -34,7 +23,7 @@
<script setup> <script setup>
import { defineProps } from "vue"; import { defineProps } from "vue";
import { SEND_MESSAGE_CONTENT_TEXT } from "@/constant/constant"; import { SEND_MESSAGE_CONTENT_TEXT, SEND_MESSAGE_COMMAND_TYPE } from "@/constant/constant";
import ModuleTitle from "@/components/ModuleTitle/index.vue"; import ModuleTitle from "@/components/ModuleTitle/index.vue";
const props = defineProps({ const props = defineProps({
@@ -45,8 +34,12 @@ const props = defineProps({
}); });
const sendReply = (item) => { const sendReply = (item) => {
const topic = item.userInputContent || item.topic.replace(/^#/, ""); if (item.userInputContentType && item.userInputContentType === '1') {
uni.$emit(SEND_MESSAGE_CONTENT_TEXT, topic); const commonItem = { type: item.userInputContent, title: item.topic }
uni.$emit(SEND_MESSAGE_COMMAND_TYPE, commonItem);
return;
}
uni.$emit(SEND_MESSAGE_CONTENT_TEXT, item.userInputContent);
}; };
</script> </script>

View File

@@ -1,26 +1,14 @@
<template> <template>
<view class="webview"> <view>
<!-- 使用 NavBar 组件 --> <web-view :src="webviewUrl" @message="handleH5Message"></web-view>
<TopNavBar title="网页浏览" @back="goBack" />
<!-- WebView 内容区域 -->
<view class="webview-content">
<web-view :src="webviewUrl"></web-view>
</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import TopNavBar from "@/components/TopNavBar/index.vue";
const webviewUrl = ref(""); const webviewUrl = ref("");
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
onMounted(() => { onMounted(() => {
// 获取页面参数 // 获取页面参数
const pages = getCurrentPages(); const pages = getCurrentPages();
@@ -33,24 +21,26 @@ onMounted(() => {
webviewUrl.value = decodeURIComponent(options.url); webviewUrl.value = decodeURIComponent(options.url);
} }
}); });
const handleH5Message = (event) => {
const messageData = event.detail.data[0];
console.log("Received message from H5:", messageData);
// 根据需要处理H5传递过来的消息
const action = messageData.action;
switch (action) {
case "navigateBack":
uni.navigateBack();
break;
case "navigateTo":
break
default:
console.log("Unknown action:", action);
}
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
.webview {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #fff;
}
.webview-content {
flex: 1;
margin-top: calc(44px + var(--status-bar-height));
}
.webview-content {
width: 100%;
height: 100%;
}
</style>

View File

@@ -6,7 +6,7 @@ export function navigateTo(url, args) {
// 如果有额外参数拼接到URL后面 // 如果有额外参数拼接到URL后面
const paramString = objectToUrlParams(args); const paramString = objectToUrlParams(args);
if (paramString) { if (paramString) {
if (targetUrl.contains("?")) { if (typeof targetUrl === "string" && targetUrl.includes("?")) {
targetUrl += "&"; targetUrl += "&";
} else { } else {
targetUrl += "?"; targetUrl += "?";
@@ -14,6 +14,7 @@ export function navigateTo(url, args) {
targetUrl += paramString; targetUrl += paramString;
} }
} }
console.log("Navigating to URL:", targetUrl);
uni.navigateTo({ uni.navigateTo({
url: "/pages/webview/index?url=" + encodeURIComponent(targetUrl), url: "/pages/webview/index?url=" + encodeURIComponent(targetUrl),
}); });

View File

@@ -21,3 +21,7 @@
.h-24 { .h-24 {
height: 24px; height: 24px;
} }
.h72 {
height: 72px;
}

View File

@@ -6,6 +6,10 @@
width: 100vw; width: 100vw;
} }
.w-vw-24 {
width: calc(100vw - 24px);
}
.w-24 { .w-24 {
width: 24px; width: 24px;
} }
@@ -22,6 +26,12 @@
width: 60px; width: 60px;
} }
.w-80 { .w-80 {
width: 80px; width: 80px;
} }
.w-102 {
width: 102px;
}