Compare commits

..

No commits in common. "515ae4e78c55ead98bdb1348df93ea0765f7fb53" and "f57fbd29aa2e5dd57c2c1a8eb07ee11fd21a8e37" have entirely different histories.

32 changed files with 1978 additions and 2311 deletions

View File

@ -2,11 +2,12 @@
position: relative; position: relative;
padding: 6px 2px 2px; padding: 6px 2px 2px;
display: inline-block; display: inline-block;
} }
.module-title { .module-title {
font-size: 18px; font-size: 18px;
font-family: PingFang SC, PingFang SC; font-family: DingTalk JinBuTi, DingTalk JinBuTi;
color: #000; color: #000;
position: relative; position: relative;
z-index: 1; z-index: 1;

View File

@ -1,37 +1,29 @@
<template> <template>
<view class="container"> <view class="container">
<view class="chat-ai"> <view class="chat-ai">
<view class="loading-container"> <view class="loading-container" >
<image <image v-if="isLoading" class="loading-img" src="/static/msg_loading.svg" />
v-if="isLoading" <ChatMarkdown :key="textKey" :text="processedText" />
class="loading-img" </view>
src="/static/msg_loading.svg" <slot name="content"></slot>
/> </view>
<!-- <loading v-if="isLoading" /> --> <slot name="footer"></slot>
<ChatMarkdown :key="textKey" :text="processedText" />
<DotLoading v-if="isLoading" />
</view>
<slot name="content"></slot>
</view> </view>
<slot name="footer"></slot>
</view>
</template> </template>
<script setup> <script setup>
import { defineProps, computed, ref, watch } from "vue"; import { defineProps, computed, ref, watch } from "vue";
import ChatMarkdown from "./ChatMarkdown.vue"; import ChatMarkdown from "./ChatMarkdown.vue";
import loading from "@/pages/loading/loading.vue";
import DotLoading from "@/pages/loading/DotLoading.vue";
const props = defineProps({ const props = defineProps({
text: { text: {
type: String, type: String,
default: "", default: "",
}, },
isLoading: { isLoading: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}); });
// key // key
@ -39,63 +31,63 @@ const textKey = ref(0);
// //
const processedText = computed(() => { const processedText = computed(() => {
if (!props.text) { if (!props.text) {
return ""; return "";
} }
// //
const textStr = String(props.text); const textStr = String(props.text);
//
if (textStr.includes("加载中") || textStr.includes("...")) {
return textStr;
}
//
if (textStr.includes("加载中") || textStr.includes("...")) {
return textStr; return textStr;
}
return textStr;
}); });
// text // text
watch( watch(
() => props.text, () => props.text,
(newText, oldText) => { (newText, oldText) => {
if (newText !== oldText) { if (newText !== oldText) {
textKey.value++; textKey.value++;
} }
}, },
{ immediate: true } { immediate: true }
); );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 100%; //
overflow-x: hidden; //
.chat-ai {
margin: 6px 12px;
padding: 0 12px;
max-width: 100%; // max-width: 100%; //
min-width: 100px; overflow-x: hidden; //
background: rgba(255, 255, 255, 0.4);
box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.1); .chat-ai {
border-radius: 4px 20px 20px 20px; margin: 6px 12px;
border: 1px solid; padding: 0 12px;
border-color: #ffffff;
} min-width: 90px;
background: rgba(255, 255, 255, 0.4);
box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.1);
border-radius: 4px 20px 20px 20px;
border: 1px solid;
border-color: #ffffff;
}
} }
.loading-container { .loading-container {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 4px 0; padding: 4px 0;
max-width: 100%; //
} }
.loading-img { .loading-img {
margin-left: -4px; margin-left: -4px;
width: 32px; width: 32px;
height: 32px; height: 32px;
} }
</style> </style>

View File

@ -1,45 +1,46 @@
<template> <template>
<view class="chat-mine"> <view class="chat-mine">
<text>{{ text }}</text> <text>{{ text }}</text>
<slot></slot> <slot></slot>
</view> </view>
</template> </template>
<script setup> <script setup>
import { defineProps } from "vue"; import { defineProps } from "vue";
defineProps({ defineProps({
text: { text: {
type: String, type: String,
default: "", default: ''
}, }
}); })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-mine { .chat-mine {
margin: 6px 12px; margin: 6px 12px;
padding: 8px 16px; padding: 8px 16px;
background-color: #00a6ff; background-color: #00A6FF;
box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.1); box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.1);
border-radius: 20px 4px 20px 20px; border-radius: 20px 4px 20px 20px;
border: 1px solid; border: 1px solid;
border-color: #ffffff; border-color: #FFFFFF;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 100%; // max-width: 100%; //
overflow-x: hidden; // overflow-x: hidden; //
text { text {
font-family: PingFang SC, PingFang SC; font-family: PingFang SC, PingFang SC;
font-weight: 400; font-weight: 400;
font-size: 15px; font-size: 14px;
color: #ffffff; color: #FFFFFF;
line-height: 22px; line-height: 22px;
text-align: justify; text-align: justify;
font-style: normal; font-style: normal;
text-transform: none; text-transform: none;
} }
} }
</style> </style>

View File

@ -1,35 +1,36 @@
<template> <template>
<view class="chat-other"> <view class="chat-other">
<text>{{ text }}</text> <text>{{ text }}</text>
<slot></slot> <slot></slot>
</view> </view>
</template> </template>
<script setup> <script setup>
import { defineProps } from "vue"; import { defineProps } from "vue";
defineProps({ defineProps({
text: { text: {
type: String, type: String,
default: "", default: ''
}, }
}); })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-other { .chat-other {
width: 100%; width: 100%;
margin: 6px 0; margin: 6px 0;
padding: 0 12px; padding: 0 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 100%; // max-width: 100%; //
overflow-x: hidden; // overflow-x: hidden; //
text { text {
font-family: PingFang SC, PingFang SC; font-family: PingFang SC, PingFang SC;
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 14px;
color: #333333; color: #333333;
} }
}
}
</style> </style>

View File

@ -1,62 +1,65 @@
<template> <template>
<view class="input-area-wrapper"> <view class="input-area-wrapper">
<view v-if="!visibleWaveBtn" class="area-input"> <view v-if="!visibleWaveBtn" class="area-input">
<!-- 语音/键盘切换 --> <!-- 语音/键盘切换 -->
<view class="input-container-voice" @click="toggleVoiceMode"> <view class="input-container-voice" @click="toggleVoiceMode">
<image v-if="!isVoiceMode" src="/static/input_voice_icon.png"></image> <image
<image v-else src="/static/input_keyboard_icon.png"></image> v-if="!isVoiceMode"
</view> src="/static/input_voice_icon.png"
></image>
<image v-else src="/static/input_keyboard_icon.png"></image>
</view>
<!-- 输入框/语音按钮容器 --> <!-- 输入框/语音按钮容器 -->
<view class="input-button-container"> <view class="input-button-container">
<textarea <textarea
ref="textareaRef" ref="textareaRef"
v-if="!isVoiceMode" v-if="!isVoiceMode"
class="textarea" class="textarea"
type="text" type="text"
cursor-spacing="20" cursor-spacing="20"
confirm-type="send" confirm-type="send"
v-model="inputMessage" v-model="inputMessage"
auto-height auto-height
:confirm-hold="true" :confirm-hold="true"
:placeholder="placeholder" :placeholder="placeholder"
:show-confirm-bar="false" :show-confirm-bar="false"
:hold-keyboard="holdKeyboard" :hold-keyboard="holdKeyboard"
:adjust-position="true" :adjust-position="true"
:disable-default-padding="true" :disable-default-padding="true"
maxlength="300" maxlength="300"
@confirm="sendMessage" @confirm="sendMessage"
@focus="handleFocus" @focus="handleFocus"
@blur="handleBlur" @blur="handleBlur"
@touchstart="handleTouchStart" @touchstart="handleTouchStart"
@touchend="handleTouchEnd" @touchend="handleTouchEnd"
/> />
<view <view
v-if="isVoiceMode" v-if="isVoiceMode"
class="hold-to-talk-button" class="hold-to-talk-button"
@longpress="handleVoiceTouchStart" @longpress="handleVoiceTouchStart"
@touchend="handleVoiceTouchEnd" @touchend="handleVoiceTouchEnd"
@touchcancel="handleVoiceTouchEnd" @touchcancel="handleVoiceTouchEnd"
> >
按住 说话 按住 说话
</view>
</view>
<view class="input-container-send">
<view class="input-container-send-btn" @click="sendMessage">
<image
v-if="props.isSessionActive"
src="/static/input_stop_icon.png"
></image>
<image v-else src="/static/input_send_icon.png"></image>
</view>
</view>
</view> </view>
</view>
<view class="input-container-send"> <!-- 录音按钮 -->
<view class="input-container-send-btn" @click="sendMessage"> <RecordingWaveBtn v-if="visibleWaveBtn" ref="recordingWaveBtnRef" />
<image
v-if="props.isSessionActive"
src="/static/input_stop_icon.png"
></image>
<image v-else src="/static/input_send_icon.png"></image>
</view>
</view>
</view> </view>
<!-- 录音按钮 -->
<RecordingWaveBtn v-if="visibleWaveBtn" ref="recordingWaveBtnRef" />
</view>
</template> </template>
<script setup> <script setup>
@ -67,18 +70,18 @@ const plugin = requirePlugin("WechatSI");
const manager = plugin.getRecordRecognitionManager(); const manager = plugin.getRecordRecognitionManager();
const props = defineProps({ const props = defineProps({
modelValue: String, modelValue: String,
holdKeyboard: Boolean, holdKeyboard: Boolean,
isSessionActive: Boolean, isSessionActive: Boolean,
stopRequest: Function, stopRequest: Function,
}); });
const emit = defineEmits([ const emit = defineEmits([
"update:modelValue", "update:modelValue",
"send", "send",
"noHideKeyboard", "noHideKeyboard",
"keyboardShow", "keyboardShow",
"keyboardHide", "keyboardHide",
"sendVoice", "sendVoice",
]); ]);
const textareaRef = ref(null); const textareaRef = ref(null);
@ -92,137 +95,137 @@ const visibleWaveBtn = ref(false);
// //
watch( watch(
() => props.modelValue, () => props.modelValue,
(val) => { (val) => {
inputMessage.value = val; inputMessage.value = val;
} }
); );
// inputMessage // inputMessage
watch(inputMessage, (val) => { watch(inputMessage, (val) => {
// emit // emit
if (val !== props.modelValue) { if (val !== props.modelValue) {
emit("update:modelValue", val); emit("update:modelValue", val);
} }
}); });
// / // /
const toggleVoiceMode = () => { const toggleVoiceMode = () => {
isVoiceMode.value = !isVoiceMode.value; isVoiceMode.value = !isVoiceMode.value;
}; };
// //
const handleVoiceTouchStart = () => { const handleVoiceTouchStart = () => {
manager.start({ lang: "zh_CN" }); manager.start({ lang: "zh_CN" });
visibleWaveBtn.value = true; visibleWaveBtn.value = true;
// //
nextTick(() => { nextTick(() => {
if (recordingWaveBtnRef.value) { if (recordingWaveBtnRef.value) {
recordingWaveBtnRef.value.startAnimation(); recordingWaveBtnRef.value.startAnimation();
} }
}); });
}; };
// //
const handleVoiceTouchEnd = () => { const handleVoiceTouchEnd = () => {
manager.stop(); manager.stop();
// //
if (recordingWaveBtnRef.value) { if (recordingWaveBtnRef.value) {
recordingWaveBtnRef.value.stopAnimation(); recordingWaveBtnRef.value.stopAnimation();
} }
visibleWaveBtn.value = false; visibleWaveBtn.value = false;
}; };
// //
const initRecord = () => { const initRecord = () => {
manager.onRecognize = (res) => { manager.onRecognize = (res) => {
let text = res.result; let text = res.result;
inputMessage.value = text; inputMessage.value = text;
}; };
// //
manager.onStop = (res) => { manager.onStop = (res) => {
console.log(res, 37); console.log(res, 37);
let text = res.result; let text = res.result;
if (text == "") { if (text == "") {
console.log("没有说话"); console.log("没有说话");
return; return;
} }
inputMessage.value = text; inputMessage.value = text;
// //
emit("send", text); emit("send", text);
}; };
}; };
// //
onMounted(() => { onMounted(() => {
// //
uni.onKeyboardHeightChange((res) => { uni.onKeyboardHeightChange((res) => {
keyboardHeight.value = res.height; keyboardHeight.value = res.height;
if (res.height) { if (res.height) {
emit("keyboardShow", res.height); emit("keyboardShow", res.height);
} else { } else {
emit("keyboardHide"); emit("keyboardHide");
} }
}); });
initRecord(); initRecord();
}); });
const sendMessage = () => { const sendMessage = () => {
if (props.isSessionActive) { if (props.isSessionActive) {
// //
if (props.stopRequest) { if (props.stopRequest) {
props.stopRequest(); props.stopRequest();
} }
} else { } else {
// //
if (!inputMessage.value.trim()) return; if (!inputMessage.value.trim()) return;
emit("send", inputMessage.value); emit("send", inputMessage.value);
// //
if (props.holdKeyboard && textareaRef.value) { if (props.holdKeyboard && textareaRef.value) {
nextTick(() => { nextTick(() => {
textareaRef.value.focus(); textareaRef.value.focus();
}); });
}
} }
}
}; };
const handleFocus = () => { const handleFocus = () => {
isFocused.value = true; isFocused.value = true;
emit("noHideKeyboard"); emit("noHideKeyboard");
}; };
const handleBlur = () => { const handleBlur = () => {
isFocused.value = false; isFocused.value = false;
}; };
const handleTouchStart = () => { const handleTouchStart = () => {
emit("noHideKeyboard"); emit("noHideKeyboard");
}; };
const handleTouchEnd = () => { const handleTouchEnd = () => {
emit("noHideKeyboard"); emit("noHideKeyboard");
}; };
// //
const focusInput = () => { const focusInput = () => {
if (textareaRef.value) { if (textareaRef.value) {
textareaRef.value.focus(); textareaRef.value.focus();
} }
}; };
// //
const blurInput = () => { const blurInput = () => {
if (textareaRef.value) { if (textareaRef.value) {
textareaRef.value.blur(); textareaRef.value.blur();
} }
}; };
// //
@ -231,87 +234,87 @@ defineExpose({ focusInput });
<style scoped lang="scss"> <style scoped lang="scss">
.area-input { .area-input {
display: flex;
align-items: center;
border-radius: 22px;
background-color: #ffffff;
box-shadow: 0px 0px 20px 0px rgba(52, 25, 204, 0.05);
margin: 0 12px;
margin-bottom: 8px;
.input-container-voice {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; border-radius: 22px;
align-self: flex-end;
width: 44px;
height: 44px;
image {
width: 22px;
height: 22px;
}
}
.input-button-container {
flex: 1;
position: relative;
}
.hold-to-talk-button {
width: 100%;
height: 44px;
color: #333333;
font-size: 16px;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff; background-color: #ffffff;
transition: all 0.2s ease; box-shadow: 0px 0px 20px 0px rgba(52, 25, 204, 0.05);
user-select: none; margin: 0 12px;
-webkit-user-select: none; margin-bottom: 8px;
}
.input-container-send { .input-container-voice {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
align-self: flex-end; align-self: flex-end;
width: 44px; width: 44px;
height: 44px; height: 44px;
.input-container-send-btn { image {
display: flex; width: 22px;
align-items: center; height: 22px;
justify-content: center; }
width: 100%;
height: 100%;
} }
image { .input-button-container {
width: 28px; flex: 1;
height: 28px; position: relative;
}
}
.textarea {
flex: 1;
box-sizing: border-box;
width: 100%;
max-height: 92px;
min-height: 22px;
font-size: 16px;
line-height: 22px;
margin: 6px 0;
&::placeholder {
color: #cccccc;
line-height: normal;
} }
&:focus { .hold-to-talk-button {
outline: none; width: 100%;
height: 44px;
color: #333333;
font-size: 16px;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff;
transition: all 0.2s ease;
user-select: none;
-webkit-user-select: none;
}
.input-container-send {
display: flex;
align-items: center;
justify-content: center;
align-self: flex-end;
width: 44px;
height: 44px;
.input-container-send-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
image {
width: 28px;
height: 28px;
}
}
.textarea {
flex: 1;
box-sizing: border-box;
width: 100%;
max-height: 92px;
min-height: 22px;
font-size: 16px;
line-height: 22px;
margin: 6px 0;
&::placeholder {
color: #cccccc;
line-height: normal;
}
&:focus {
outline: none;
}
} }
}
} }
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,21 @@
<template> <template>
<view> <view>
<zero-markdown-view :markdown="text" :aiMode="true"></zero-markdown-view> <zero-markdown-view :markdown="text" :aiMode='true'></zero-markdown-view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { defineProps } from "vue"; import { defineProps } from "vue";
defineProps({
text: {
type: String,
default: ''
}
})
defineProps({
text: {
type: String,
default: "",
},
});
</script> </script>
<style scoped></style> <style scoped>
</style>

View File

@ -1,25 +1,27 @@
<template> <template>
<view class="quick-access"> <view class="quick-access">
<view class="quick-access-scroll"> <view class="quick-access-scroll">
<view <view
class="quick-access-item" class="quick-access-item"
v-for="(item, index) in itemList" v-for="(item, index) in itemList"
:key="index" :key="index"
@click="sendReply(item)" @click="sendReply(item)"
> >
<image <image
class="quick-access-item-bg" class="quick-access-item-bg"
src="/static/quick/quick_icon_bg.png" src="/static/quick/quick_icon_bg.png"
mode="aspectFill" mode="aspectFill"
></image> ></image>
<view class="quick-access-item-title"> <view class="quick-access-item-title">
<image :src="item.icon"></image> <image :src="item.icon"></image>
<text>{{ item.title }}</text> <text>{{ item.title }}</text>
</view>
<text class="quick-access-item-content">{{
item.content
}}</text>
</view>
</view> </view>
<text class="quick-access-item-content">{{ item.content }}</text>
</view>
</view> </view>
</view>
</template> </template>
<script setup> <script setup>
@ -29,113 +31,113 @@ const itemList = ref([]);
const emits = defineEmits(["replySent"]); const emits = defineEmits(["replySent"]);
const sendReply = (item) => { const sendReply = (item) => {
emits("replySent", item); // emits("replySent", item); //
}; };
onMounted(() => { onMounted(() => {
initData(); initData();
}); });
const initData = () => { const initData = () => {
itemList.value = [ itemList.value = [
{ {
icon: "/static/quick/quick_icon_yuding.png", icon: "/static/quick/quick_icon_yuding.png",
title: "快速预定", title: "快速预定",
content: "预定门票、房间、餐食", content: "预定门票、房间、餐食",
type: "Command.quickBooking", type: "Command.quickBooking",
}, },
{ {
icon: "/static/quick/quick_icon_find.png", icon: "/static/quick/quick_icon_find.png",
title: "探索发现", title: "探索发现",
content: "探索玩法、出片佳地", content: "探索玩法、出片佳地",
type: "Command.discovery", type: "Command.discovery",
}, },
{ {
icon: "/static/quick/quick_icon_order.png", icon: "/static/quick/quick_icon_order.png",
title: "订单/工单", title: "订单/工单",
content: "我的订单/工单", content: "我的订单/工单",
type: "MyOrder", type: "MyOrder",
}, },
{ {
icon: "/static/quick/quick_icon_call.png", icon: "/static/quick/quick_icon_call.png",
title: "呼叫服务", title: "呼叫服务",
content: "加水、客房服务等", content: "加水、客房服务等",
type: "Command.createWorkOrder", type: "Command.createWorkOrder",
}, },
]; ];
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.quick-access { .quick-access {
width: 100%; width: 100%;
&-scroll { &-scroll {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
overflow-x: auto; overflow-x: auto;
white-space: nowrap; white-space: nowrap;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
}
.quick-access-item {
flex: 0 0 104px;
border-radius: 8px;
margin: 4px 4px 8px 4px;
box-shadow: 0 2px 5px 0px rgba(0, 0, 0, 0.1);
padding: 12px;
display: inline-flex;
flex-direction: column;
position: relative;
&:first-child {
margin-left: 12px;
} }
&:last-child { .quick-access-item {
margin-right: 12px; flex: 0 0 104px;
border-radius: 8px;
margin: 4px 4px 8px 4px;
box-shadow: 0 2px 5px 0px rgba(0, 0, 0, 0.1);
padding: 12px;
display: inline-flex;
flex-direction: column;
position: relative;
&:first-child {
margin-left: 12px;
}
&:last-child {
margin-right: 12px;
}
.quick-access-item-bg {
position: absolute;
top: 0;
left: 0;
z-index: 0;
border-radius: 8px;
width: 128px;
height: 56px;
}
.quick-access-item-title {
display: flex;
align-items: center;
z-index: 1;
image {
width: 16px;
height: 16px;
margin-right: 4px;
}
text {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 12px;
color: #201f32;
line-height: 16px;
}
}
.quick-access-item-content {
z-index: 1;
margin-top: 4px;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 10px;
color: #678cad;
line-height: 18px;
}
} }
.quick-access-item-bg {
position: absolute;
top: 0;
left: 0;
z-index: 0;
border-radius: 8px;
width: 128px;
height: 56px;
}
.quick-access-item-title {
display: flex;
align-items: center;
z-index: 1;
image {
width: 16px;
height: 16px;
margin-right: 4px;
}
text {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 12px;
color: #201f32;
line-height: 16px;
}
}
.quick-access-item-content {
z-index: 1;
margin-top: 4px;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 10px;
color: #678cad;
line-height: 18px;
}
}
} }
</style> </style>

View File

@ -1,18 +1,20 @@
<template> <template>
<view class="top-bg"> </view> <view class="top-bg">
</view>
</template> </template>
<script></script> <script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.top-bg { .top-bg {
width: 100%; width: 100%;
height: 270px; height: 270px;
overflow: hidden; overflow: hidden;
background: linear-gradient(180deg, #42adf9 0%, #6cd1ff 51%, #e9f3f7 99%); background: linear-gradient( 180deg, #42ADF9 0%, #6CD1FF 51%, #E9F3F7 99%);
.top-bg-img { .top-bg-img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
</style> </style>

View File

@ -1,46 +1,48 @@
<template> <template>
<view class="nav-bar"> <view class="nav-bar">
<view class="nav-item" @click="showDrawer('showLeft')"> <view class="nav-item" @click="showDrawer('showLeft')">
<uni-icons type="bars" size="24" color="#333"></uni-icons> <uni-icons type="bars" size="24" color="#333"></uni-icons>
</view> </view>
<uni-drawer ref="showLeft" mode="left" :width="320"> <uni-drawer ref="showLeft" mode="left" :width="320">
<DrawerHome @closeDrawer="closeDrawer('showLeft')" /> <DrawerHome @closeDrawer="closeDrawer('showLeft')" />
</uni-drawer> </uni-drawer>
</view>
</view>
</template> </template>
<script setup> <script setup>
import { defineEmits, ref } from "vue"; import { defineEmits, ref } from 'vue'
import DrawerHome from "@/pages/drawer/DrawerHome.vue"; import DrawerHome from "@/pages/drawer/DrawerHome.vue";
const showLeft = ref(false); const showLeft = ref(false)
//
const showDrawer = (e) => {
showLeft.value.open()
}
//
const closeDrawer = (e) => {
showLeft.value.close()
}
//
const showDrawer = (e) => {
showLeft.value.open();
};
//
const closeDrawer = (e) => {
showLeft.value.close();
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.nav-bar { .nav-bar {
display: flex; display: flex;
align-items: center; align-items: center;
height: 44px; height: 44px;
padding: 0 15px; padding: 0 15px;
.nav-item { .nav-item {
width: 24px; width: 24px;
height: 24px; height: 24px;
margin-right: 10px; margin-right: 10px;
} }
.nav-item-icon { .nav-item-icon {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
</style> </style>

View File

@ -1,15 +1,18 @@
<template> <template>
<view class="top-bg-content"> <view class="top-bg-content">
<view class="top-item"> <view class="top-item">
<!-- :style="backgroundStyle" --> <!-- :style="backgroundStyle" -->
<image <image
class="top-item-left" class="top-item-left"
:src="initPageImages.welcomeImageUrl" :src="initPageImages.welcomeImageUrl"
></image> ></image>
<image class="top-item-right" :src="initPageImages.logoImageUrl"></image> <image
class="top-item-right"
:src="initPageImages.logoImageUrl"
></image>
</view>
<ChatCardAI v-if="welcomeContent.length" :text="welcomeContent" />
</view> </view>
<ChatCardAI v-if="welcomeContent.length" :text="welcomeContent" />
</view>
</template> </template>
<script setup> <script setup>
@ -17,56 +20,56 @@ import { defineProps, computed, ref } from "vue";
import ChatCardAI from "./ChatCardAI.vue"; import ChatCardAI from "./ChatCardAI.vue";
const props = defineProps({ const props = defineProps({
initPageImages: { initPageImages: {
type: Object, type: Object,
default: { default: {
backgroundImageUrl: "", backgroundImageUrl: "",
logoImageUrl: "", logoImageUrl: "",
welcomeImageUrl: "", welcomeImageUrl: "",
},
},
welcomeContent: {
type: String,
default:
"查信息、预定下单、探索玩法、呼叫服务、我通通可以满足,快试试问我问题吧!",
}, },
},
welcomeContent: {
type: String,
default:
"查信息、预定下单、探索玩法、呼叫服务、我通通可以满足,快试试问我问题吧!",
},
}); });
// //
const backgroundStyle = computed(() => { const backgroundStyle = computed(() => {
return { return {
backgroundImage: `url(${props.initPageImages.backgroundImageUrl})`, backgroundImage: `url(${props.initPageImages.backgroundImageUrl})`,
backgroundSize: "cover", backgroundSize: "cover",
backgroundPosition: "center", backgroundPosition: "center",
backgroundRepeat: "no-repeat", backgroundRepeat: "no-repeat",
}; };
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.top-bg-content { .top-bg-content {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: stretch; align-items: stretch;
flex-direction: column; flex-direction: column;
} }
.top-item { .top-item {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 32px; padding: 0 32px;
margin-bottom: -6px; margin-bottom: -6px;
} }
.top-item-left { .top-item-left {
width: 118px; width: 118px;
height: 52px; height: 52px;
} }
.top-item-right { .top-item-right {
width: 130px; width: 130px;
height: 130px; height: 130px;
} }
</style> </style>

View File

@ -1,155 +1,153 @@
.chat-container { .chat-container {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
background-color: #e9f3f7; background-color: #E9F3F7;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden !important; overflow: hidden !important;
position: relative; position: relative;
/* 确保在键盘弹起时布局正确 */ /* 确保在键盘弹起时布局正确 */
box-sizing: border-box; box-sizing: border-box;
.chat-container-bg { .chat-container-bg {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 0; z-index: 0;
height: 270px; height: 270px;
background: linear-gradient(180deg, #42adf9 0%, #6cd1ff 51%, #e9f3f7 99%); background: linear-gradient( 180deg, #42ADF9 0%, #6CD1FF 51%, #E9F3F7 99%);
} }
.chat-content { .chat-content {
width: 100vw; width: 100vw;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
min-height: 0; min-height: 0;
z-index: 1; z-index: 1;
overflow: hidden; overflow: hidden;
} }
.chat-container-top-bannar { .chat-container-top-bannar {
width: 100vw; width: 100vw;
flex-shrink: 0; flex-shrink: 0;
touch-action: none; touch-action: none;
} }
.area-msg-list { .area-msg-list {
width: 100vw; width: 100vw;
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
min-height: 0; min-height: 0;
height: 0; height: 0;
padding: 4px 0 0; padding: 4px 0 0;
overscroll-behavior: contain; /* 阻止滚动穿透 */ overscroll-behavior: contain; /* 阻止滚动穿透 */
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.message-item-ai { .message-item-ai {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
} }
.message-item-mine { .message-item-mine {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
} }
.message-item-other { .message-item-other {
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
} }
} }
.footer-area { .footer-area {
width: 100vw; width: 100vw;
flex-shrink: 0; flex-shrink: 0;
padding: 4px 0 20px 0; /* 直接设置20px底部安全距离 */ padding: 4px 0 20px 0; /* 直接设置20px底部安全距离 */
background-color: #e9f3f7; background-color: #E9F3F7;
touch-action: pan-x; touch-action: pan-x;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
min-height: fit-content; min-height: fit-content;
/* 安卓键盘适配 - 使用相对定位配合adjustPan */ /* 安卓键盘适配 - 使用相对定位配合adjustPan */
position: relative; position: relative;
z-index: 1; z-index: 1;
transition: padding-bottom 0.3s ease; transition: padding-bottom 0.3s ease;
/* 确保输入区域始终可见 */ /* 确保输入区域始终可见 */
// transform: translateZ(0); // transform: translateZ(0);
// -webkit-transform: translateZ(0); // -webkit-transform: translateZ(0);
} }
.area-input { .area-input {
display: flex; display: flex;
align-items: center; align-items: center;
border-radius: 22px; border-radius: 22px;
background-color: #ffffff; background-color: #FFFFFF;
box-shadow: 0px 0px 20px 0px rgba(52, 25, 204, 0.05); box-shadow: 0px 0px 20px 0px rgba(52,25,204,0.05);
margin: 0 12px; margin: 0 12px;
.input-container-voice { .input-container-voice {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 44px; width: 44px;
height: 44px; height: 44px;
flex-shrink: 0; flex-shrink: 0;
align-self: flex-end; align-self: flex-end;
image { image {
width: 22px; width: 22px;
height: 22px; height: 22px;
} }
} }
.input-container-send { .input-container-send {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 44px; width: 44px;
height: 44px; height: 44px;
flex-shrink: 0; flex-shrink: 0;
align-self: flex-end; align-self: flex-end;
image { image {
width: 28px; width: 28px;
height: 28px; height: 28px;
} }
} }
.textarea { .textarea {
flex: 1; flex: 1;
max-height: 92px; max-height: 92px;
min-height: 22px; min-height: 22px;
font-size: 16px; font-size: 16px;
line-height: 22px; line-height: 22px;
margin-bottom: 2px; margin-bottom: 2px;
align-items: center; align-items: center;
} }
} }
// 打字机光标闪烁动画 // 打字机光标闪烁动画
.typing-cursor { .typing-cursor {
display: inline-block; display: inline-block;
color: #42adf9; color: #42ADF9;
font-weight: bold; font-weight: bold;
font-size: 1.2em; font-size: 1.2em;
animation: blink 1s infinite; animation: blink 1s infinite;
margin-left: 2px; margin-left: 2px;
} }
@keyframes blink { @keyframes blink {
0%, 0%, 50% {
50% { opacity: 1;
opacity: 1; }
} 51%, 100% {
51%, opacity: 0;
100% { }
opacity: 0; }
}
}

View File

@ -1,54 +1,50 @@
<template> <template>
<view class="drawer-home"> <view class="drawer-home">
<view class="drawer-home-nav"> <view class="drawer-home-nav">
<uni-icons <uni-icons type="closeempty" size="22" color="#333333" class="close-icon" @click="closeDrawer"></uni-icons>
type="closeempty" <text class="title">我的</text>
size="22" </view>
color="#333333"
class="close-icon"
@click="closeDrawer"
></uni-icons>
<text class="title">我的</text>
</view>
<MineSetting /> <MineSetting/>
</view>
</view>
</template> </template>
<script setup> <script setup>
import MineSetting from "./MineSetting.vue"; import MineSetting from './MineSetting.vue'
import { defineEmits } from "vue"; import { defineEmits } from 'vue'
const emits = defineEmits(["closeDrawer"]); const emits = defineEmits(['closeDrawer'])
const closeDrawer = () => {
emits('closeDrawer')
}
const closeDrawer = () => {
emits("closeDrawer");
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.drawer-home { .drawer-home {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
background-color: #fff; background-color: #fff;
padding-top: 44px; padding-top: 44px;
} }
.drawer-home-nav { .drawer-home-nav {
position: relative; position: relative;
padding: 12px; padding: 12px;
display: flex; display: flex;
justify-content: center; /* 文字水平居中 */ justify-content: center; /* 文字水平居中 */
align-items: center; /* 垂直居中 */ align-items: center; /* 垂直居中 */
.title { .title {
font-size: 18px; font-size: 18px;
text-align: center; text-align: center;
color: #333333; color: #333333;
} }
.close-icon { .close-icon {
position: absolute; position: absolute;
left: 12px; /* 距离左边12px */ left: 12px; /* 距离左边12px */
} }
} }
</style> </style>

View File

@ -36,64 +36,60 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from 'vue'
import { getLoginUserPhone } from "@/request/api/LoginApi"; import { getLoginUserPhone } from '@/request/api/LoginApi'
// //
const userInfo = ref({ const userInfo = ref({
avatar: "/static/default-avatar.png", avatar: '/static/default-avatar.png',
nickname: "微信用户", nickname: '微信用户',
phone: "", phone: ''
}); })
// type // type
const menuList = ref([ const menuList = ref([
// { label: '', type: 'navigate', url: '/pages/change-phone/change-phone' }, // { label: '', type: 'navigate', url: '/pages/change-phone/change-phone' },
{ { label: '账号注销', type: 'navigate', url: '/pages/cancel-account/cancel-account' },
label: "账号注销",
type: "navigate",
url: "/pages/cancel-account/cancel-account",
},
// { label: '&', type: 'navigate', url: '/pages/agreement/agreement' }, // { label: '&', type: 'navigate', url: '/pages/agreement/agreement' },
{ label: "联系客服", type: "action", action: "contactService" }, { label: '联系客服', type: 'action', action: 'contactService' }
]); ])
// //
onMounted(() => { onMounted(() => {
getLoginUserPhoneInfo(); getLoginUserPhoneInfo()
}); })
const getLoginUserPhoneInfo = async () => { const getLoginUserPhoneInfo = async () => {
const res = await getLoginUserPhone(); const res = await getLoginUserPhone()
if (res.code === 0) { if (res.code === 0) {
userInfo.value.phone = res.data; userInfo.value.phone = res.data
} }
}; }
// //
const handleMenuClick = (item) => { const handleMenuClick = (item) => {
if (item.type === "navigate" && item.url) { if (item.type === 'navigate' && item.url) {
uni.navigateTo({ url: item.url }); uni.navigateTo({ url: item.url })
} else if (item.type === "action") { } else if (item.type === 'action') {
if (item.action === "contactService") { if (item.action === 'contactService') {
uni.showToast({ title: "联系客服功能待实现", icon: "none" }); uni.showToast({ title: '联系客服功能待实现', icon: 'none' })
} }
} }
}; }
// 退 // 退
const handleLogout = () => { const handleLogout = () => {
uni.showModal({ uni.showModal({
title: "温馨提示", title: '温馨提示',
content: "确定要退出登录吗?", content: '确定要退出登录吗?',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
uni.clearStorageSync(); uni.clearStorageSync()
uni.reLaunch({ url: "/pages/login/index" }); uni.reLaunch({ url: '/pages/login/index' })
} }
}, }
}); })
}; }
</script> </script>
<style scoped> <style scoped>

View File

@ -17,7 +17,7 @@
<ModuleTitle title="购买须知" /> <ModuleTitle title="购买须知" />
<zero-markdown-view :markdown="goodsData.commodityTip" :aiMode='true' /> <zero-markdown-view :markdown="goodsData.commodityTip" />
</view> </view>
</scroll-view> </scroll-view>

View File

@ -1,47 +0,0 @@
<template>
<view class="wave">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
</template>
<script></script>
<style lang="scss" scoped>
.wave {
position: relative;
text-align: center;
width: 30px;
.dot {
display: inline-block;
width: 3px;
height: 3px;
border-radius: 50%;
margin-right: 3px;
background: #333333;
animation: wave 1.3s linear infinite;
&:nth-child(2) {
animation-delay: -1.1s;
}
&:nth-child(3) {
animation-delay: -0.9s;
}
}
}
@keyframes wave {
0%,
60%,
100% {
transform: initial;
}
30% {
transform: translateY(-5px);
}
}
</style>

View File

@ -1,23 +0,0 @@
<template>
<view class="ai">
<view class="container">
<view class="c c4"></view>
<view class="c c1"></view>
<view class="c c2"></view>
<view class="c c3"></view>
<view class="rings"></view>
</view>
<view class="glass"></view>
</view>
</template>
<script setup>
</script>
<style lang="scss" scoped>
@import "styles/loading.scss";
</style>

View File

@ -1,281 +0,0 @@
@property --a {
syntax: "<angle>";
inherits: true;
initial-value: 0deg;
}
@property --l {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --x {
syntax: "<length>";
inherits: false;
initial-value: 0;
}
@property --y {
syntax: "<length>";
inherits: false;
initial-value: 0;
}
@property --o {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
.ai {
--s: 32px;
// --p: calc(var(--s) / 4);
width: var(--s);
aspect-ratio: 1;
--bg-color: color-mix(in srgb, #7b7bf4, transparent 90%);
background: radial-gradient(
60% 75% at center,
var(--bg-color) 50%,
transparent 50%
),
radial-gradient(75% 60% at center, var(--bg-color) 50%, transparent 50%);
padding: var(--p);
margin-right: 6px;
display: grid;
place-items: center;
position: relative;
border-radius: 50%;
transform: scale(1.0);
}
$count: 4;
:root {
--count: #{$count};
--radius: 6vmin;
}
@keyframes ai {
from {
--a: 360deg;
--l: 0.35;
--o: 1;
}
30% {
--l: 1.5;
}
70% {
--o: 0.4;
--l: 0.05;
}
98% {
--o: 0.7;
}
to {
--a: 0deg;
--l: 0.35;
--o: 1;
}
}
.c {
opacity: 0.9;
position: absolute;
width: 10vmin;
aspect-ratio: 1;
border-radius: 50%;
--offset-per-item: calc(360deg / var(--count));
--current-angle-offset: calc(var(--offset-per-item) * var(--i) + var(--a));
translate: calc(
cos(var(--current-angle-offset)) * var(--radius) + var(--x, 0)
)
calc(sin(var(--current-angle-offset)) * var(--radius) * -1);
scale: calc(0.6 + var(--l));
animation: ai 5.5s cubic-bezier(0.45, -0.35, 0.16, 1.5) infinite;
transition: opacity 0.3s linear;
opacity: var(--o, 1);
@for $i from 0 through $count {
&:nth-child(#{$i + 1}) {
--i: #{$i};
}
}
}
.c1 {
background: #79e3ee;
background: radial-gradient(50% 50% at center, #79e3ee, #e7e7fb);
background: radial-gradient(50% 50% at center, #c979ee, #74bcd6);
--x: 1vmin;
width: 16vmin;
animation-timing-function: cubic-bezier(0.12, 0.32, 0.68, 0.24);
}
.c2 {
background: radial-gradient(50% 50% at center, #ef788c, #e7e7fb);
width: 15vmin;
}
.c3 {
background: radial-gradient(50% 50% at center, #eb7fc6, transparent);
width: 5vmin;
opacity: 0.6;
--x: -1vmin;
}
.c4 {
background: #6d67c8;
animation-timing-function: cubic-bezier(0.39, -0.03, 0.75, 0.47);
}
.container {
overflow: hidden;
background: #b6a9f8;
width: 100%;
border-radius: 50%;
aspect-ratio: 1;
position: relative;
display: grid;
place-items: center;
}
.glass {
overflow: hidden;
position: absolute;
--w: 0.5vmin;
inset: calc(var(--s) - var(--s));
border-radius: 50%;
backdrop-filter: blur(1.3vmin);
box-shadow: 0 0 8vmin color-mix(in srgb, black, transparent 70%);
background: radial-gradient(
10vmin at 70% 30%,
rgba(255, 255, 255, 0.2),
transparent
);
// // lines
// &:after {
// content: "";
// position: absolute;
// inset: 0;
// --c: rgba(255, 255, 255, 0.03);
// --w: 0.0625rem;
// --g: 0.1875rem;
// background: repeating-linear-gradient(
// var(--c),
// var(--c),
// var(--w),
// transparent var(--w),
// transparent calc(var(--w) + var(--g))
// );
// border-radius: inherit;
// border: 1vmin rgba(255, 255, 255, 0.1) solid;
// }
}
@property --value {
syntax: "<angle>";
inherits: true;
initial-value: 0deg;
}
@property --width-ratio {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --scale {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
:root {
--width: 1vmin;
--duration: 8s;
}
.rings {
aspect-ratio: 1;
border-radius: 50%;
position: absolute;
inset: 0;
perspective: 11rem;
opacity: .9;
&:before,
&:after {
content: "";
position: absolute;
inset: 0;
background: rgba(255, 0, 0, 1);
border-radius: 50%;
--width-ratio: 1;
border: calc(var(--width) * var(--width-ratio)) solid transparent;
mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
background: linear-gradient(
white,
blue,
magenta,
violet,
lightyellow) border-box;
mask-composite: exclude;
animation: ring var(--duration) ease-in-out infinite;
--start: 180deg;
--value: var(--start);
--scale: 1;
transform: rotateY(var(--value)) rotateX(var(--value)) rotateZ(var(--value))
scale(var(--scale));
}
&:before {
--start: 180deg;
}
&:after {
--start: 90deg;
}
> .rings {
&:before {
--start: 360deg;
}
&:after {
--start: 270deg;
}
}
}
@keyframes ring {
from {
--value: var(--start);
--scale: 1;
}
50% {
--scale: 1.2;
--width-ratio: 1.5;
}
70% {
--scale: 1;
--value: calc(var(--start) + 180deg);
--width-ratio: 1;
}
80% {
--scale: 1.2;
--width-ratio: 1.5;
}
to {
--value: calc(var(--start) + 360deg);
--scale: 1;
--width-ratio: 1;
}
}

View File

@ -12,31 +12,32 @@
</template> </template>
<script setup> <script setup>
import { ref, nextTick, defineEmits } from "vue"; import { ref, nextTick, defineEmits } from 'vue'
import { onMounted } from "vue"; import { onMounted } from 'vue'
import { SCROLL_TO_BOTTOM } from "@/constant/constant"; import { SCROLL_TO_BOTTOM } from '@/constant/constant'
const props = defineProps({ const props = defineProps({
question: { question: {
type: String, type: String,
default: "", default: ''
}, }
}); })
const tags = ref([]); const tags = ref([])
const emits = defineEmits(["replySent"]); const emits = defineEmits(['replySent']);
const handleClick = (item) => { const handleClick = (item) => {
emits("replySent", item); emits('replySent', item)
}; }
onMounted(() => { onMounted(() => {
tags.value = props.question.split(/[&|;]/).filter((tag) => tag.trim() !== ""); tags.value = props.question.split(/[&|;]/).filter(tag => tag.trim() !== '')
nextTick(() => { nextTick(() => {
setTimeout(() => { setTimeout(() => {
uni.$emit(SCROLL_TO_BOTTOM, true); uni.$emit(SCROLL_TO_BOTTOM, true)
}, 300); }, 300)
}); });
}); })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -58,7 +59,7 @@ onMounted(() => {
} }
.tag-text { .tag-text {
color: #00a6ff; /* 蓝色文字,可根据设计调整 */ color: #00A6FF; /* 蓝色文字,可根据设计调整 */
font-size: 14px; font-size: 14px;
} }
</style> </style>

View File

@ -1,82 +1,82 @@
<template> <template>
<view class="container"> <view class="container">
<swiper <swiper class="swiper" circular
class="swiper" :indicator-dots="activityList.length > 1"
circular indicator-color="#FFFFFF"
:indicator-dots="activityList.length > 1" indicator-active-color="#00A6FF"
indicator-color="#FFFFFF" :autoplay="autoplay"
indicator-active-color="#00A6FF" :interval="interval"
:autoplay="autoplay" :duration="duration">
:interval="interval" <swiper-item v-for="item in activityList" :key="item.id">
:duration="duration" <view class="swiper-item" @click="handleClick(item)">
> <image :src="item.activityCover" mode="aspectFill"></image>
<swiper-item v-for="item in activityList" :key="item.id"> <view class="corner-btn">快速预定</view>
<view class="swiper-item" @click="handleClick(item)"> </view>
<image :src="item.activityCover" mode="aspectFill"></image> </swiper-item>
<view class="corner-btn">快速预定</view> </swiper>
</view>
</swiper-item> </view>
</swiper>
</view>
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import { ref } from 'vue'
import { SEND_COMMAND_TEXT } from "@/constant/constant"; import { SEND_COMMAND_TEXT } from '@/constant/constant'
const autoplay = ref(true); const autoplay = ref(true)
const interval = ref(3000); const interval = ref(3000)
const duration = ref(500); const duration = ref(500)
const props = defineProps({ const props = defineProps({
activityList: { activityList: {
type: Array, type: Array,
default: [], default: []
}, }
}); })
const handleClick = (item) => {
uni.$emit(SEND_COMMAND_TEXT, '快速预定')
}
const handleClick = (item) => {
uni.$emit(SEND_COMMAND_TEXT, "快速预定");
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.container { .container {
margin-bottom: 6px; margin-bottom: 6px;
.uni-margin-wrap { .uni-margin-wrap {
width: 100%; width: 100%;
} }
.swiper { .swiper {
height: 120px; height: 120px;
border-radius: 8px; border-radius: 8px;
} }
.swiper-item { .swiper-item {
position: relative; position: relative;
display: block; display: block;
height: 120px; height: 120px;
line-height: 120px; line-height: 120px;
text-align: center; text-align: center;
image { image {
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 8px; border-radius: 8px;
display: block; display: block;
} }
.corner-btn { .corner-btn {
position: absolute; position: absolute;
right: 12px; right: 12px;
bottom: 12px; bottom: 12px;
background-color: #ffeb00; background-color: #ffeb00;
color: #333; color: #333;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
padding: 4px 12px; padding: 4px 12px;
border-radius: 20px; border-radius: 20px;
line-height: 1.5; line-height: 1.5;
} }
} }
}
}
</style> </style>

View File

@ -22,6 +22,7 @@
<text class="calendar-text">日历</text> <text class="calendar-text">日历</text>
</view> </view>
</view> </view>
</view> </view>
</template> </template>

View File

@ -1,203 +1,214 @@
<template> <template>
<view class="container"> <view class="container">
<ModuleTitle :title="commodityDTO.title" /> <ModuleTitle :title="commodityDTO.title" />
<view class="container-scroll"> <view class="container-scroll">
<view <view
v-for="(item, index) in commodityDTO.commodityList" v-for="(item, index) in commodityDTO.commodityList"
:key="`${item.commodityId}-${index}`" :key="`${item.commodityId}-${index}`"
>
<view class="mk-card-item" @click="placeOrderHandle(item)">
<!-- <view class="card-badge">超值推荐</view> -->
<image class="card-img" :src="item.commodityIcon" mode="aspectFill" />
<view class="card-content">
<view class="card-title-column">
<text class="card-title">{{ item.commodityName }}</text>
<view
class="card-tags"
v-for="tag in item.commodityTradeRuleList"
:key="tag"
>
<text class="card-tag">{{ tag }}</text>
</view>
</view>
<template
v-for="(serviceItem, index) in item.commodityServices"
:key="serviceItem.serviceTitle"
> >
<view v-if="index < 3" class="card-desc" <view class="mk-card-item" @click="placeOrderHandle(item)">
>· {{ serviceItem.serviceTitle }}</view <!-- <view class="card-badge">超值推荐</view> -->
> <image
</template> class="card-img"
<view class="card-bottom-row"> :src="item.commodityIcon"
<view class="card-price-row"> mode="aspectFill"
<text class="card-price-fu"></text> />
<text class="card-price">{{ item.commodityPrice }}</text> <view class="card-content">
<text class="card-unit">/</text> <view class="card-title-column">
</view> <text class="card-title">{{
item.commodityName
}}</text>
<view
class="card-tags"
v-for="tag in item.commodityTradeRuleList"
:key="tag"
>
<text class="card-tag">{{ tag }}</text>
</view>
</view>
<template
v-for="(
serviceItem, index
) in item.commodityServices"
:key="serviceItem.serviceTitle"
>
<view v-if="index < 3" class="card-desc"
>· {{ serviceItem.serviceTitle }}</view
>
</template>
<view class="card-bottom-row">
<view class="card-price-row">
<text class="card-price-fu"></text>
<text class="card-price">{{
item.commodityPrice
}}</text>
<text class="card-unit">/</text>
</view>
<text class="card-btn">下单</text> <text class="card-btn">下单</text>
</view>
</view>
</view>
</view> </view>
</view>
</view> </view>
</view>
</view> </view>
</view>
</template> </template>
<script setup> <script setup>
import ModuleTitle from "@/components/ModuleTitle/index.vue"; import ModuleTitle from "@/components/ModuleTitle/index.vue";
import { defineProps } from "vue"; import { defineProps } from "vue";
const props = defineProps({ const props = defineProps({
commodityDTO: { commodityDTO: {
type: Object, type: Object,
default: {}, default: {},
}, },
}); });
/// ///
const placeOrderHandle = (item) => { const placeOrderHandle = (item) => {
uni.navigateTo({ uni.navigateTo({
url: `/pages/goods/index?commodityId=${item.commodityId}`, url: `/pages/goods/index?commodityId=${item.commodityId}`,
}); });
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
.container-scroll { .container-scroll {
display: flex;
flex-direction: row;
overflow-x: auto;
overflow-y: hidden;
margin: 4px 0;
.mk-card-item {
position: relative;
display: flex;
flex-direction: column;
align-items: start;
width: 188px;
// height: 244px;
background-color: #ffffff;
border-radius: 10px;
margin-right: 8px;
padding-bottom: 12px;
.card-badge {
position: absolute;
top: 8px;
left: 8px;
background: #ffe7b2;
color: #b97a00;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
z-index: 2;
}
.card-img {
width: 188px;
height: 114px;
border-radius: 10px;
object-fit: cover; /* 确保图片不变形,保持比例裁剪 */
flex-shrink: 0; /* 防止图片被压缩 */
}
.card-content {
box-sizing: border-box;
padding: 10px 12px 0 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: start;
width: 100%;
flex: 1; /* 让内容区域占据剩余空间 */
overflow: hidden; /* 防止内容溢出 */
}
.card-title-column {
display: flex;
align-items: start;
flex-direction: column;
width: 100%;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #222;
width: 100%;
/* 限制标题最多显示两行 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
max-height: 2.8em; /* 2行的高度 */
}
.card-tags {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: start; overflow-x: auto;
padding: 6px 0; overflow-y: hidden;
} margin: 4px 0;
.card-tag { .mk-card-item {
color: #ff6600; position: relative;
font-size: 10px;
border-radius: 4px;
padding: 0 6px;
margin-left: 2px;
border: 1px solid #ff6600;
}
.card-desc { display: flex;
font-size: 13px; flex-direction: column;
color: #888; align-items: start;
margin-top: 2px; width: 188px;
} // height: 244px;
background-color: #ffffff;
border-radius: 10px;
margin-right: 8px;
padding-bottom: 12px;
.card-bottom-row { .card-badge {
display: flex; position: absolute;
align-items: center; top: 8px;
justify-content: space-between; left: 8px;
margin-top: 8px; background: #ffe7b2;
width: 100%; color: #b97a00;
} font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
z-index: 2;
}
.card-price-row { .card-img {
.card-price-fu { width: 188px;
color: #ff6600; height: 114px;
font-size: 11px; border-radius: 10px;
font-weight: normal; object-fit: cover; /* 确保图片不变形,保持比例裁剪 */
flex-shrink: 0; /* 防止图片被压缩 */
}
.card-content {
box-sizing: border-box;
padding: 10px 12px 0 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: start;
width: 100%;
flex: 1; /* 让内容区域占据剩余空间 */
overflow: hidden; /* 防止内容溢出 */
}
.card-title-column {
display: flex;
align-items: start;
flex-direction: column;
width: 100%;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #222;
width: 100%;
/* 限制标题最多显示两行 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
max-height: 2.8em; /* 2行的高度 */
}
.card-tags {
display: flex;
flex-direction: row;
align-items: start;
padding: 6px 0;
}
.card-tag {
background: #f5f5f5;
color: #ff6600;
font-size: 10px;
border-radius: 4px;
padding: 0 6px;
margin-left: 2px;
border: 1px solid #ff6600;
}
.card-desc {
font-size: 13px;
color: #888;
margin-top: 2px;
}
.card-bottom-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
width: 100%;
}
.card-price-row {
.card-price-fu {
color: #ff6600;
font-size: 11px;
font-weight: normal;
}
.card-price {
color: #ff6600;
font-size: 16px;
font-weight: bold;
}
.card-unit {
font-size: 11px;
color: #888;
font-weight: normal;
margin-left: 2px;
}
}
.card-btn {
background: #ff6600;
color: #fff;
font-size: 15px;
border-radius: 20px;
padding: 0 18px;
height: 32px;
line-height: 32px;
}
} }
.card-price {
color: #ff6600;
font-size: 16px;
font-weight: bold;
}
.card-unit {
font-size: 11px;
color: #888;
font-weight: normal;
margin-left: 2px;
}
}
.card-btn {
background: #ff6600;
color: #fff;
font-size: 15px;
border-radius: 20px;
padding: 0 18px;
height: 32px;
line-height: 32px;
}
} }
}
} }
</style> </style>

View File

@ -1,33 +1,36 @@
<template> <template>
<view class="container"> <view class="container">
<template v-if="toolCall.picture && toolCall.picture.length > 0"> <template v-if="toolCall.picture && toolCall.picture.length > 0">
<ModuleTitle :title="图片详情" /> <ModuleTitle :title="图片详情" />
<ImageSwiper :images="toolCall.picture" /> <ImageSwiper :images="toolCall.picture" />
</template> </template>
<template v-if="toolCall.commodityList"> <template v-if="toolCall.commodityList">
<DetailCardGoodsContentList :commodityList="toolCall.commodityList" /> <DetailCardGoodsContentList :commodityList="toolCall.commodityList" />
</template> </template>
</view>
</view>
</template> </template>
<script setup> <script setup>
import { defineProps } from "vue"; import { defineProps } from 'vue'
import ModuleTitle from "@/components/ModuleTitle/index.vue"; import ModuleTitle from '@/components/ModuleTitle/index.vue'
import ImageSwiper from "@/components/ImageSwiper/index.vue"; import ImageSwiper from '@/components/ImageSwiper/index.vue'
import DetailCardGoodsContentList from "./DetailCardGoodsContentList.vue"; import DetailCardGoodsContentList from './DetailCardGoodsContentList.vue'
const props = defineProps({
toolCall: {
type: Object,
default: {}
}
})
const props = defineProps({
toolCall: {
type: Object,
default: {},
},
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.container { .container {
width: 100%; width: 100%;
padding: 12px 0; padding: 12px 0;
} }
</style> </style>

View File

@ -1,207 +1,214 @@
<template> <template>
<view class="container"> <view class="container">
<ModuleTitle title="相关商品" /> <ModuleTitle title="相关商品" />
<view class="container-scroll"> <view class="container-scroll">
<view <view
v-for="(item, index) in commodityList" v-for="(item, index) in commodityList"
:key="`${item.commodityId}-${index}`" :key="`${item.commodityId}-${index}`"
>
<view class="mk-card-item" @click="placeOrderHandle(item)">
<image
class="card-img"
:src="item.commodityPhoto"
mode="aspectFill"
/>
<view class="card-content">
<view class="card-title-column">
<text class="card-title">{{ item.commodityName }}</text>
<view
class="card-tags"
v-for="tag in item.commodityTradeRuleList"
:key="tag"
>
<text class="card-tag">{{ tag }}</text>
</view>
</view>
<template
v-for="(serviceItem, index) in item.commodityServices"
:key="serviceItem.serviceTitle"
> >
<view v-if="index < 3" class="card-desc" <view class="mk-card-item" @click="placeOrderHandle(item)">
>· {{ serviceItem.serviceTitle }}</view <image
> class="card-img"
</template> :src="item.commodityPhoto"
<view class="card-bottom-row"> mode="aspectFill"
<view class="card-price-row"> />
<text class="card-price-fu"></text> <view class="card-content">
<text class="card-price">{{ item.specificationPrice }}</text> <view class="card-title-column">
<text class="card-unit">/</text> <text class="card-title">{{
</view> item.commodityName
<text class="card-btn">下单</text> }}</text>
<view
class="card-tags"
v-for="tag in item.commodityTradeRuleList"
:key="tag"
>
<text class="card-tag">{{ tag }}</text>
</view>
</view>
<template
v-for="(
serviceItem, index
) in item.commodityServices"
:key="serviceItem.serviceTitle"
>
<view v-if="index < 3" class="card-desc"
>· {{ serviceItem.serviceTitle }}</view
>
</template>
<view class="card-bottom-row">
<view class="card-price-row">
<text class="card-price-fu"></text>
<text class="card-price">{{
item.specificationPrice
}}</text>
<text class="card-unit">/</text>
</view>
<text class="card-btn">下单</text>
</view>
</view>
</view>
</view> </view>
</view>
</view> </view>
</view>
</view> </view>
</view>
</template> </template>
<script setup> <script setup>
import ModuleTitle from "@/components/ModuleTitle/index.vue"; import ModuleTitle from "@/components/ModuleTitle/index.vue";
import { defineProps } from "vue"; import { defineProps } from "vue";
const props = defineProps({ const props = defineProps({
commodityList: { commodityList: {
type: Array, type: Array,
default: [], default: [],
}, },
}); });
/// ///
const placeOrderHandle = (item) => { const placeOrderHandle = (item) => {
uni.navigateTo({ uni.navigateTo({
url: `/pages/goods/index?commodityId=${item.commodityId}`, url: `/pages/goods/index?commodityId=${item.commodityId}`,
}); });
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
margin: 6px 0 0; margin: 6px 0 0;
.container-scroll { .container-scroll {
display: flex;
flex-direction: row;
overflow-x: auto;
overflow-y: hidden;
margin: 4px 0;
.mk-card-item {
position: relative;
display: flex;
flex-direction: column;
align-items: start;
width: 188px;
// height: 244px;
background-color: #ffffff;
border-radius: 10px;
margin-right: 8px;
padding-bottom: 12px;
.card-badge {
position: absolute;
top: 8px;
left: 8px;
background: #ffe7b2;
color: #b97a00;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
z-index: 2;
}
.card-img {
width: 188px;
height: 114px;
border-radius: 10px;
object-fit: cover; /* 确保图片不变形,保持比例裁剪 */
flex-shrink: 0; /* 防止图片被压缩 */
}
.card-content {
box-sizing: border-box;
padding: 10px 12px 0 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: start;
width: 100%;
flex: 1; /* 让内容区域占据剩余空间 */
overflow: hidden; /* 防止内容溢出 */
}
.card-title-column {
display: flex;
align-items: start;
flex-direction: column;
width: 100%;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #222;
width: 100%;
/* 限制标题最多显示两行 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
max-height: 2.8em; /* 2行的高度 */
}
.card-tags {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: start; overflow-x: auto;
padding: 6px 0; overflow-y: hidden;
} margin: 4px 0;
.card-tag { .mk-card-item {
color: #ff6600; position: relative;
font-size: 10px;
border-radius: 4px;
padding: 0 6px;
margin-left: 2px;
border: 1px solid #ff6600;
}
.card-desc { display: flex;
font-size: 13px; flex-direction: column;
color: #888; align-items: start;
margin-top: 2px; width: 188px;
} // height: 244px;
background-color: #ffffff;
border-radius: 10px;
margin-right: 8px;
padding-bottom: 12px;
.card-bottom-row { .card-badge {
display: flex; position: absolute;
align-items: center; top: 8px;
justify-content: space-between; left: 8px;
margin-top: 8px; background: #ffe7b2;
width: 100%; color: #b97a00;
} font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
z-index: 2;
}
.card-price-row { .card-img {
.card-price-fu { width: 188px;
color: #ff6600; height: 114px;
font-size: 11px; border-radius: 10px;
font-weight: normal; object-fit: cover; /* 确保图片不变形,保持比例裁剪 */
flex-shrink: 0; /* 防止图片被压缩 */
}
.card-content {
box-sizing: border-box;
padding: 10px 12px 0 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: start;
width: 100%;
flex: 1; /* 让内容区域占据剩余空间 */
overflow: hidden; /* 防止内容溢出 */
}
.card-title-column {
display: flex;
align-items: start;
flex-direction: column;
width: 100%;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #222;
width: 100%;
/* 限制标题最多显示两行 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
max-height: 2.8em; /* 2行的高度 */
}
.card-tags {
display: flex;
flex-direction: row;
align-items: start;
padding: 6px 0;
}
.card-tag {
background: #f5f5f5;
color: #ff6600;
font-size: 10px;
border-radius: 4px;
padding: 0 6px;
margin-left: 2px;
border: 1px solid #ff6600;
}
.card-desc {
font-size: 13px;
color: #888;
margin-top: 2px;
}
.card-bottom-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
width: 100%;
}
.card-price-row {
.card-price-fu {
color: #ff6600;
font-size: 11px;
font-weight: normal;
}
.card-price {
color: #ff6600;
font-size: 16px;
font-weight: bold;
}
.card-unit {
font-size: 11px;
color: #888;
font-weight: normal;
margin-left: 2px;
}
}
.card-btn {
background: #ff6600;
color: #fff;
font-size: 15px;
border-radius: 20px;
padding: 0 18px;
height: 32px;
line-height: 32px;
}
} }
.card-price {
color: #ff6600;
font-size: 16px;
font-weight: bold;
}
.card-unit {
font-size: 11px;
color: #888;
font-weight: normal;
margin-left: 2px;
}
}
.card-btn {
background: #ff6600;
color: #fff;
font-size: 15px;
border-radius: 20px;
padding: 0 18px;
height: 32px;
line-height: 32px;
}
} }
}
} }
</style> </style>

View File

@ -1,42 +1,43 @@
<template> <template>
<view class="container"> <view class="container">
<view v-for="item in themeDTOList" :key="item.title"> <view v-for="item in themeDTOList" :key="item.title">
<RecommendPostsList :recommendTheme="item" /> <RecommendPostsList :recommendTheme="item" />
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, nextTick } from "vue"; import { ref, nextTick } from 'vue'
import { onMounted } from "vue"; import { onMounted } from 'vue'
import { discoveryCradComponent } from "@/request/api/MainPageDataApi"; import { discoveryCradComponent } from '@/request/api/MainPageDataApi'
import RecommendPostsList from "../recommend/RecommendPostsList.vue"; import RecommendPostsList from '../recommend/RecommendPostsList.vue'
import { SCROLL_TO_BOTTOM } from "@/constant/constant"; import { SCROLL_TO_BOTTOM } from '@/constant/constant'
const themeDTOList = ref([]); const themeDTOList = ref([])
const loadDiscoveryCradComponent = async () => { const loadDiscoveryCradComponent = async () => {
const res = await discoveryCradComponent(); const res = await discoveryCradComponent()
if (res.code === 0 && res.data) { if(res.code === 0 && res.data) {
themeDTOList.value = res.data.themeDTOList; themeDTOList.value = res.data.themeDTOList
nextTick(() => { nextTick(() => {
setTimeout(() => { setTimeout(() => {
uni.$emit(SCROLL_TO_BOTTOM, true); uni.$emit(SCROLL_TO_BOTTOM, true)
}, 300); }, 300)
}); });
} }
}; }
onMounted(() => {
loadDiscoveryCradComponent()
})
onMounted(() => {
loadDiscoveryCradComponent();
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.container { .container {
width: 100%; width: 100%;
flex: 1; flex: 1;
margin-bottom: 12px; margin-bottom: 12px;
} }
</style> </style>

View File

@ -1,18 +1,18 @@
<template> <template>
<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" v-for="(item, index) in recommendTheme.recommendPostsList"
:key="index" :key="index"
> >
<view class="mk-card-item" @click="sendReply(item)"> <view class="mk-card-item" @click="sendReply(item)">
<image :src="item.coverPhoto" mode="widthFix"></image> <image :src="item.coverPhoto" mode="widthFix"></image>
<text>{{ item.topic }}</text> <text>{{ item.topic }}</text>
</view>
</view>
</view> </view>
</view>
</view> </view>
</view>
</template> </template>
<script setup> <script setup>
@ -20,48 +20,48 @@ import ModuleTitle from "@/components/ModuleTitle/index.vue";
import { RECOMMEND_POSTS_TITLE } from "@/constant/constant"; import { RECOMMEND_POSTS_TITLE } from "@/constant/constant";
import { defineProps } from "vue"; import { defineProps } from "vue";
const props = defineProps({ const props = defineProps({
recommendTheme: { recommendTheme: {
type: Object, type: Object,
default: {}, default: {},
}, },
}); });
const sendReply = (item) => { const sendReply = (item) => {
const topic = item.userInputContent || item.topic.replace(/^#/, ""); const topic = item.userInputContent || item.topic.replace(/^#/, "");
uni.$emit(RECOMMEND_POSTS_TITLE, topic); uni.$emit(RECOMMEND_POSTS_TITLE, topic);
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
.container-scroll { .container-scroll {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
overflow-x: auto; overflow-x: auto;
margin-top: 4px; margin-top: 4px;
.mk-card-item { .mk-card-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: start; align-items: start;
width: 188px; width: 188px;
height: 154px; height: 154px;
background-color: #ffffff; background-color: #ffffff;
border-radius: 10px; border-radius: 10px;
margin-right: 8px; margin-right: 8px;
image { image {
width: 188px; width: 188px;
height: 112px; height: 112px;
} }
text { text {
padding: 12px; padding: 12px;
text-align: center; text-align: center;
font-weight: 500; font-weight: 500;
font-size: 12px; font-size: 12px;
color: #333333; color: #333333;
} }
}
} }
}
} }
</style> </style>

View File

@ -1,27 +1,29 @@
<template> <template>
<view class="container"> <view class="container">
<view v-for="item in recommendThemeList" :key="item.themeName"> <view v-for="item in recommendThemeList" :key="item.themeName">
<RecommendPostsList :recommendTheme="item" /> <RecommendPostsList :recommendTheme="item" />
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import RecommendPostsList from "./RecommendPostsList.vue"; import RecommendPostsList from './RecommendPostsList.vue';
import { defineProps } from 'vue'
const props = defineProps({
recommendThemeList: {
type: Array,
default: []
}
})
import { defineProps } from "vue";
const props = defineProps({
recommendThemeList: {
type: Array,
default: [],
},
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.container { .container {
width: 100%; width: 100%;
flex: 1; flex: 1;
margin-bottom: 12px; margin-bottom: 12px;
} }
</style> </style>

View File

@ -1,50 +1,48 @@
<template> <template>
<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" <view class="mk-card-item" @click="sendReply(item)">
:key="index" <image :src="item.coverPhoto" mode="aspectFill"></image>
> <view class="overlay-gradient">
<view class="mk-card-item" @click="sendReply(item)"> <text class="overlay-text">{{ item.topic }}</text>
<image :src="item.coverPhoto" mode="aspectFill"></image> </view>
<view class="overlay-gradient"> </view>
<text class="overlay-text">{{ item.topic }}</text> </view>
</view> </view>
</view> </view>
</view>
</view>
</view>
</template> </template>
<script setup> <script setup>
import ModuleTitle from "@/components/ModuleTitle/index.vue"; import ModuleTitle from '@/components/ModuleTitle/index.vue'
import { defineProps } from "vue"; import { defineProps } from 'vue'
import { RECOMMEND_POSTS_TITLE } from "@/constant/constant.js"; import { RECOMMEND_POSTS_TITLE } from '@/constant/constant.js'
const props = defineProps({ const props = defineProps({
recommendTheme: { recommendTheme: {
type: Object, type: Object,
default: {}, default: {}
}, }
}); })
const sendReply = (item) => {
const topic = item.userInputContent || item.topic.replace(/^#/, '');
uni.$emit(RECOMMEND_POSTS_TITLE, topic);
}
const sendReply = (item) => {
const topic = item.userInputContent || item.topic.replace(/^#/, "");
uni.$emit(RECOMMEND_POSTS_TITLE, topic);
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100%; width: 100%;
} }
.container-scroll { .container-scroll {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
overflow-x: auto; overflow-x: auto;
margin: 4px 0 6px; margin-top: 4px;
.mk-card-item { .mk-card-item {
flex-shrink: 0; /* 关键:防止 flex 布局压缩子元素宽度 */ flex-shrink: 0; /* 关键:防止 flex 布局压缩子元素宽度 */
@ -89,4 +87,5 @@ const sendReply = (item) => {
} }
} }
} }
</style> </style>

View File

@ -2,11 +2,7 @@
<view class="notice-info mb12"> <view class="notice-info mb12">
<view class="notice-title"> 购买须知 </view> <view class="notice-title"> 购买须知 </view>
<zero-markdown-view <zero-markdown-view :markdown="orderData.commodityTip" :fontSize="14" />
:markdown="orderData.commodityTip"
:fontSize="14"
:aiMode="true"
/>
</view> </view>
</template> </template>

View File

@ -36,10 +36,6 @@ export default {
type: String, type: String,
default: '#2d2d2d' default: '#2d2d2d'
}, },
fontFamily: {
type: String,
default: 'PingFang SC, PingFang SC'
},
aiMode: { aiMode: {
type: Boolean, type: Boolean,
default: false default: false
@ -91,64 +87,59 @@ export default {
initTagStyle() { initTagStyle() {
const themeColor = this.themeColor const themeColor = this.themeColor
const codeBgColor = this.codeBgColor const codeBgColor = this.codeBgColor
const fontFamily = this.fontFamily
let zeroStyle = { let zeroStyle = {
p: ` p: `
margin:6px 0; margin:5px 5px;
font-size: 15px; font-size: 15px;
line-height:1.65; line-height:1.75;
font-family: ${fontFamily}; letter-spacing:0.2em;
word-spacing:0.1em;
`, `,
// //
h1: ` h1: `
margin:6px 0; margin:25px 0;
font-size: 24px; font-size: 24px;
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
padding:3px 10px 1px; padding:3px 10px 1px;
border-bottom: 2px solid ${themeColor}; border-bottom: 2px solid ${themeColor};
border-top-right-radius:3px; border-top-right-radius:3px;
border-top-left-radius:3px; border-top-left-radius:3px;
`, `,
// //
h2: ` h2: `
margin:6px 0; margin:40px 0 20px 0;
font-size: 20px; font-size: 20px;
text-align:center; text-align:center;
color:${themeColor}; color:${themeColor};
font-family: ${fontFamily};
font-weight:bolder; font-weight:bolder;
padding-left:10px; padding-left:10px;
// border:1px solid ${themeColor}; // border:1px solid ${themeColor};
`, `,
// //
h3: ` h3: `
margin:6px 0; margin:30px 0 10px 0;
font-size: 18px; font-size: 18px;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
padding-left:10px; padding-left:10px;
border-left:3px solid ${themeColor}; border-left:3px solid ${themeColor};
`, `,
// //
blockquote: ` blockquote: `
margin:6px 0; margin:15px 0;
font-size:15px; font-size:15px;
font-family: ${fontFamily};
color: #777777; color: #777777;
border-left: 4px solid #dddddd; border-left: 4px solid #dddddd;
padding: 0 10px; padding: 0 10px;
`, `,
// //
ul: ` ul: `
margin: 6px 0; margin: 10px 0;
color: #555; color: #555;
`, `,
li: ` li: `
margin: 4px 0; margin: 5px 0;
color: #555; color: #555;
`, `,
// //
@ -159,12 +150,10 @@ export default {
strong: ` strong: `
font-weight: border; font-weight: border;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
`, `,
// //
em: ` em: `
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
letter-spacing:0.3em; letter-spacing:0.3em;
`, `,
// 线 // 线
@ -205,72 +194,61 @@ export default {
initTagStyleForAi() { initTagStyleForAi() {
const themeColor = this.themeColor const themeColor = this.themeColor
const codeBgColor = this.codeBgColor const codeBgColor = this.codeBgColor
const fontFamily = this.fontFamily
let zeroStyle = { let zeroStyle = {
p: ` p: `
margin:6px 0; font-size: 16px;
font-size: 15px;
line-height:1.55;
font-family: ${fontFamily};
`, `,
// //
h1: ` h1: `
margin:4px 0; margin:12px 0 10px 0;
font-size: 20px; font-size: 22px;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
`, `,
// //
h2: ` h2: `
margin:4px 0; margin:10px 0 10px 0;
font-size: 18px; font-size: 20px;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
`, `,
// //
h3: ` h3: `
margin:4x 0; margin:8x 0 8px 0;
font-size: 16px; font-size: 18px;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
`, `,
// //
h4: ` h4: `
margin:4px 0; margin:6px 0 8px 0;
font-size: 15px; font-size: 16px;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
`, `,
// //
h5: ` h5: `
margin:4px 0; margin:6px 0 8px 0;
font-size: 14px; font-size: 16px;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
`, `,
// //
h6: ` h6: `
margin:4px 0; margin:6px 0 8px 0;
font-size: 12px; font-size: 16px;
color: ${themeColor}; color: ${themeColor}
font-family: ${fontFamily};
`, `,
// //
blockquote: ` blockquote: `
margin:4px 0; margin:12px 0;
font-size:14px; font-size:14px;
color: #777777; color: #777777;
border-left: 4px solid #dddddd; border-left: 4px solid #dddddd;
padding: 0 10px; padding: 0 10px;
font-family: ${fontFamily};
`, `,
// //
ul: ` ul: `
margin: 4px 0; margin: 8px 0;
color: #555; color: #555;
`, `,
li: ` li: `
margin: 4px 0; margin: 5px 0;
color: #555; color: #555;
`, `,
// //
@ -281,7 +259,6 @@ export default {
strong: ` strong: `
font-weight: border; font-weight: border;
color: ${themeColor}; color: ${themeColor};
font-family: ${fontFamily};
`, `,
// //
em: ` em: `
@ -295,14 +272,14 @@ export default {
border:none; border:none;
text-align:center; text-align:center;
background-image:linear-gradient(to right,rgba(248,57,41,0),${themeColor},rgba(248,57,41,0)); background-image:linear-gradient(to right,rgba(248,57,41,0),${themeColor},rgba(248,57,41,0));
margin:4px 0; margin:8px 0;
`, `,
// //
table: ` table: `
border-spacing:0; border-spacing:0;
overflow:auto; overflow:auto;
min-width:100%; min-width:100%;
margin:4px 0; margin:8px 0;
border-collapse: collapse; border-collapse: collapse;
`, `,
th: ` th: `
@ -319,7 +296,6 @@ export default {
background: ${codeBgColor}; background: ${codeBgColor};
font-size:12px; font-size:12px;
position: relative; position: relative;
font-family: ${fontFamily};
`, `,
} }
this.tagStyle = zeroStyle this.tagStyle = zeroStyle

View File

@ -7,7 +7,7 @@ class TypewriterManager {
// 配置选项 // 配置选项
this.options = { this.options = {
typingSpeed: 50, // 打字速度(毫秒) typingSpeed: 50, // 打字速度(毫秒)
cursorText: '', // 光标样式 cursorText: '<text class="typing-cursor">|</text>', // 光标样式
...options ...options
}; };
@ -59,7 +59,7 @@ class TypewriterManager {
if (this.isTyping) return; if (this.isTyping) return;
this.isTyping = true; this.isTyping = true;
// 不要在启动时重置displayedContent而是从当前位置继续 this.displayedContent = "";
this._typeNextChar(); this._typeNextChar();
} }
@ -75,7 +75,7 @@ class TypewriterManager {
this.displayedContent.length + 1 this.displayedContent.length + 1
); );
const displayContent = this.displayedContent; const displayContent = this.displayedContent + this.options.cursorText;
// 调用内容更新回调 // 调用内容更新回调
if (this.onContentUpdate) { if (this.onContentUpdate) {
@ -87,12 +87,10 @@ class TypewriterManager {
this.onCharacterTyped(this.displayedContent); this.onCharacterTyped(this.displayedContent);
} }
// 确保最小延迟,避免定时器堆积 // 继续下一个字符
const delay = Math.max(this.options.typingSpeed, 30); // 设置最小延迟30ms
this.typewriterTimer = setTimeout(() => { this.typewriterTimer = setTimeout(() => {
this._typeNextChar(); this._typeNextChar();
}, delay); }, this.options.typingSpeed);
} else { } else {
// 打字完成,移除光标 // 打字完成,移除光标
if (this.onContentUpdate) { if (this.onContentUpdate) {