feat: update AigcPhotoCard, FaqHelpCard, MapNavigationCard, NoticeCard, ScenicImageCard with new layouts, styles, and mock data; enhance user interaction and visual appeal
This commit is contained in:
@@ -1,31 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<CardShell class="aigc-photo-card p-16" variant="soft">
|
<view
|
||||||
<view class="aigc-photo-card__body flex flex-items-center gap-12">
|
class="aigc-photo-card relative rounded-24 overflow-hidden w-full"
|
||||||
<view class="aigc-photo-card__icon flex flex-items-center flex-justify-center w-44 h-44 rounded-16 bg-CCFBF1 font-size-22 font-900">{{ data.icon }}</view>
|
:class="{ 'is-disabled': disabled }"
|
||||||
<view class="aigc-photo-card__content flex-full">
|
@click="handleAction"
|
||||||
<view class="aigc-photo-card__title font-size-16 font-900">{{ data.title }}</view>
|
>
|
||||||
<view class="aigc-photo-card__subtitle color-0F766E font-size-11 font-700">{{ data.subtitle }}</view>
|
<image
|
||||||
</view>
|
class="aigc-photo-card__image block w-full"
|
||||||
</view>
|
:src="data.cover"
|
||||||
<view class="aigc-photo-card__preview grid grid-cols-3 gap-8 mt-14">
|
mode="aspectFill"
|
||||||
<MediaFrame
|
|
||||||
v-for="(image, index) in images"
|
|
||||||
:key="image || index"
|
|
||||||
class="aigc-photo-card__image h-82 rounded-14"
|
|
||||||
:src="image"
|
|
||||||
/>
|
/>
|
||||||
|
<view class="aigc-photo-card__shade"></view>
|
||||||
|
|
||||||
|
<view class="aigc-photo-card__play flex flex-items-center flex-justify-center rounded-full">
|
||||||
|
<view class="aigc-photo-card__triangle"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="aigc-photo-card__title color-white font-size-16 font-900">
|
||||||
|
{{ data.title }}
|
||||||
</view>
|
</view>
|
||||||
<view class="aigc-photo-card__button mt-14 p-12 rounded-16 color-white bg-0F766E font-size-13 font-900 text-center" :class="{ 'is-disabled': disabled }" @click="handleAction">
|
|
||||||
{{ data.buttonText }}
|
|
||||||
</view>
|
</view>
|
||||||
</CardShell>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
|
||||||
import CardShell from "../SharedVisual/CardShell.vue";
|
|
||||||
import MediaFrame from "../SharedVisual/MediaFrame.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -39,8 +35,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(["select", "action"]);
|
const emit = defineEmits(["select", "action"]);
|
||||||
|
|
||||||
const images = computed(() => props.data.images || []);
|
|
||||||
|
|
||||||
const handleAction = () => {
|
const handleAction = () => {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
emit("select", props.data);
|
emit("select", props.data);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
export default {
|
export default {
|
||||||
icon: "AI",
|
id: "xiaoqikong-flying",
|
||||||
title: "生成一张景区合照",
|
title: "沉浸式飞越 · 小七孔",
|
||||||
subtitle: "上传照片后生成同框纪念照",
|
cover: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=1000&q=80",
|
||||||
buttonText: "开始生成",
|
action: {
|
||||||
images: [
|
type: "play",
|
||||||
"https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=400&q=80",
|
},
|
||||||
"https://images.unsplash.com/photo-1529156069898-49953e39b3ac?auto=format&fit=crop&w=400&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=400&q=80",
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,35 +1,54 @@
|
|||||||
.aigc-photo-card {
|
.aigc-photo-card {
|
||||||
background: linear-gradient(145deg, #ffffff 0%, #f0fdfa 100%);
|
height: 272px;
|
||||||
|
background: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aigc-photo-card__body {
|
.aigc-photo-card:active {
|
||||||
|
opacity: 0.86;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aigc-photo-card__icon {
|
.aigc-photo-card.is-disabled {
|
||||||
color: #0f766e;
|
opacity: 0.55;
|
||||||
}
|
|
||||||
|
|
||||||
.aigc-photo-card__content {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aigc-photo-card__title {
|
|
||||||
color: #134e4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aigc-photo-card__subtitle {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aigc-photo-card__preview {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.aigc-photo-card__image {
|
.aigc-photo-card__image {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aigc-photo-card__button {
|
.aigc-photo-card__shade {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(15, 23, 42, 0.38);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aigc-photo-card__button.is-disabled {
|
.aigc-photo-card__play {
|
||||||
opacity: 0.55;
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.48);
|
||||||
|
background: rgba(255, 255, 255, 0.28);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aigc-photo-card__triangle {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin-left: 6px;
|
||||||
|
border-top: 17px solid transparent;
|
||||||
|
border-bottom: 17px solid transparent;
|
||||||
|
border-left: 23px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aigc-photo-card__title {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 72px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 22px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.32);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<CardShell class="faq-help-card p-16" variant="soft">
|
<view class="faq-help-card bg-white rounded-24 w-full">
|
||||||
<view class="faq-help-card__header flex flex-justify-between gap-12">
|
<view class="faq-help-card__header flex flex-items-center">
|
||||||
<view>
|
<view class="faq-help-card__icon flex flex-items-center flex-justify-center rounded-full font-size-16 font-900">
|
||||||
<view class="faq-help-card__title color-1E293B font-size-16 font-900">{{ data.title }}</view>
|
{{ data.icon }}
|
||||||
<view class="faq-help-card__subtitle color-94A3B8 font-size-11 font-700">{{ data.subtitle }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
<BadgePill v-if="data.badge" :label="data.badge" tone="blue" />
|
<view class="faq-help-card__title color-1E293B font-size-20 font-900">
|
||||||
|
{{ data.title }}
|
||||||
</view>
|
</view>
|
||||||
<view class="faq-help-card__list flex flex-col gap-8">
|
</view>
|
||||||
|
|
||||||
|
<view class="faq-help-card__divider"></view>
|
||||||
|
|
||||||
|
<view class="faq-help-card__list">
|
||||||
<view
|
<view
|
||||||
v-for="item in questions"
|
v-for="item in questions"
|
||||||
:key="item.id || item.question"
|
:key="item.id || item.text"
|
||||||
class="faq-help-card__item p-12 rounded-16 bg-F8FAFC"
|
class="faq-help-card__item flex flex-items-center"
|
||||||
:class="{ 'is-open': openedId === item.id }"
|
:class="{ 'is-disabled': disabled }"
|
||||||
@click="toggle(item)"
|
@click="handleSelect(item)"
|
||||||
>
|
>
|
||||||
<view class="faq-help-card__question flex flex-justify-between gap-12 color-1E293B font-size-13 font-900">
|
<view class="faq-help-card__question color-475569 font-size-18 font-900 ellipsis-1 flex-full">
|
||||||
<text>{{ item.question }}</text>
|
{{ item.text }}
|
||||||
<text class="faq-help-card__arrow color-2563EB">{{ openedId === item.id ? "−" : "+" }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view v-if="openedId === item.id" class="faq-help-card__answer color-64748B font-size-12 font-700">
|
<view class="faq-help-card__arrow color-CBD5E1 font-size-22 font-900 flex-shrink-0">›</view>
|
||||||
{{ item.answer }}
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</CardShell>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed } from "vue";
|
||||||
import CardShell from "../SharedVisual/CardShell.vue";
|
|
||||||
import BadgePill from "../SharedVisual/BadgePill.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
@@ -45,12 +44,10 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(["select"]);
|
const emit = defineEmits(["select"]);
|
||||||
|
|
||||||
const openedId = ref("");
|
|
||||||
const questions = computed(() => props.data.questions || []);
|
const questions = computed(() => props.data.questions || []);
|
||||||
|
|
||||||
const toggle = (item) => {
|
const handleSelect = (item) => {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
openedId.value = openedId.value === item.id ? "" : item.id;
|
|
||||||
emit("select", item);
|
emit("select", item);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
export default {
|
export default {
|
||||||
title: "常见问题",
|
id: "faq-help",
|
||||||
subtitle: "快速了解游览和服务信息",
|
icon: "?",
|
||||||
badge: "FAQ",
|
title: "您可能还想了解",
|
||||||
questions: [
|
questions: [
|
||||||
{ id: "q1", question: "景区几点停止入园?", answer: "通常闭园前 1 小时停止入园,节假日以现场公告为准。" },
|
{
|
||||||
{ id: "q2", question: "可以携带宠物吗?", answer: "多数室内区域不支持宠物进入,导盲犬等服务犬除外。" },
|
id: "shuttle-time",
|
||||||
{ id: "q3", question: "下雨还能游玩吗?", answer: "小雨可游览,雨后水景更好,但请注意栈道湿滑。" },
|
text: "观光车末班车发车时间",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "power-bank",
|
||||||
|
text: "景区内哪里有共享充电宝",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pet-policy",
|
||||||
|
text: "可以带宠物入园吗",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,36 +1,54 @@
|
|||||||
.faq-help-card {
|
.faq-help-card {
|
||||||
|
padding: 34px 30px 26px;
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.72);
|
||||||
|
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.05);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-help-card__header {
|
.faq-help-card__header {
|
||||||
margin-bottom: 12px;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-help-card__icon {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 2px solid #10b981;
|
||||||
|
color: #10b981;
|
||||||
|
line-height: 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-help-card__title {
|
.faq-help-card__title {
|
||||||
|
line-height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-help-card__subtitle {
|
.faq-help-card__divider {
|
||||||
margin-top: 4px;
|
height: 1px;
|
||||||
}
|
margin: 24px 0 18px;
|
||||||
|
background: #f1f5f9;
|
||||||
.faq-help-card__list {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-help-card__item {
|
.faq-help-card__item {
|
||||||
|
min-height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-help-card__item.is-open {
|
.faq-help-card__item:active {
|
||||||
background: #eff6ff;
|
opacity: 0.86;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-help-card__item.is-disabled {
|
||||||
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-help-card__question {
|
.faq-help-card__question {
|
||||||
|
min-width: 0;
|
||||||
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-help-card__arrow {
|
.faq-help-card__arrow {
|
||||||
font-size: 18px;
|
width: 22px;
|
||||||
line-height: 14px;
|
margin-left: 16px;
|
||||||
}
|
text-align: right;
|
||||||
|
line-height: 24px;
|
||||||
.faq-help-card__answer {
|
|
||||||
margin-top: 8px;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<CardShell class="map-navigation-card" variant="soft">
|
<view class="map-navigation-card bg-white rounded-24 overflow-hidden w-full">
|
||||||
<view class="map-navigation-card__map">
|
<view class="map-navigation-card__media">
|
||||||
<MediaFrame class="map-navigation-card__image h-178" :src="data.mapImage" />
|
<image
|
||||||
<view class="map-navigation-card__pin color-334155 font-size-10 font-900">{{ data.pinText }}</view>
|
class="map-navigation-card__image block w-full"
|
||||||
|
:src="data.image"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view class="map-navigation-card__badge flex flex-items-center color-white font-size-12 font-900">
|
||||||
|
<text class="map-navigation-card__badge-icon">{{ badge.icon }}</text>
|
||||||
|
<text>{{ badge.text }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="map-navigation-card__bar flex flex-items-center gap-12">
|
</view>
|
||||||
|
|
||||||
|
<view class="map-navigation-card__content flex flex-items-center">
|
||||||
<view class="map-navigation-card__info flex-full">
|
<view class="map-navigation-card__info flex-full">
|
||||||
<view class="map-navigation-card__title color-1E293B font-size-14 font-900 ellipsis-1">{{ data.title }}</view>
|
<view class="map-navigation-card__title color-1E293B font-size-18 font-900 ellipsis-1">
|
||||||
<view class="map-navigation-card__distance color-94A3B8 font-size-11 font-800 ellipsis-1">{{ data.distance }}</view>
|
{{ data.title }}
|
||||||
|
</view>
|
||||||
|
<view class="map-navigation-card__distance color-94A3B8 font-size-14 font-900 ellipsis-1">
|
||||||
|
{{ data.distance }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="map-navigation-card__button flex flex-items-center flex-justify-center bg-0F172A color-white font-size-18 font-900"
|
||||||
|
:class="{ 'is-disabled': disabled }"
|
||||||
|
@click="handleAction"
|
||||||
|
>
|
||||||
|
<text class="map-navigation-card__button-icon">{{ action.icon }}</text>
|
||||||
|
<text>{{ action.text }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="map-navigation-card__button rounded-50 color-white bg-0F172A font-size-12 font-900" :class="{ 'is-disabled': disabled }" @click="handleAction">
|
|
||||||
{{ data.buttonText }}
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</CardShell>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import CardShell from "../SharedVisual/CardShell.vue";
|
import { computed } from "vue";
|
||||||
import MediaFrame from "../SharedVisual/MediaFrame.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
@@ -33,6 +49,9 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(["select", "action"]);
|
const emit = defineEmits(["select", "action"]);
|
||||||
|
|
||||||
|
const badge = computed(() => props.data.badge || {});
|
||||||
|
const action = computed(() => props.data.action || {});
|
||||||
|
|
||||||
const handleAction = () => {
|
const handleAction = () => {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
emit("select", props.data);
|
emit("select", props.data);
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
export default {
|
export default {
|
||||||
mapImage: "https://images.unsplash.com/photo-1524661135-423995f22d0b?auto=format&fit=crop&w=900&q=80",
|
id: "xiaoqikong-bridge-nav",
|
||||||
pinText: "目的地",
|
title: "前往小七孔桥",
|
||||||
title: "前往翠谷瀑布",
|
image: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=1000&q=80",
|
||||||
distance: "距你 500m · 步行约 8 分钟",
|
distance: "步行约 450 米",
|
||||||
buttonText: "导航",
|
badge: {
|
||||||
|
icon: "⌖",
|
||||||
|
text: "目的地",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
icon: "↗",
|
||||||
|
text: "导航",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
.map-navigation-card__map {
|
.map-navigation-card {
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.72);
|
||||||
|
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.05);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-navigation-card__media {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__image {
|
.map-navigation-card__image {
|
||||||
|
height: 248px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__pin {
|
.map-navigation-card__badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 18px;
|
left: 16px;
|
||||||
left: 18px;
|
bottom: 16px;
|
||||||
padding: 5px 9px;
|
height: 34px;
|
||||||
border-radius: 999px;
|
padding: 0 12px;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
border-radius: 8px;
|
||||||
|
background: rgba(15, 23, 42, 0.72);
|
||||||
|
line-height: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__bar {
|
.map-navigation-card__badge-icon {
|
||||||
padding: 0 14px 14px;
|
margin-right: 6px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-navigation-card__content {
|
||||||
|
min-height: 96px;
|
||||||
|
padding: 20px 26px 22px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__info {
|
.map-navigation-card__info {
|
||||||
@@ -24,16 +40,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__title {
|
.map-navigation-card__title {
|
||||||
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__distance {
|
.map-navigation-card__distance {
|
||||||
margin-top: 4px;
|
margin-top: 6px;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__button {
|
.map-navigation-card__button {
|
||||||
padding: 9px 14px;
|
width: 108px;
|
||||||
|
height: 58px;
|
||||||
|
margin-left: 18px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-navigation-card__button:active {
|
||||||
|
opacity: 0.86;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-navigation-card__button.is-disabled {
|
.map-navigation-card__button.is-disabled {
|
||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-navigation-card__button-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 19px;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,56 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="notice-card">
|
<view class="notice-card w-full">
|
||||||
<CardShell v-if="!activeNotice" class="notice-card__list p-16" variant="soft">
|
<view v-if="!detailOpen" class="notice-card__summary bg-FFFBEB rounded-24 w-full">
|
||||||
<view class="notice-card__header flex flex-justify-between gap-12">
|
<view class="notice-card__summary-title color-B45309 font-size-16 font-900">
|
||||||
<view>
|
{{ summary.title }}
|
||||||
<view class="notice-card__title color-1E293B font-size-16 font-900">{{ data.title }}</view>
|
|
||||||
<view class="notice-card__subtitle color-94A3B8 font-size-11 font-700">{{ data.subtitle }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
<BadgePill v-if="data.badge" :label="data.badge" tone="rose" />
|
<view class="notice-card__summary-content color-D97706 font-size-14 font-900">
|
||||||
|
{{ summary.content }}
|
||||||
|
</view>
|
||||||
|
<view class="notice-card__summary-footer flex flex-items-center flex-justify-between">
|
||||||
|
<view class="notice-card__summary-time color-D97706 font-size-12 font-900">
|
||||||
|
{{ summary.publishTime }}
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
v-for="item in notices"
|
class="notice-card__summary-link color-B45309 font-size-14 font-900"
|
||||||
:key="item.id || item.title"
|
|
||||||
class="notice-card__item flex gap-12 border-bottom-F1F5F9"
|
|
||||||
:class="{ 'is-disabled': disabled }"
|
:class="{ 'is-disabled': disabled }"
|
||||||
@click="openNotice(item)"
|
@click="openDetail"
|
||||||
>
|
>
|
||||||
<view class="notice-card__item-body flex-full">
|
{{ summary.actionText }}
|
||||||
<view class="notice-card__item-title color-1E293B font-size-13 font-900 ellipsis-1">{{ item.title }}</view>
|
</view>
|
||||||
<view class="notice-card__item-desc color-94A3B8 font-size-11 font-700 ellipsis-2">{{ item.summary }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-card__item-date color-E11D48 font-size-10 font-900">{{ item.date }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
</CardShell>
|
|
||||||
|
|
||||||
<DetailShell
|
<view v-else class="notice-card__detail bg-white rounded-24 overflow-hidden w-full">
|
||||||
v-else
|
<view class="notice-card__detail-head flex flex-items-center">
|
||||||
:title="activeNotice.title"
|
|
||||||
:tag="activeNotice.type"
|
|
||||||
tone="rose"
|
|
||||||
@back="closeNotice"
|
|
||||||
>
|
|
||||||
<view class="notice-card__detail p-16">
|
|
||||||
<MediaFrame v-if="activeNotice.cover" class="notice-card__cover h-154" :src="activeNotice.cover" />
|
|
||||||
<view class="notice-card__meta color-94A3B8 font-size-11 font-800">{{ activeNotice.date }}</view>
|
|
||||||
<view
|
<view
|
||||||
v-for="(paragraph, index) in paragraphs"
|
class="notice-card__back flex flex-items-center flex-justify-center rounded-full flex-shrink-0"
|
||||||
:key="index"
|
@click="closeDetail"
|
||||||
class="notice-card__paragraph color-475569 font-size-13 font-700"
|
|
||||||
>
|
>
|
||||||
{{ paragraph }}
|
‹
|
||||||
|
</view>
|
||||||
|
<view class="notice-card__head-title color-1E293B font-size-18 font-900">
|
||||||
|
{{ detail.navTitle }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<image
|
||||||
|
class="notice-card__image block w-full"
|
||||||
|
:src="detail.image"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<view class="notice-card__detail-body">
|
||||||
|
<view class="notice-card__detail-title color-1E293B font-size-20 font-900">
|
||||||
|
{{ detail.title }}
|
||||||
|
</view>
|
||||||
|
<view class="notice-card__time-pill flex flex-items-center bg-F8FAFC color-475569 font-size-13 font-900">
|
||||||
|
<text class="notice-card__time-icon">{{ detail.timeIcon }}</text>
|
||||||
|
<text>{{ detail.publishTime }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-card__divider"></view>
|
||||||
|
<view
|
||||||
|
v-for="paragraph in paragraphs"
|
||||||
|
:key="paragraph.id"
|
||||||
|
class="notice-card__paragraph color-475569 font-size-14 font-800"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
v-for="segment in paragraph.segments"
|
||||||
|
:key="segment.text"
|
||||||
|
:class="{ 'notice-card__strong': segment.strong }"
|
||||||
|
>
|
||||||
|
{{ segment.text }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</DetailShell>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import CardShell from "../SharedVisual/CardShell.vue";
|
|
||||||
import DetailShell from "../SharedVisual/DetailShell.vue";
|
|
||||||
import BadgePill from "../SharedVisual/BadgePill.vue";
|
|
||||||
import MediaFrame from "../SharedVisual/MediaFrame.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
@@ -63,20 +81,22 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["select", "back"]);
|
const emit = defineEmits(["select", "back", "action"]);
|
||||||
|
|
||||||
const activeNotice = ref(null);
|
const detailOpen = ref(false);
|
||||||
const notices = computed(() => props.data.notices || []);
|
const summary = computed(() => props.data.summary || {});
|
||||||
const paragraphs = computed(() => activeNotice.value?.paragraphs || []);
|
const detail = computed(() => props.data.detail || {});
|
||||||
|
const paragraphs = computed(() => detail.value.paragraphs || []);
|
||||||
|
|
||||||
const openNotice = (item) => {
|
const openDetail = () => {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
activeNotice.value = item;
|
detailOpen.value = true;
|
||||||
emit("select", item);
|
emit("select", props.data);
|
||||||
|
emit("action", props.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeNotice = () => {
|
const closeDetail = () => {
|
||||||
activeNotice.value = null;
|
detailOpen.value = false;
|
||||||
emit("back");
|
emit("back");
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,24 +1,49 @@
|
|||||||
export default {
|
export default {
|
||||||
title: "景区公告",
|
id: "wolongtan-trail-notice",
|
||||||
subtitle: "重要通知与运营提醒",
|
summary: {
|
||||||
badge: "公告",
|
title: "卧龙潭",
|
||||||
notices: [
|
content: "卧龙潭排队严重。",
|
||||||
|
publishTime: "发布时间:2025年7月12日 09:30",
|
||||||
|
actionText: "详情",
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
navTitle: "交通公告",
|
||||||
|
image: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=1000&q=80",
|
||||||
|
title: "卧龙潭至鸳鸯湖步道临时封闭通知",
|
||||||
|
timeIcon: "◷",
|
||||||
|
publishTime: "发布时间:2025年7月12日 09:30",
|
||||||
|
paragraphs: [
|
||||||
{
|
{
|
||||||
id: "notice-1",
|
id: "rain",
|
||||||
title: "观光车运营时间调整",
|
segments: [
|
||||||
summary: "因道路维护,今日 16:30 后部分站点临时调整。",
|
{
|
||||||
date: "今日",
|
text: "因近日持续降雨导致景区内水位明显上升,为保障游客人身安全,景区管理部门决定自即日起临时封闭",
|
||||||
type: "运营",
|
|
||||||
cover: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=900&q=80",
|
|
||||||
paragraphs: ["今日 16:30 后,水上森林站点将临时调整至游客中心东侧。", "已购票游客可凭原票乘坐接驳车,请根据现场指引有序排队。"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "notice-2",
|
text: "卧龙潭至鸳鸯湖沉溪步道",
|
||||||
title: "雨后栈道防滑提醒",
|
strong: true,
|
||||||
summary: "部分亲水路段地面湿滑,请穿防滑鞋并照看儿童。",
|
},
|
||||||
date: "昨天",
|
{
|
||||||
type: "安全",
|
text: ",封闭时间视水位情况另行通知。",
|
||||||
paragraphs: ["雨后栈道区域湿滑,请勿奔跑或翻越护栏。", "如遇临时封闭,请服从现场工作人员安排。"],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "route",
|
||||||
|
segments: [
|
||||||
|
{
|
||||||
|
text: "受影响游客可改走景区主干道绕行,全程增加约15分钟。观光车服务正常运营,建议优先乘车前往鸳鸯湖区域。",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "reopen",
|
||||||
|
segments: [
|
||||||
|
{
|
||||||
|
text: "如水位下降恢复安全标准,景区将第一时间通过广播及公告重新开放步道,请游客留意景区实时公告。",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,59 +2,122 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__list {
|
.notice-card__summary,
|
||||||
|
.notice-card__detail {
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__header {
|
.notice-card__summary {
|
||||||
margin-bottom: 12px;
|
padding: 16px 20px 14px;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
box-shadow: 0 8px 22px rgba(245, 158, 11, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__title {
|
.notice-card__summary-title {
|
||||||
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__subtitle {
|
.notice-card__summary-content {
|
||||||
margin-top: 4px;
|
margin-top: 6px;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__item {
|
.notice-card__summary-footer {
|
||||||
padding: 13px 0;
|
min-width: 0;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__item:last-child {
|
.notice-card__summary-time {
|
||||||
border-bottom: none;
|
min-width: 0;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__item.is-disabled {
|
.notice-card__summary-link {
|
||||||
|
margin-left: 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-decoration: underline;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__summary-link:active {
|
||||||
|
opacity: 0.82;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__summary-link.is-disabled {
|
||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__item-body {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-card__item-title {
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-card__item-desc {
|
|
||||||
margin-top: 5px;
|
|
||||||
line-height: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-card__item-date {
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-card__detail {
|
.notice-card__detail {
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.72);
|
||||||
|
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__cover {
|
.notice-card__detail-head {
|
||||||
margin-bottom: 14px;
|
height: 68px;
|
||||||
|
padding: 0 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__meta {
|
.notice-card__back {
|
||||||
margin-bottom: 12px;
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
margin-right: 14px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__head-title {
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__image {
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__detail-body {
|
||||||
|
padding: 22px 20px 28px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__detail-title {
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__time-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 34px;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 999px;
|
||||||
|
line-height: 34px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__time-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__divider {
|
||||||
|
height: 1px;
|
||||||
|
margin: 26px 0 20px;
|
||||||
|
background: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card__paragraph {
|
.notice-card__paragraph {
|
||||||
margin-bottom: 12px;
|
line-height: 30px;
|
||||||
line-height: 22px;
|
}
|
||||||
|
|
||||||
|
.notice-card__paragraph + .notice-card__paragraph {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card__strong {
|
||||||
|
color: #1e293b;
|
||||||
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<CardShell class="scenic-image-card" variant="soft" pressable :disabled="disabled" @select="handleAction">
|
<view
|
||||||
<view class="scenic-image-card__image-wrap relative">
|
class="scenic-image-card relative rounded-24 overflow-hidden w-full"
|
||||||
<MediaFrame class="scenic-image-card__image h-260" :src="data.image" />
|
:class="{ 'is-disabled': disabled }"
|
||||||
|
@click="handleSelect"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="scenic-image-card__image block w-full"
|
||||||
|
:src="data.image"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="scenic-image-card__expand flex flex-items-center flex-justify-center rounded-full color-white font-size-18 font-900"
|
||||||
|
@click.stop="handleAction"
|
||||||
|
>
|
||||||
|
{{ action.icon }}
|
||||||
|
</view>
|
||||||
|
|
||||||
<view v-if="hasCaption" class="scenic-image-card__caption">
|
<view v-if="hasCaption" class="scenic-image-card__caption">
|
||||||
<view class="scenic-image-card__caption-title color-white font-size-14 font-900">{{ data.caption.title }}</view>
|
<view v-if="caption.title" class="scenic-image-card__title color-white font-size-18 font-900 ellipsis-1">
|
||||||
<view v-if="data.caption.subtitle" class="scenic-image-card__caption-subtitle font-size-11 font-700">
|
{{ caption.title }}
|
||||||
{{ data.caption.subtitle }}
|
</view>
|
||||||
|
<view v-if="caption.subtitle" class="scenic-image-card__subtitle color-white font-size-14 font-900 ellipsis-1">
|
||||||
|
{{ caption.subtitle }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="scenic-image-card__expand absolute flex flex-items-center flex-justify-center w-32 h-32 rounded-full color-white font-size-16 font-900">⛶</view>
|
|
||||||
</view>
|
</view>
|
||||||
</CardShell>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import CardShell from "../SharedVisual/CardShell.vue";
|
|
||||||
import MediaFrame from "../SharedVisual/MediaFrame.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
@@ -31,11 +44,17 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(["select", "action"]);
|
const emit = defineEmits(["select", "action"]);
|
||||||
|
|
||||||
const hasCaption = computed(() => Boolean(props.data.caption?.title || props.data.caption?.subtitle));
|
const caption = computed(() => props.data.caption || {});
|
||||||
|
const action = computed(() => props.data.action || {});
|
||||||
|
const hasCaption = computed(() => Boolean(caption.value.title || caption.value.subtitle));
|
||||||
|
|
||||||
|
const handleSelect = () => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
emit("select", props.data);
|
||||||
|
};
|
||||||
|
|
||||||
const handleAction = () => {
|
const handleAction = () => {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
emit("select", props.data);
|
|
||||||
emit("action", props.data);
|
emit("action", props.data);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
export default {
|
export const scenicImageWithoutCaption = {
|
||||||
|
id: "bridge-mist-plain",
|
||||||
image: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=1000&q=80",
|
image: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=1000&q=80",
|
||||||
caption: {
|
action: {
|
||||||
title: "水上森林二号机位",
|
icon: "↗",
|
||||||
subtitle: "下午逆光较弱,适合拍摄树影和水面反射",
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
id: "bridge-mist",
|
||||||
|
image: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=1000&q=80",
|
||||||
|
caption: {
|
||||||
|
title: "古桥晨雾机位",
|
||||||
|
subtitle: "清晨 6:30 雾气最浓,长焦压缩效果极佳",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
icon: "↗",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,30 @@
|
|||||||
.scenic-image-card__image-wrap {
|
.scenic-image-card {
|
||||||
|
height: 324px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenic-image-card:active,
|
||||||
|
.scenic-image-card__expand:active {
|
||||||
|
opacity: 0.86;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenic-image-card.is-disabled {
|
||||||
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenic-image-card__image {
|
.scenic-image-card__image {
|
||||||
border-radius: 0;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenic-image-card__expand {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 2;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(15, 23, 42, 0.36);
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenic-image-card__caption {
|
.scenic-image-card__caption {
|
||||||
@@ -10,21 +32,17 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
padding: 42px 16px 16px;
|
padding: 72px 22px 20px;
|
||||||
background: linear-gradient(180deg, rgba(15, 23, 42, 0) 0%, rgba(15, 23, 42, 0.72) 100%);
|
background: linear-gradient(180deg, rgba(15, 23, 42, 0) 0%, rgba(15, 23, 42, 0.72) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenic-image-card__caption-title {
|
.scenic-image-card__title {
|
||||||
line-height: 20px;
|
line-height: 24px;
|
||||||
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenic-image-card__caption-subtitle {
|
.scenic-image-card__subtitle {
|
||||||
margin-top: 3px;
|
margin-top: 4px;
|
||||||
color: rgba(255, 255, 255, 0.72);
|
line-height: 18px;
|
||||||
}
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
|
||||||
|
|
||||||
.scenic-image-card__expand {
|
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
background: rgba(0, 0, 0, 0.32);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user