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:
DEV_DSW
2026-05-13 16:28:57 +08:00
parent 9da046c314
commit 0afb690262
15 changed files with 514 additions and 266 deletions

View File

@@ -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);

View File

@@ -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",
],
}; };

View File

@@ -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);
} }

View File

@@ -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>

View File

@@ -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: "可以带宠物入园吗",
},
], ],
}; };

View File

@@ -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;
} }

View File

@@ -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);

View File

@@ -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: "导航",
},
}; };

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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: "如水位下降恢复安全标准,景区将第一时间通过广播及公告重新开放步道,请游客留意景区实时公告。",
},
],
},
],
},
}; };

View File

@@ -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;
} }

View File

@@ -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>

View File

@@ -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: "↗",
}, },
}; };

View File

@@ -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);
} }