Files
QSY-lvjuxcx/pkgExplore/store_detail/components/StoreInfo.vue
liangjian 1111ac148b feat: 重构核心租住流程页面与交互体验
统一注册、看房、申请与入住等核心页面的视觉和流程衔接,补齐支付、签约与状态流转闭环,提升移动端操作连贯性。
2026-07-29 09:05:38 +08:00

281 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="store-info-panel">
<!-- 核心信息卡片:地址 -->
<view class="info-card tn-flex tn-flex-row-between tn-flex-col-center" @click="openMap">
<view class="address-left tn-flex tn-flex-col-center">
<view class="icon-wrap tn-flex tn-flex-row-center tn-flex-col-center">
<text class="tn-icon-location-fill location-icon"></text>
</view>
<view class="address-content">
<text class="address-label">{{ $tr('公寓地址') }}</text>
<text class="address-title tn-text-ellipsis">{{ shortAddress }}</text>
</view>
</view>
<view class="address-right tn-flex tn-flex-col-center">
<text class="nav-text">{{ $tr('导航') }}</text>
<text class="tn-icon-right nav-arrow"></text>
</view>
</view>
<!-- 小区简介 -->
<view class="section-panel" v-if="displayDescription">
<view class="section-header">
<view class="title-bar"></view>
<text class="section-title">{{ $tr('小区简介') }}</text>
</view>
<view
class="store-desc"
:class="{
'store-desc--collapsed': !isRichDescription && isDescCollapsed
}"
>
<rich-text v-if="isRichDescription" class="store-desc-rich" :nodes="displayDescriptionNodes"></rich-text>
<text v-else>{{ displayDescription }}</text>
</view>
<view v-if="shouldShowToggle" class="expand-btn tn-flex tn-flex-row-center tn-flex-col-center" @click="toggleDesc">
<text>{{ isDescCollapsed ? $tr('展开全部') : $tr('收起') }}</text>
<text :class="isDescCollapsed ? 'tn-icon-down' : 'tn-icon-up'" class="expand-icon"></text>
</view>
</view>
</view>
</template>
<script>
import { normalizeRichTextImages } from '../../../utils/rich-text'
function hasHtmlTag(value = '') {
return /<\/?(p|div|img|br|span|h[1-6]|ul|li|ol|table|td|tr|strong|em|section|article|blockquote)[^>]*>/i.test(String(value || ''))
}
function normalizeRichDescription(value = '') {
let html = String(value || '').trim()
if (!html) {
return ''
}
html = normalizeRichTextImages(
html,
'display:block;width:100%;max-width:100%;height:auto;box-sizing:border-box;border-radius:16rpx;margin:16rpx 0;vertical-align:top;'
)
html = html.replace(/<p\b([^>]*)>/gi, '<p$1 style="line-height:1.9;color:#475569;margin:0 0 20rpx;word-break:break-word;">')
html = html.replace(/<div\b([^>]*)>/gi, '<div$1 style="line-height:1.9;color:#475569;margin:0 0 20rpx;word-break:break-word;">')
html = html.replace(/<ul\b([^>]*)>/gi, '<ul$1 style="padding-left:28rpx;margin:0 0 20rpx;color:#475569;line-height:1.9;">')
html = html.replace(/<ol\b([^>]*)>/gi, '<ol$1 style="padding-left:32rpx;margin:0 0 20rpx;color:#475569;line-height:1.9;">')
html = html.replace(/<li\b([^>]*)>/gi, '<li$1 style="margin-bottom:12rpx;">')
html = html.replace(/<h([1-6])\b([^>]*)>/gi, '<h$1$2 style="color:#1E3A5F;line-height:1.5;margin:0 0 20rpx;font-weight:600;">')
html = html.replace(/<strong\b([^>]*)>/gi, '<strong$1 style="color:#1E3A5F;font-weight:600;">')
html = html.replace(/<a\b([^>]*)>/gi, '<a$1 style="color:#2E4A73;text-decoration:underline;">')
return html
}
export default {
props: {
storeData: {
type: Object,
default: () => ({})
},
language: {
type: String,
default: 'zh'
}
},
data() {
return {
isDescCollapsed: true
}
},
computed: {
displayDescription() {
if (this.language === 'en') {
return this.storeData.descriptionEnDisplay || this.storeData.descriptionEn || this.storeData.description || ''
}
return this.storeData.description || this.storeData.descriptionEn || ''
},
isRichDescription() {
return hasHtmlTag(this.displayDescription)
},
displayDescriptionNodes() {
return this.isRichDescription ? normalizeRichDescription(this.displayDescription) : ''
},
shouldShowToggle() {
if (this.isRichDescription) {
return false
}
return String(this.displayDescription || '').length > 90
},
shortAddress() {
const addr = this.language === 'en'
? (this.storeData.addressEnDisplay || this.storeData.addressEn || this.storeData.address || '')
: (this.storeData.address || this.storeData.addressEn || '')
if (!addr) {
return this.$tr('暂无地址')
}
if (this.language === 'en') {
return addr
}
const short = addr.replace(/^(?:.*?省|.*?自治区)?(?:.*?市|.*?自治州)?/, '');
return short.trim() ? short : (addr || this.$tr('暂无地址'));
}
},
methods: {
toggleDesc() {
this.isDescCollapsed = !this.isDescCollapsed;
},
openMap() {
this.$emit('open-map');
}
}
}
</script>
<style scoped lang="scss">
$navy-deep: #1E3A5F;
$navy-light: #2E4A73;
$gold: #C9A96E;
$gold-light: #D4BC8C;
$text-primary: #0F172A;
$text-secondary: #64748B;
$text-tertiary: #94A3B8;
.store-info-panel {
padding: 0 32rpx;
margin-top: 24rpx;
position: relative;
z-index: 10;
}
.info-card {
background: #FFFFFF;
border: 1rpx solid rgba($navy-deep, 0.08);
box-shadow: 0 8rpx 28rpx rgba(30, 58, 95, 0.06);
border-radius: 28rpx;
padding: 32rpx;
transition: all 0.2s;
&:active {
transform: scale(0.98);
box-shadow: 0 12rpx 32rpx rgba(30, 58, 95, 0.1);
}
}
.address-left {
flex: 1;
min-width: 0;
padding-right: 20rpx;
}
.icon-wrap {
width: 72rpx;
height: 72rpx;
background: linear-gradient(135deg, rgba($navy-deep, 0.08) 0%, rgba($navy-deep, 0.04) 100%);
border-radius: 20rpx;
margin-right: 20rpx;
flex-shrink: 0;
border: 1rpx solid rgba($navy-deep, 0.08);
}
.location-icon {
color: $navy-deep;
font-size: 36rpx;
}
.address-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.address-label {
font-size: 24rpx;
color: $text-tertiary;
margin-bottom: 6rpx;
}
.address-title {
font-size: 30rpx;
font-weight: 700;
color: $text-primary;
flex: 1;
}
.address-right {
flex-shrink: 0;
padding-left: 24rpx;
border-left: 1rpx solid rgba($navy-deep, 0.08);
}
.nav-text {
font-size: 26rpx;
color: $navy-deep;
font-weight: 600;
}
.nav-arrow {
font-size: 24rpx;
color: $gold;
margin-left: 6rpx;
}
.section-panel {
background: #FFFFFF;
border: 1rpx solid rgba($navy-deep, 0.08);
box-shadow: 0 8rpx 28rpx rgba(30, 58, 95, 0.06);
margin-top: 24rpx;
padding: 32rpx;
border-radius: 28rpx;
}
.section-header {
margin-bottom: 24rpx;
display: flex;
align-items: center;
}
.title-bar {
width: 6rpx;
height: 32rpx;
background: linear-gradient(180deg, $gold 0%, $gold-light 100%);
border-radius: 999rpx;
margin-right: 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 800;
color: $navy-deep;
}
.store-desc {
font-size: 28rpx;
color: $text-secondary;
line-height: 1.7;
word-break: break-word;
}
.store-desc--collapsed {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.store-desc-rich {
display: block;
}
.expand-btn {
margin-top: 20rpx;
font-size: 26rpx;
color: $navy-deep;
font-weight: 600;
}
.expand-icon {
margin-left: 8rpx;
font-size: 28rpx;
line-height: 1;
color: $gold;
}
</style>