Files
lucky_shop/components-diy/diy-card-entry.vue
ZF sun 48a17a59f7 feat(diy-card): 名片 DIY 组件 2.0 视觉与功能升级(原生渲染, 禁 webview)
- diy-card-common.js(新): 设计 token 层 —— 颜色/字阶/间距/圆角/阴影/动效/
  触达尺寸 + px2rpx/flagOn/initialOf/avatarBg/themeGradient/visibleRows 助手
- diy-card-mini.vue(新): 共享名片卡基件 —— 封面+遮罩/渐变饰条/Logo/主题色
  头像(首字兜底)/资料行折叠(showRows 总开关+maxRows 上限+展开收起)/快捷操作
  芯片(首个主动作实心, 72rpx 触达)/二维码/详情按钮/骨架屏 shimmer/badge 插槽
- diy-card-entry.vue: 三预设(gradient/solid/minimal)×两布局(left/center)×
  图标装饰(内置字形/图片)×按钮四变体(solid/ghost/link/none)
- diy-card-list.vue: 三布局(grid 双列/list/scroll 横滑)+骨架屏+逐项 fade-up
  入场+标题行(计数/更多)+企业徽章+空态
- diy-card-show.vue / diy-card-self.vue: 改为基件包装; self 管登录态
  (hasToken watch 刷新)+兜底(fallbackMode)+空态 CTA+「我的名片」角标
- 修复死配置: 面板「显示资料行」showRows 此前被 C 端丢弃, 现由基件消费
  (历史配置无此字段时默认显示, 兼容 1.0)
- 规范: 禁 CSS 变量, 动态样式走 $util.objToStyle, rpx 尺寸
2026-10-21 09:56:51 +08:00

268 lines
6.8 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-card-entry" class="diy-card-entry" :class="'entry-' + stylePreset" :style="wrapStyle" hover-class="entry-press" @tap="goLink">
<!-- 图标装饰(可选) -->
<view v-if="showIcon" class="entry-icon" :style="iconWrapStyle">
<image v-if="iconType === 'image' && iconImage" class="icon-img" :src="$util.img(iconImage)" mode="aspectFit" />
<text v-else class="icon-glyph">{{ iconGlyph || '💼' }}</text>
</view>
<view class="entry-body">
<text class="entry-title" :style="titleStyle">{{ value.title || '扫名片 · 谈合作' }}</text>
<text v-if="value.subtitle" class="entry-sub" :style="subStyle">{{ value.subtitle }}</text>
</view>
<!-- 按钮(可选; link 模式为文字箭头) -->
<view v-if="buttonStyle !== 'none' && value.btnText" :class="['entry-btn', 'btn-' + buttonStyle]" :style="btnStyle" hover-class="btn-press" hover-stay-time="60">
<text class="entry-btn-text" :style="btnTextStyle">{{ value.btnText }}</text>
<text v-if="buttonStyle === 'link'" class="btn-arrow" :style="btnTextStyle">›</text>
</view>
</view>
</template>
<script>
// 数字名片 · 名片入口横幅(装修组件)
// 预设: gradient(渐变) / solid(纯色+主题饰条) / minimal(透明轻量)
// 布局: left / center; 按钮: solid / ghost / link / none
import { CardTokens, px2rpx, flagOn, num } from './diy-card-common.js';
export default {
name: 'diy-card-entry',
props: {
value: { type: Object }
},
computed: {
v() {
return this.value || {};
},
stylePreset() {
const p = String(this.v.stylePreset || 'gradient');
return ['gradient', 'solid', 'minimal'].includes(p) ? p : 'gradient';
},
isCenter() {
return String(this.v.layout || 'left') === 'center';
},
showIcon() {
return flagOn(this.v.showIcon, false);
},
iconType() {
return String(this.v.iconType || 'icon') === 'image' ? 'image' : 'icon';
},
iconImage() {
return this.v.iconImage || '';
},
iconGlyph() {
return this.v.iconGlyph || '名';
},
buttonStyle() {
const b = String(this.v.buttonStyle || 'solid');
return ['solid', 'ghost', 'link', 'none'].includes(b) ? b : 'solid';
},
isGradient() {
return this.stylePreset === 'gradient';
},
isMinimal() {
return this.stylePreset === 'minimal';
},
themeColor() {
return this.v.bgColorStart || CardTokens.color.primary;
},
wrapStyle() {
const v = this.v;
const radius = px2rpx(num(v.borderRadius, CardTokens.radius.md)) + 'rpx';
const pad = px2rpx(num(v.padding, CardTokens.space.lg)) + 'rpx';
if (this.isMinimal) {
return this.$util.objToStyle({
background: 'transparent',
borderRadius: radius,
padding: pad,
boxShadow: 'none',
});
}
if (this.isGradient) {
return this.$util.objToStyle({
background: 'linear-gradient(135deg, ' + (v.bgColorStart || CardTokens.color.primary) + ' 0%, ' + (v.bgColorEnd || '#1E40AF') + ' 100%)',
borderRadius: radius,
padding: pad,
minHeight: '120rpx',
});
}
// solid: 主题色底 + 左侧亮饰条
return this.$util.objToStyle({
background: v.bgColorStart || CardTokens.color.primary,
borderRadius: radius,
padding: pad,
borderLeft: '8rpx solid ' + (v.bgColorEnd || 'rgba(255,255,255,0.35)'),
minHeight: '120rpx',
});
},
iconWrapStyle() {
return this.$util.objToStyle({
background: this.isGradient || !this.isMinimal ? 'rgba(255,255,255,0.18)' : 'rgba(47,107,255,0.10)',
});
},
titleStyle() {
return this.$util.objToStyle({
color: this.v.textColor || (this.isMinimal ? CardTokens.color.text : '#FFFFFF'),
});
},
subStyle() {
return this.$util.objToStyle({
color: this.v.textColor || (this.isMinimal ? CardTokens.color.textSub : '#FFFFFF'),
opacity: this.isMinimal ? 1 : 0.85,
});
},
btnStyle() {
const v = this.v;
const radius = px2rpx(CardTokens.radius.pill) + 'rpx';
const pad = '0 ' + px2rpx(CardTokens.space.xl) + 'rpx';
if (this.buttonStyle === 'ghost') {
return this.$util.objToStyle({
border: '2rpx solid ' + (v.accentColor || '#FFEB3B'),
borderRadius: radius,
padding: pad,
height: px2rpx(CardTokens.touch.chipHeight) + 'rpx',
});
}
if (this.buttonStyle === 'link') {
return this.$util.objToStyle({ padding: '0 0 0 ' + px2rpx(CardTokens.space.sm) + 'rpx' });
}
return this.$util.objToStyle({
background: v.accentColor || '#FFEB3B',
borderRadius: radius,
padding: pad,
height: px2rpx(CardTokens.touch.chipHeight) + 'rpx',
});
},
btnTextStyle() {
const v = this.v;
if (this.buttonStyle === 'ghost') {
return this.$util.objToStyle({ color: v.accentColor || '#FFEB3B' });
}
if (this.buttonStyle === 'link') {
return this.$util.objToStyle({ color: v.textColor || CardTokens.color.primary });
}
return this.$util.objToStyle({ color: '#333333' });
},
},
methods: {
goLink() {
const v = this.v;
let url = '';
if (v.linkMode === 'contacts') {
url = '/pages_tool/card/contacts';
} else if (v.linkMode === 'custom') {
url = String(v.customUrl || '').trim();
if (url !== '' && url.indexOf('/') !== 0) {
uni.showToast({ title: '自定义路径需以 / 开头', icon: 'none' });
return;
}
} else {
url = '/pages_tool/card/list';
}
if (url === '') {
uni.showToast({ title: '未配置跳转路径', icon: 'none' });
return;
}
this.$util.redirectTo(url);
},
},
};
</script>
<style lang="scss" scoped>
.diy-card-entry {
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
overflow: hidden;
&.entry-center {
justify-content: center;
text-align: center;
.entry-body {
align-items: center;
}
}
.entry-icon {
flex-shrink: 0;
width: 96rpx;
height: 96rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.icon-img {
width: 100%;
height: 100%;
display: block;
}
.icon-glyph {
font-size: 48rpx;
}
}
.entry-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
.entry-title {
font-size: 32rpx;
font-weight: 700;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.entry-sub {
font-size: 24rpx;
line-height: 1.5;
margin-top: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
.entry-btn {
flex-shrink: 0;
margin-left: 20rpx;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
.entry-btn-text {
font-size: 24rpx;
font-weight: 600;
}
.btn-arrow {
font-size: 28rpx;
margin-left: 2rpx;
}
}
}
.entry-press {
transform: scale(0.985);
opacity: 0.92;
}
.btn-press {
opacity: 0.72;
}
</style>