Files
lucky_shop/components-diy/diy-group.vue
ZF sun d0fe005c4a diy: 名片 DIY 组件原生渲染(禁 webview) + diy-group 分发
- 新增 diy-card-entry/list/show/self.vue(easycom 自动加载):
  头像/资料行/快捷操作原生渲染, tel 走 makePhoneCall,
  wx/mail/web 走复制, 点击卡片跳 pages_tool/card/detail
- diy-card-self 登录态自适应: hasToken watch 自动刷新,
  未登录兜底(企业卡/指定卡/登录引导)由后端 /card/api/diy/mycard 决定
- diy-group.vue: DiyCard* 四个 componentName 分发分支
2026-10-21 09:13:56 +08:00

451 lines
14 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 data-component-name="diy-group" class="diy-group">
<view v-for="(item, index) in diyDataArray" :key="index" :style="item.pageStyle">
<view v-if="addonIsExist.store && item.componentName == 'StoreShow'">
<!-- 门店展示 -->
<diy-store :value="item"></diy-store>
</view>
<template v-if="item.componentName == 'Kefu'">
<!-- 客服按钮 -->
<diy-kefu :value="item"></diy-kefu>
</template>
<template v-if="item.componentName == 'Form'">
<!-- 表单组件 -->
<diy-form :value="item"></diy-form>
</template>
<template v-if="addonIsExist.store && item.componentName == 'StoreLabel'">
<!-- 门店标签 -->
<diy-store-label :value="item"></diy-store-label>
</template>
<template v-if="item.componentName == 'Picture'">
<!-- 单图组组件 -->
<diy-picture :value="item"></diy-picture>
</template>
<template v-if="item.componentName == 'Listmenu'">
<!-- 列表按钮组件 -->
<diy-listmenu :value="item"></diy-listmenu>
</template>
<template v-if="item.componentName == 'Text'">
<!-- 文本 -->
<diy-text :value="item"></diy-text>
</template>
<template v-if="item.componentName == 'Notice'">
<!-- 公告 -->
<diy-notice :value="item"></diy-notice>
</template>
<template v-if="item.componentName == 'GraphicNav'">
<!-- 图文导航 -->
<diy-graphic-nav :value="item"></diy-graphic-nav>
</template>
<template v-if="item.componentName == 'ImageAds'">
<!-- 图片广告 -->
<diy-img-ads :value="item"></diy-img-ads>
</template>
<template v-if="item.componentName == 'Search'">
<!-- 搜索 -->
<diy-search :value="item" :topNavColor="topNavColor" :global="diyGlobalData.global"
:haveTopCategory="haveTopCategory" :followOfficialAccount="followOfficialAccount"></diy-search>
</template>
<template v-if="item.componentName == 'RichText'">
<!-- 富文本 -->
<diy-rich-text :value="item"></diy-rich-text>
</template>
<!-- #ifdef H5 -->
<template v-if="['Webview', 'WebView'].includes(item.componentName)">
<!-- 网页容器 -->
<diy-webview :value="item"></diy-webview>
</template>
<!-- #endif -->
<template v-if="item.componentName == 'HorzLine'">
<!-- 辅助线 -->
<diy-horz-line :value="item"></diy-horz-line>
</template>
<template v-if="item.componentName == 'HorzBlank'">
<!-- 辅助空白 -->
<diy-horz-blank :value="item"></diy-horz-blank>
</template>
<template v-if="item.componentName == 'Coupon' && addonIsExist.coupon">
<!-- 优惠券 -->
<diy-coupon :value="item"></diy-coupon>
</template>
<template v-if="item.componentName == 'GoodsList'">
<!-- 商品列表 -->
<diy-goods-list :value="item"></diy-goods-list>
</template>
<template v-if="item.componentName == 'ManyGoodsList'">
<!-- 多商品组 -->
<diy-many-goods-list :value="item" :global="diyGlobalData.global"
:scrollTop="scrollTop"></diy-many-goods-list>
</template>
<template v-if="item.componentName == 'RubikCube'">
<!-- 魔方、橱窗 -->
<diy-rubik-cube :value="item"></diy-rubik-cube>
</template>
<template v-if="item.componentName == 'Video'">
<!-- 视频 -->
<diy-video :value="item"></diy-video>
</template>
<template v-if="item.componentName == 'Seckill' && addonIsExist.seckill">
<!-- 秒杀 -->
<diy-seckill :value="item"></diy-seckill>
</template>
<template v-if="item.componentName == 'Pintuan' && addonIsExist.pintuan">
<!-- 拼团 -->
<diy-pintuan :value="item"></diy-pintuan>
</template>
<template v-if="item.componentName == 'Groupbuy' && addonIsExist.groupbuy">
<!-- 团购 -->
<diy-groupbuy :value="item"></diy-groupbuy>
</template>
<!-- 拼团返利 -->
<template v-if="item.componentName == 'Pinfan' && addonIsExist.pinfan">
<diy-pinfan :value="item"></diy-pinfan>
</template>
<template v-if="item.componentName == 'Bargain' && addonIsExist.bargain">
<!-- 砍价 -->
<diy-bargain :value="item"></diy-bargain>
</template>
<template v-if="item.componentName == 'Presale' && addonIsExist.bargain">
<!-- 预售 -->
<diy-presale :value="item"></diy-presale>
</template>
<template v-if="item.componentName == 'Notes' && addonIsExist.notes">
<!-- 店铺笔记 -->
<diy-notes :value="item"></diy-notes>
</template>
<template v-if="item.componentName == 'FloatBtn'">
<!-- 浮动按钮 -->
<diy-float-btn :value="item"></diy-float-btn>
</template>
<template v-if="item.componentName == 'LiveInfo'">
<!-- 小程序直播 -->
<!-- #ifdef MP-WEIXIN -->
<diy-live :value="item"></diy-live>
<!-- #endif -->
</template>
<template v-if="item.componentName == 'FenxiaoGoodsList'">
<!-- 分销商品 -->
<diy-fenxiao-goods-list :value="item"></diy-fenxiao-goods-list>
</template>
<template v-if="item.componentName == 'GoodsRecommend'">
<!-- 商品推荐 -->
<diy-goods-recommend :value="item"></diy-goods-recommend>
</template>
<template v-if="item.componentName == 'DiyRecommendCode'">
<!-- 自定义推荐码组件 -->
<diy-recommend-code :value="item"></diy-recommend-code>
</template>
<template v-else-if="item.componentName == 'DiyCardEntry'">
<!-- 数字名片 · 名片入口横幅 -->
<diy-card-entry :value="item"></diy-card-entry>
</template>
<template v-else-if="item.componentName == 'DiyCardList'">
<!-- 数字名片 · 团队名片墙 -->
<diy-card-list :value="item"></diy-card-list>
</template>
<template v-else-if="item.componentName == 'DiyCardShow'">
<!-- 数字名片 · 精选名片 -->
<diy-card-show :value="item"></diy-card-show>
</template>
<template v-else-if="item.componentName == 'DiyCardSelf'">
<!-- 数字名片 · 我的名片(登录态自适应) -->
<diy-card-self :value="item"></diy-card-self>
</template>
<template v-if="item.componentName == 'GoodsBrand'">
<!-- 商品品牌 -->
<diy-goods-brand :value="item"></diy-goods-brand>
</template>
<template v-if="item.componentName == 'Article'">
<!-- 文章 -->
<diy-article :value="item"></diy-article>
</template>
<template v-if="item.componentName == 'MerchList'">
<!-- 商户列表 -->
<diy-merch-list :value="item"></diy-merch-list>
</template>
<template v-if="item.componentName == 'MemberInfo'">
<!-- 自定义会员中心——会员信息 -->
<diy-member-info ref="diyMemberIndex" :value="item" :global="diyGlobalData.global"></diy-member-info>
</template>
<template v-if="item.componentName == 'MemberMyOrder'">
<!-- 自定义会员中心——我的订单 -->
<diy-member-my-order ref="diyMemberMyOrder" :value="item"></diy-member-my-order>
</template>
<template v-if="item.componentName == 'QuickNav'">
<!-- 快捷导航 -->
<diy-quick-nav :value="item"></diy-quick-nav>
</template>
<template v-if="item.componentName == 'PaymentQrcode'">
<!-- 付款码 -->
<diy-payment-qrcode :value="item"></diy-payment-qrcode>
</template>
<template v-if="item.componentName == 'HotArea'">
<!-- 热区 -->
<diy-hot-area :value="item"></diy-hot-area>
</template>
<template v-if="item.componentName == 'FollowOfficialAccount'">
<!-- 关注公众号 -->
<diy-follow-official-account :value="item"></diy-follow-official-account>
</template>
<template v-if="item.componentName == 'Map'">
<!-- 地图组件 -->
<diy-map :value="item"></diy-map>
</template>
<template v-if="item.componentName == 'Audio'">
<!-- 音频 -->
<diy-audio :value="item"></diy-audio>
</template>
<template v-if="item.componentName == 'ImageNav'">
<!-- 图片导航 -->
<diy-image-nav :value="item"></diy-image-nav>
</template>
<template v-if="item.componentName == 'Digit'">
<!-- 数字 -->
<diy-digit :value="item"></diy-digit>
</template>
<template v-if="item.componentName == 'VideoList'">
<!-- 视频列表 -->
<diy-video-list :value="item"></diy-video-list>
</template>
<template v-if="item.componentName == 'BottomNav'">
<!-- 底部导航 -->
<diy-bottom-nav :value="item"></diy-bottom-nav>
</template>
<template v-if="item.componentName == 'CategoryItem'">
<!-- 分类项 -->
<diy-category-item :value="item"></diy-category-item>
</template>
<template v-if="item.componentName == 'Category'">
<!-- 分类 -->
<diy-category :value="item"></diy-category>
</template>
<template v-if="item.componentName == 'Icon'">
<!-- 图标 -->
<diy-icon :value="item"></diy-icon>
</template>
<template v-if="item.componentName == 'Tab'">
<!-- Tab 组件 -->
<diy-tab :value="item" :diyGlobal="diyGlobalData"></diy-tab>
</template>
<template v-if="['ChannelList', 'WechatChannel'].includes(item.componentName)">
<!-- 视频号列表 -->
<diy-channel-list :value="item"></diy-channel-list>
</template>
<!-- 自定义扩展组件 -->
<diy-comp-extend :value="item"></diy-comp-extend>
</view>
</view>
</template>
<script>
// 组件组展示
import DiyMinx from './minx.js'
export default {
name: 'diy-group',
props: {
diyData: {
type: Object
},
scrollTop: {
type: [String, Number],
default: '0'
},
haveTopCategory: {
type: Boolean
},
followOfficialAccount: {
type: Object
},
},
mixins: [DiyMinx],
data() {
return {
diyGlobalData: this.diyData ? this.$util.deepClone(this.diyData) : null,
processedData: null
};
},
watch: {
diyData: {
handler() {
this.diyGlobalData = this.diyData ? this.$util.deepClone(this.diyData) : null;
this.processData();
this._notifyLoaded();
},
deep: true
}
},
created() {
console.log('[DIY-GROUP][created] hasDiyData=', !!this.diyData, 'valueLen=', this.diyData && this.diyData.value && this.diyData.value.length);
this.processData();
},
mounted() {
console.log('[DIY-GROUP][mounted] hasDiyData=', !!this.diyData, 'valueLen=', this.diyData && this.diyData.value && this.diyData.value.length);
if (this.diyData && this.diyData.value) {
this._notifyLoaded();
}
},
computed: {
topNavColor() {
if (!this.diyData?.global) return '';
const { topNavBg, topNavColor } = this.diyData.global;
if (topNavBg) {
return this.scrollTop > 20 ? topNavColor : 'transparent';
}
return topNavColor;
},
diyDataArray() {
if (!this.processedData) {
this.processData();
}
const showModuleData = this.$store.state.diyGroupShowModule
? JSON.parse(this.$store.state.diyGroupShowModule)
: null;
if (!showModuleData || !showModuleData.length) {
return this.processedData || [];
}
if (showModuleData.includes('null')) {
return [];
}
return (this.processedData || []).filter(item =>
showModuleData.includes(item.componentName)
);
}
},
methods: {
_notifyLoaded() {
console.log('[DIY-GROUP][_notifyLoaded] enter, will emit loaded in 300ms');
if (this._loadedTimer) clearTimeout(this._loadedTimer);
this._loadedTimer = setTimeout(() => {
console.log('[DIY-GROUP][_notifyLoaded] emit loaded');
this.$emit('loaded');
}, 300);
},
processData() {
if (!this.diyGlobalData || !this.diyGlobalData.value) {
this.processedData = [];
return;
}
this.processedData = this.diyGlobalData.value.map((item, index) => {
const newItem = this.$util.deepClone(item);
newItem.pageStyle = '';
newItem.moduleIndex = this.diyData.tabIndex !== undefined
? `${this.diyData.tabIndex}-${index + 1}`
: index + 1;
if (newItem.componentName !== 'Search' || newItem.positionWay !== 'fixed') {
// PR-DIY-SEARCH-MARGIN-COLOR (2026-09-17): 搜索组件的上下左右边距区域颜色
// 与搜索框背景色(componentBgColor, 后台自定义)保持一致, 使边距和搜索框融为一体;
// 兼容旧数据: 未配置 componentBgColor 时回退 pageBgColor。其余组件仍用 pageBgColor。
const wrapBgColor = (newItem.componentName === 'Search' && newItem.componentBgColor)
? newItem.componentBgColor
: newItem.pageBgColor;
newItem.pageStyle = `background-color:${wrapBgColor};`;
// 边距归一化: 兼容后台两种数据结构(否则边距不生效)
// ① 标准对象形态 margin = { top, bottom, both } —— 大多数组件
// ② 扁平字段形态 marginTop / marginBottom / marginBoth
// —— 如"滑动图片"(ImageNav), 其 margin 是空数组 [], 左右边距存在 marginBoth 里
// both / marginBoth 表示"左右边距"; 数值 → rpx 统一 × 2 (与其它组件一致)
const rawMargin = newItem.margin;
const objMargin = (rawMargin && typeof rawMargin === 'object' && !Array.isArray(rawMargin)) ? rawMargin : null;
const toMarginNum = (v) => { const n = Number(v); return isNaN(n) ? 0 : n; };
const pickMargin = (objKey, flatKey) => {
if (objMargin && objMargin[objKey] !== undefined && objMargin[objKey] !== null && objMargin[objKey] !== '') {
return toMarginNum(objMargin[objKey]);
}
return toMarginNum(newItem[flatKey]);
};
const marginTopNum = pickMargin('top', 'marginTop');
const marginBottomNum = pickMargin('bottom', 'marginBottom');
const marginBothNum = pickMargin('both', 'marginBoth');
// 左右: 若有独立的 left/right 字段则优先, 否则用 both(左右一致)
const marginLeftNum = (objMargin && objMargin.left !== undefined) ? toMarginNum(objMargin.left)
: (newItem.marginLeft !== undefined ? toMarginNum(newItem.marginLeft) : marginBothNum);
const marginRightNum = (objMargin && objMargin.right !== undefined) ? toMarginNum(objMargin.right)
: (newItem.marginRight !== undefined ? toMarginNum(newItem.marginRight) : marginBothNum);
newItem.pageStyle += `padding-top:${marginTopNum * 2}rpx;`;
newItem.pageStyle += `padding-bottom:${marginBottomNum * 2}rpx;`;
newItem.pageStyle += `padding-left:${marginLeftNum * 2}rpx;`;
newItem.pageStyle += `padding-right:${marginRightNum * 2}rpx;`;
}
return newItem;
});
}
}
};
</script>
<style lang="scss">
.diy-group {
width: 100%;
}
</style>