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 尺寸
This commit is contained in:
130
components-diy/diy-card-common.js
Normal file
130
components-diy/diy-card-common.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* 名片 DIY 组件 · 共享设计系统层
|
||||||
|
*
|
||||||
|
* 目标(UI Designer 设计系统先行原则):
|
||||||
|
* - 4 个组件共用同一套 token(颜色/字阶/间距/圆角/阴影/动效时长)
|
||||||
|
* - 通用 helpers(px2rpx/头像兜底色/显隐开关解析)集中一处, 避免组件间漂移
|
||||||
|
* - 严格遵循小程序规范: 禁 CSS 变量, 动态样式一律 $util.objToStyle, 单位 rpx
|
||||||
|
*
|
||||||
|
* 注: 本文件为纯 JS(无 wxss), 小程序/H5 通用。
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const CardTokens = {
|
||||||
|
color: {
|
||||||
|
primary: '#2F6BFF', // 主题默认蓝(名片 wap 公开页同色系)
|
||||||
|
text: '#1F2329', // 主文字
|
||||||
|
textSub: '#8A8F99', // 次文字(公司/职位)
|
||||||
|
textLabel: '#B0B4BC', // 字段标签
|
||||||
|
line: '#EEF0F3', // 分割线
|
||||||
|
bg: '#FFFFFF', // 卡片底
|
||||||
|
skeleton: '#F2F3F5', // 骨架块
|
||||||
|
skeletonHi: '#E8EAEE', // 骨架高亮扫过
|
||||||
|
scrim: 'rgba(15, 23, 42, 0.45)', // 封面图遮罩(对比度保障)
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
sm: 8,
|
||||||
|
md: 16,
|
||||||
|
lg: 24,
|
||||||
|
pill: 999,
|
||||||
|
},
|
||||||
|
/** 字阶(px, 输出时 ×2 转 rpx) */
|
||||||
|
fontSize: {
|
||||||
|
display: 40, // 入口主标题
|
||||||
|
title: 32, // 分组标题/名片姓名
|
||||||
|
name: 30,
|
||||||
|
meta: 24, // 公司/职位/字段值
|
||||||
|
caption: 22, // 辅助说明
|
||||||
|
},
|
||||||
|
/** 间距(px) */
|
||||||
|
space: {
|
||||||
|
xs: 4,
|
||||||
|
sm: 8,
|
||||||
|
md: 12,
|
||||||
|
lg: 16,
|
||||||
|
xl: 20,
|
||||||
|
xxl: 24,
|
||||||
|
},
|
||||||
|
/** 阴影 */
|
||||||
|
shadow: {
|
||||||
|
card: '0 4rpx 16rpx rgba(15, 23, 42, 0.06)',
|
||||||
|
raised: '0 8rpx 24rpx rgba(15, 23, 42, 0.10)',
|
||||||
|
},
|
||||||
|
/** 动效时长(ms) */
|
||||||
|
duration: {
|
||||||
|
fast: 150,
|
||||||
|
normal: 240,
|
||||||
|
slow: 320,
|
||||||
|
},
|
||||||
|
/** 最小触达尺寸(rpx, 小程序可点性下限 44px≈88rpx, 芯片取 72rpx 高度) */
|
||||||
|
touch: {
|
||||||
|
chipHeight: 72,
|
||||||
|
btnHeight: 80,
|
||||||
|
avatar: 104,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** px → rpx(组件配置字段均为 px 口径) */
|
||||||
|
export function px2rpx(v) {
|
||||||
|
const n = Number(v);
|
||||||
|
return isNaN(n) ? 0 : Math.round(n * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显隐开关解析: 兼容 true/false、1/0、"1"/"0"、"true"/"false"
|
||||||
|
* 未下发(undefined)时返回 fallback(由调用方决定默认值)
|
||||||
|
*/
|
||||||
|
export function flagOn(v, fallback) {
|
||||||
|
if (v === undefined || v === null || v === '') return fallback;
|
||||||
|
if (v === true || v === 1 || v === '1' || v === 'true') return true;
|
||||||
|
if (v === false || v === 0 || v === '0' || v === 'false') return false;
|
||||||
|
return !!v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 姓名首字兜底(头像无图时) */
|
||||||
|
export function initialOf(name) {
|
||||||
|
const s = String(name || '').trim();
|
||||||
|
return s ? s.charAt(0) : '名';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 由字符串确定性派生色相(同名同色, 列表视觉稳定) */
|
||||||
|
export function hueOf(str) {
|
||||||
|
const s = String(str || '');
|
||||||
|
let h = 0;
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
h = (h * 31 + s.charCodeAt(i)) % 360;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 头像兜底底色: 主题色优先, 否则按名片确定性色相 */
|
||||||
|
export function avatarBg(card) {
|
||||||
|
const theme = card && card.theme_color;
|
||||||
|
if (theme) return theme;
|
||||||
|
const h = hueOf((card && (card.card_id || card.name)) || 'card');
|
||||||
|
return 'hsl(' + h + ', 62%, 55%)';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 主题渐变(头部饰条/主按钮): 有渐变末色用双色, 否则单色 */
|
||||||
|
export function themeGradient(card, angle) {
|
||||||
|
const theme = (card && card.theme_color) || CardTokens.color.primary;
|
||||||
|
const end = card && card.gradient_end;
|
||||||
|
const deg = (card && card.gradient_angle) || angle || 135;
|
||||||
|
return end
|
||||||
|
? 'linear-gradient(' + deg + 'deg, ' + theme + ' 0%, ' + end + ' 100%)'
|
||||||
|
: 'linear-gradient(' + deg + 'deg, ' + theme + ' 20%, ' + theme + ' 100%)';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 安全数字(配置字段) */
|
||||||
|
export function num(v, dft) {
|
||||||
|
const n = Number(v);
|
||||||
|
return isNaN(n) ? dft : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 字段行: 过滤空值, 限制条数(其余折叠) */
|
||||||
|
export function visibleRows(rows, max) {
|
||||||
|
const list = Array.isArray(rows) ? rows.filter(r => r && String(r.value || '').trim() !== '') : [];
|
||||||
|
if (max && max > 0 && list.length > max) {
|
||||||
|
return { shown: list.slice(0, max), rest: list.slice(max), hasMore: true };
|
||||||
|
}
|
||||||
|
return { shown: list, rest: [], hasMore: false };
|
||||||
|
}
|
||||||
@@ -1,52 +1,152 @@
|
|||||||
<template>
|
<template>
|
||||||
<view data-component-name="diy-card-entry" class="diy-card-entry" :style="wrapStyle" @tap="goLink">
|
<view data-component-name="diy-card-entry" class="diy-card-entry" :class="'entry-' + stylePreset" :style="wrapStyle" hover-class="entry-press" @tap="goLink">
|
||||||
<view class="entry-left">
|
<!-- 图标装饰(可选) -->
|
||||||
<text class="entry-title" :style="textStyle">{{ value.title || '扫名片 · 谈合作' }}</text>
|
<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>
|
<text v-if="value.subtitle" class="entry-sub" :style="subStyle">{{ value.subtitle }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="value.btnText" class="entry-btn" :style="btnStyle">
|
|
||||||
<text class="entry-btn-text">{{ value.btnText }}</text>
|
<!-- 按钮(可选; 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>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 数字名片 · 名片入口横幅(装修组件, 原生渲染不依赖 webview)
|
// 数字名片 · 名片入口横幅(装修组件)
|
||||||
import DiyMinx from './minx.js';
|
// 预设: gradient(渐变) / solid(纯色+主题饰条) / minimal(透明轻量)
|
||||||
import golbalConfig from '@/common/js/golbalConfig.js';
|
// 布局: left / center; 按钮: solid / ghost / link / none
|
||||||
|
import { CardTokens, px2rpx, flagOn, num } from './diy-card-common.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'diy-card-entry',
|
name: 'diy-card-entry',
|
||||||
props: {
|
props: {
|
||||||
value: { type: Object }
|
value: { type: Object }
|
||||||
},
|
},
|
||||||
mixins: [DiyMinx, golbalConfig],
|
|
||||||
computed: {
|
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() {
|
wrapStyle() {
|
||||||
const v = this.value || {};
|
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({
|
return this.$util.objToStyle({
|
||||||
background: 'linear-gradient(135deg, ' + (v.bgColorStart || '#2F6BFF') + ' 0%, ' + (v.bgColorEnd || '#1E40AF') + ' 100%)',
|
background: v.bgColorStart || CardTokens.color.primary,
|
||||||
borderRadius: this.px2rpx(v.borderRadius || 12) + 'rpx',
|
borderRadius: radius,
|
||||||
padding: this.px2rpx(v.padding || 16) + 'rpx ' + this.px2rpx(v.padding || 16) + 'rpx'
|
padding: pad,
|
||||||
|
borderLeft: '8rpx solid ' + (v.bgColorEnd || 'rgba(255,255,255,0.35)'),
|
||||||
|
minHeight: '120rpx',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
textStyle() {
|
iconWrapStyle() {
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).textColor || '#FFFFFF' });
|
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() {
|
subStyle() {
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).textColor || '#FFFFFF', opacity: 0.85 });
|
return this.$util.objToStyle({
|
||||||
|
color: this.v.textColor || (this.isMinimal ? CardTokens.color.textSub : '#FFFFFF'),
|
||||||
|
opacity: this.isMinimal ? 1 : 0.85,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
btnStyle() {
|
btnStyle() {
|
||||||
return this.$util.objToStyle({ background: (this.value || {}).accentColor || '#FFEB3B' });
|
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: {
|
methods: {
|
||||||
px2rpx(v) {
|
|
||||||
const n = Number(v);
|
|
||||||
return isNaN(n) ? 0 : Math.round(n * 2);
|
|
||||||
},
|
|
||||||
goLink() {
|
goLink() {
|
||||||
const v = this.value || {};
|
const v = this.v;
|
||||||
let url = '';
|
let url = '';
|
||||||
if (v.linkMode === 'contacts') {
|
if (v.linkMode === 'contacts') {
|
||||||
url = '/pages_tool/card/contacts';
|
url = '/pages_tool/card/contacts';
|
||||||
@@ -64,8 +164,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$util.redirectTo(url);
|
this.$util.redirectTo(url);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -75,10 +175,39 @@ export default {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.entry-left {
|
&.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;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -88,6 +217,11 @@ export default {
|
|||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-sub {
|
.entry-sub {
|
||||||
@@ -105,14 +239,29 @@ export default {
|
|||||||
.entry-btn {
|
.entry-btn {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-left: 20rpx;
|
margin-left: 20rpx;
|
||||||
padding: 12rpx 28rpx;
|
display: flex;
|
||||||
border-radius: 999rpx;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.entry-btn-text {
|
.entry-btn-text {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333333;
|
}
|
||||||
|
|
||||||
|
.btn-arrow {
|
||||||
|
font-size: 28rpx;
|
||||||
|
margin-left: 2rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entry-press {
|
||||||
|
transform: scale(0.985);
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-press {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,24 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<view data-component-name="diy-card-list" class="diy-card-list">
|
<view data-component-name="diy-card-list" class="diy-card-list">
|
||||||
<view class="card-wall" :style="wallStyle">
|
<view class="card-wall" :style="wallStyle">
|
||||||
<text v-if="value.title" class="wall-title" :style="titleStyle">{{ value.title }}</text>
|
<!-- 标题行: 标题 + 数量 + 查看全部 -->
|
||||||
|
<view v-if="showTitle || showCount || showMore" class="wall-head">
|
||||||
|
<text v-if="showTitle" class="wall-title" :style="titleStyle">{{ value.title || '团队名片' }}</text>
|
||||||
|
<text v-if="showCount && list.length" class="wall-count" :style="countStyle">{{ list.length }} 人</text>
|
||||||
|
<view class="head-spacer"></view>
|
||||||
|
<text v-if="showMore" class="wall-more" :style="moreStyle" hover-class="btn-press" @tap="goMore">{{ moreText || '查看全部' }} ›</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view v-if="list.length" class="wall-grid">
|
<!-- 骨架屏 -->
|
||||||
<view v-for="(item, index) in list" :key="index" class="wall-item" :style="itemStyle" @tap="goDetail(item)">
|
<view v-if="loading && !list.length" class="wall-skeleton">
|
||||||
|
<view v-for="n in skeletonCount" :key="'sk' + n" class="sk-item">
|
||||||
|
<view class="sk sk-avatar"></view>
|
||||||
|
<view class="sk-col">
|
||||||
|
<view class="sk sk-line" style="width:50%"></view>
|
||||||
|
<view class="sk sk-line" style="width:70%;margin-top:14rpx;"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 横滑布局 -->
|
||||||
|
<scroll-view v-else-if="layout === 'scroll' && list.length" class="wall-scroll" scroll-x show-scrollbar="false">
|
||||||
|
<view v-for="(item, index) in list" :key="'s' + index" class="scroll-item fade-up" :style="scrollItemStyle" hover-class="btn-press" @tap="goDetail(item)">
|
||||||
<view class="item-avatar" :style="avatarStyle(item)">
|
<view class="item-avatar" :style="avatarStyle(item)">
|
||||||
<image v-if="item.avatar" class="avatar-img" :src="$util.img(item.avatar)" mode="aspectFill" />
|
<image v-if="item.avatar" class="avatar-img" :src="$util.img(item.avatar)" mode="aspectFill" />
|
||||||
<text v-else class="avatar-initial" :style="initialStyle(item)">{{ (item.name || '名').substring(0, 1) }}</text>
|
<text v-else class="avatar-initial">{{ initialOf(item.name) }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="scroll-name" :style="nameStyle">{{ item.name || '未命名名片' }}</text>
|
||||||
|
<text v-if="infoLine(item)" class="scroll-meta" :style="infoStyle">{{ infoLine(item) }}</text>
|
||||||
|
<text v-if="isEnterprise(item)" class="item-badge" :style="badgeStyle(item)">企业</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 网格/列表布局 -->
|
||||||
|
<view v-else-if="list.length" :class="['wall-grid', layout === 'list' ? 'is-list' : 'is-grid']">
|
||||||
|
<view v-for="(item, index) in list" :key="'g' + index" class="wall-item fade-up"
|
||||||
|
:style="[itemStyle, { animationDelay: (index * 60) + 'ms' }]" hover-class="btn-press" @tap="goDetail(item)">
|
||||||
|
<view class="item-avatar" :style="avatarStyle(item)">
|
||||||
|
<image v-if="item.avatar" class="avatar-img" :src="$util.img(item.avatar)" mode="aspectFill" />
|
||||||
|
<text v-else class="avatar-initial">{{ initialOf(item.name) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="item-info">
|
<view class="item-info">
|
||||||
<text class="item-name" :style="nameStyle">{{ item.name || '未命名名片' }}</text>
|
<view class="name-row">
|
||||||
|
<text class="item-name" :style="nameStyle">{{ item.name || '未命名名片' }}</text>
|
||||||
|
<text v-if="isEnterprise(item)" class="item-badge" :style="badgeStyle(item)">企业</text>
|
||||||
|
</view>
|
||||||
<text v-if="infoLine(item)" class="item-meta" :style="infoStyle">{{ infoLine(item) }}</text>
|
<text v-if="infoLine(item)" class="item-meta" :style="infoStyle">{{ infoLine(item) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="showArrow" class="item-arrow" :style="infoStyle">›</text>
|
<text v-if="showArrow" class="item-arrow" :style="infoStyle">›</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="wall-empty" :style="infoStyle">
|
<!-- 空态 -->
|
||||||
<text>{{ emptyTip }}</text>
|
<view v-else-if="!loading" class="wall-empty" :style="infoStyle">
|
||||||
|
<text class="empty-icon">🗂️</text>
|
||||||
|
<text class="empty-tip">{{ cardIdsConfigured ? '暂无可展示的名片' : '商家还未挑选名片' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -26,114 +63,166 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 数字名片 · 团队名片墙(装修组件)
|
// 数字名片 · 团队名片墙(装修组件)
|
||||||
// 数据口径: 商家在设计面板手动挑选 card_ids, 走 /card/api/diy/cards 公开摘要
|
// 布局: grid(1/2 列) / list(整行) / scroll(横滑); 骨架屏 + 逐项入场 + 按压反馈
|
||||||
import DiyMinx from './minx.js';
|
import { CardTokens, px2rpx, flagOn, num, initialOf, avatarBg } from './diy-card-common.js';
|
||||||
import golbalConfig from '@/common/js/golbalConfig.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'diy-card-list',
|
name: 'diy-card-list',
|
||||||
props: {
|
props: {
|
||||||
value: { type: Object }
|
value: { type: Object }
|
||||||
},
|
},
|
||||||
mixins: [DiyMinx, golbalConfig],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
list: []
|
list: [],
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
v() {
|
||||||
|
return this.value || {};
|
||||||
|
},
|
||||||
cardIds() {
|
cardIds() {
|
||||||
return String((this.value || {}).cardIds || '')
|
return String(this.v.cardIds || '')
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(s => s.trim())
|
.map(s => s.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.slice(0, 20)
|
.slice(0, 20)
|
||||||
.join(',');
|
.join(',');
|
||||||
},
|
},
|
||||||
showArrow() {
|
cardIdsConfigured() {
|
||||||
const flag = this.getDiyToggle(['showArrow']);
|
return String(this.v.cardIds || '').trim() !== '';
|
||||||
return flag === undefined ? true : flag;
|
|
||||||
},
|
},
|
||||||
emptyTip() {
|
layout() {
|
||||||
const v = this.value || {};
|
const l = String(this.v.layout || 'grid');
|
||||||
return String(v.cardIds || '').trim() === '' ? '商家还未挑选名片' : '暂无可展示的名片';
|
return ['grid', 'list', 'scroll'].includes(l) ? l : 'grid';
|
||||||
|
},
|
||||||
|
columns() {
|
||||||
|
return Number(this.v.columns) === 1 ? 1 : 2;
|
||||||
|
},
|
||||||
|
showTitle() {
|
||||||
|
return String(this.v.title || '').trim() !== '';
|
||||||
|
},
|
||||||
|
showCount() {
|
||||||
|
return flagOn(this.v.showCount, false);
|
||||||
|
},
|
||||||
|
showMore() {
|
||||||
|
return flagOn(this.v.showMore, false);
|
||||||
|
},
|
||||||
|
moreText() {
|
||||||
|
return this.v.moreText || '';
|
||||||
|
},
|
||||||
|
showArrow() {
|
||||||
|
return flagOn(this.v.showArrow, true);
|
||||||
|
},
|
||||||
|
skeletonCount() {
|
||||||
|
return this.layout === 'list' ? 3 : this.columns * 2;
|
||||||
},
|
},
|
||||||
wallStyle() {
|
wallStyle() {
|
||||||
const v = this.value || {};
|
|
||||||
return this.$util.objToStyle({
|
return this.$util.objToStyle({
|
||||||
background: v.bgColor || '#FFFFFF',
|
background: this.v.bgColor || CardTokens.color.bg,
|
||||||
borderRadius: this.px2rpx(v.borderRadius || 12) + 'rpx',
|
borderRadius: px2rpx(num(this.v.borderRadius, CardTokens.radius.md)) + 'rpx',
|
||||||
padding: this.px2rpx(v.padding || 12) + 'rpx'
|
padding: px2rpx(num(this.v.padding, CardTokens.space.md)) + 'rpx',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
titleStyle() {
|
titleStyle() {
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).titleColor || '#333333' });
|
return this.$util.objToStyle({ color: this.v.titleColor || CardTokens.color.text });
|
||||||
},
|
},
|
||||||
nameStyle() {
|
nameStyle() {
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).nameColor || '#1F2329' });
|
return this.$util.objToStyle({ color: this.v.nameColor || CardTokens.color.text });
|
||||||
},
|
},
|
||||||
infoStyle() {
|
infoStyle() {
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).infoColor || '#888888' });
|
return this.$util.objToStyle({ color: this.v.infoColor || CardTokens.color.textSub });
|
||||||
|
},
|
||||||
|
countStyle() {
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
color: CardTokens.color.primary,
|
||||||
|
background: 'rgba(47,107,255,0.08)',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
moreStyle() {
|
||||||
|
return this.$util.objToStyle({ color: CardTokens.color.textLabel });
|
||||||
},
|
},
|
||||||
itemStyle() {
|
itemStyle() {
|
||||||
const v = this.value || {};
|
const gap = num(this.v.gap, 10);
|
||||||
const gap = Number(v.gap) || 10;
|
const width = this.layout === 'list' || this.columns === 1 ? '100%' : 'calc(50% - ' + px2rpx(gap / 2) + 'rpx)';
|
||||||
const cols = Number(v.columns) === 1 ? 1 : 2;
|
|
||||||
const width = cols === 1 ? '100%' : 'calc(50% - ' + this.px2rpx(gap / 2) + 'rpx)';
|
|
||||||
return this.$util.objToStyle({
|
return this.$util.objToStyle({
|
||||||
width: width,
|
width: width,
|
||||||
marginBottom: this.px2rpx(gap) + 'rpx'
|
marginBottom: px2rpx(gap) + 'rpx',
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
scrollItemStyle() {
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
width: '260rpx',
|
||||||
|
marginRight: px2rpx(num(this.v.gap, 10)) + 'rpx',
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'value.cardIds'() {
|
'value.cardIds'() {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
px2rpx(v) {
|
initialOf,
|
||||||
const n = Number(v);
|
avatarStyle(item) {
|
||||||
return isNaN(n) ? 0 : Math.round(n * 2);
|
return this.$util.objToStyle({ background: avatarBg(item) });
|
||||||
|
},
|
||||||
|
badgeStyle(item) {
|
||||||
|
return this.$util.objToStyle({ background: avatarBg(item) });
|
||||||
|
},
|
||||||
|
isEnterprise(item) {
|
||||||
|
return item && item.card_type === 'enterprise';
|
||||||
|
},
|
||||||
|
infoLine(item) {
|
||||||
|
const showJob = flagOn(this.v.showJob, true);
|
||||||
|
const showCompany = flagOn(this.v.showCompany, true);
|
||||||
|
const parts = [];
|
||||||
|
if (showJob && item.job_title) parts.push(item.job_title);
|
||||||
|
if (showCompany && item.company) parts.push(item.company);
|
||||||
|
return parts.join(' · ');
|
||||||
},
|
},
|
||||||
fetchData() {
|
fetchData() {
|
||||||
if (this.cardIds === '') {
|
if (this.cardIds === '') {
|
||||||
this.list = [];
|
this.list = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.loading = true;
|
||||||
this.$api.sendRequest({
|
this.$api.sendRequest({
|
||||||
url: '/card/api/diy/cards',
|
url: '/card/api/diy/cards',
|
||||||
data: { card_ids: this.cardIds },
|
data: { card_ids: this.cardIds },
|
||||||
success: res => {
|
success: res => {
|
||||||
|
this.loading = false;
|
||||||
if (res.code == 0 && res.data) {
|
if (res.code == 0 && res.data) {
|
||||||
this.list = res.data.list || [];
|
this.list = res.data.list || [];
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
fail: () => {
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
avatarStyle(item) {
|
|
||||||
return this.$util.objToStyle({ background: item.theme_color || '#2F6BFF' });
|
|
||||||
},
|
|
||||||
initialStyle(item) {
|
|
||||||
return this.$util.objToStyle({ color: '#FFFFFF' });
|
|
||||||
},
|
|
||||||
infoLine(item) {
|
|
||||||
const v = this.value || {};
|
|
||||||
const parts = [];
|
|
||||||
const showJob = this.getDiyToggle(['showJob']);
|
|
||||||
const showCompany = this.getDiyToggle(['showCompany']);
|
|
||||||
if ((showJob === undefined || showJob) && item.job_title) parts.push(item.job_title);
|
|
||||||
if ((showCompany === undefined || showCompany) && item.company) parts.push(item.company);
|
|
||||||
return parts.join(' · ');
|
|
||||||
},
|
|
||||||
goDetail(item) {
|
goDetail(item) {
|
||||||
if (!item || !item.card_id) return;
|
if (!item || !item.card_id) return;
|
||||||
this.$util.redirectTo('/pages_tool/card/detail', { card_id: item.card_id });
|
this.$util.redirectTo('/pages_tool/card/detail', { card_id: item.card_id });
|
||||||
}
|
},
|
||||||
}
|
goMore() {
|
||||||
|
const mode = String(this.v.moreLinkMode || 'list');
|
||||||
|
let url = '/pages_tool/card/list';
|
||||||
|
if (mode === 'contacts') {
|
||||||
|
url = '/pages_tool/card/contacts';
|
||||||
|
} else if (mode === 'custom') {
|
||||||
|
url = String(this.v.moreLinkUrl || '').trim();
|
||||||
|
if (url === '' || url.indexOf('/') !== 0) {
|
||||||
|
uni.showToast({ title: '自定义路径需以 / 开头', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$util.redirectTo(url);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -145,86 +234,266 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.wall-title {
|
.wall-head {
|
||||||
display: block;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wall-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wall-item {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 2rpx solid #f0f0f0;
|
margin-bottom: 20rpx;
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.item-avatar {
|
.wall-title {
|
||||||
flex-shrink: 0;
|
|
||||||
width: 80rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.avatar-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-initial {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
margin-left: 16rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.item-name {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-meta {
|
|
||||||
font-size: 22rpx;
|
|
||||||
margin-top: 4rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-arrow {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: 8rpx;
|
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wall-count {
|
||||||
|
font-size: 20rpx;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
padding: 2rpx 12rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wall-more {
|
||||||
|
font-size: 22rpx;
|
||||||
|
padding: 8rpx 0 8rpx 16rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 网格/列表 */
|
||||||
|
.wall-grid {
|
||||||
|
&.is-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wall-item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 2rpx solid #f0f0f0;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-initial {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 2rpx 10rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-meta {
|
||||||
|
font-size: 22rpx;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-arrow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 横滑 */
|
||||||
|
.wall-scroll {
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.scroll-item {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2rpx solid #f0f0f0;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx 16rpx;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.item-avatar {
|
||||||
|
width: 104rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-initial {
|
||||||
|
font-size: 42rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-meta {
|
||||||
|
font-size: 20rpx;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 12rpx;
|
||||||
|
right: 12rpx;
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 2rpx 10rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空态 */
|
||||||
.wall-empty {
|
.wall-empty {
|
||||||
padding: 30rpx 0;
|
display: flex;
|
||||||
font-size: 24rpx;
|
flex-direction: column;
|
||||||
text-align: center;
|
align-items: center;
|
||||||
|
padding: 40rpx 0;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 56rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
font-size: 24rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏 */
|
||||||
|
.wall-skeleton {
|
||||||
|
.sk-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.sk-avatar {
|
||||||
|
width: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-col {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk {
|
||||||
|
background: linear-gradient(90deg, #f2f3f5 25%, #e8eaee 37%, #f2f3f5 63%);
|
||||||
|
background-size: 400rpx 100%;
|
||||||
|
animation: sk-shimmer 1.2s linear infinite;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
|
||||||
|
&.sk-line {
|
||||||
|
height: 24rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade-up {
|
||||||
|
animation: card-fade-up 240ms ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-press {
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sk-shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -200rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 200rpx 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes card-fade-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(12rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
582
components-diy/diy-card-mini.vue
Normal file
582
components-diy/diy-card-mini.vue
Normal file
@@ -0,0 +1,582 @@
|
|||||||
|
<template>
|
||||||
|
<view data-component-name="diy-card-mini" class="card-mini">
|
||||||
|
<!-- 骨架屏: 首屏加载/切换名片 -->
|
||||||
|
<view v-if="loading" class="mini-skeleton" :style="wrapStyle">
|
||||||
|
<view class="sk-row">
|
||||||
|
<view class="sk sk-avatar"></view>
|
||||||
|
<view class="sk-col">
|
||||||
|
<view class="sk sk-line" style="width:55%"></view>
|
||||||
|
<view class="sk sk-line" style="width:35%;margin-top:16rpx;"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="sk sk-line" style="width:100%;margin-top:28rpx;"></view>
|
||||||
|
<view class="sk sk-line" style="width:72%;margin-top:16rpx;"></view>
|
||||||
|
<view class="sk sk-btn" style="margin-top:28rpx;"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空态: 未拿到名片 -->
|
||||||
|
<view v-else-if="!card" class="mini-empty" :style="wrapStyle">
|
||||||
|
<text class="empty-icon">💳</text>
|
||||||
|
<text class="empty-tip" :style="subTextStyle">{{ emptyTip || '暂无名片数据' }}</text>
|
||||||
|
<view v-if="emptyActionText" class="empty-btn" :style="emptyBtnStyle" hover-class="btn-press" @tap="onEmptyAction">
|
||||||
|
<text class="empty-btn-text">{{ emptyActionText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 名片卡 -->
|
||||||
|
<view v-else class="mini-card fade-up" :style="wrapStyle">
|
||||||
|
<!-- 角标插槽(如 self 组件的「我的名片」) -->
|
||||||
|
<view class="mini-badge-slot">
|
||||||
|
<slot name="badge"></slot>
|
||||||
|
</view>
|
||||||
|
<!-- 封面图(可选) + 遮罩 -->
|
||||||
|
<image v-if="showCover" class="mini-cover" :src="$util.img(card.cover)" mode="aspectFill" />
|
||||||
|
<view v-if="showCover" class="mini-cover-scrim"></view>
|
||||||
|
|
||||||
|
<!-- 主题渐变饰条(可选) -->
|
||||||
|
<view v-if="showGradient" class="mini-gradient" :style="gradientStyle"></view>
|
||||||
|
|
||||||
|
<!-- Logo(可选, 右上) -->
|
||||||
|
<image v-if="showLogo && card.logo" class="mini-logo" :src="$util.img(card.logo)" mode="aspectFit" />
|
||||||
|
|
||||||
|
<view class="mini-head">
|
||||||
|
<view class="mini-avatar" :style="avatarStyle">
|
||||||
|
<image v-if="card.avatar" class="avatar-img" :src="$util.img(card.avatar)" mode="aspectFill" />
|
||||||
|
<text v-else class="avatar-initial">{{ initialOf(card.name) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="mini-head-info">
|
||||||
|
<view class="name-row">
|
||||||
|
<text class="mini-name" :style="nameStyle">{{ card.name || '未命名名片' }}</text>
|
||||||
|
<text v-if="isEnterprise" class="mini-badge" :style="badgeStyle">企业</text>
|
||||||
|
</view>
|
||||||
|
<text v-if="card.card_title" class="mini-card-title" :style="subTextStyle">{{ card.card_title }}</text>
|
||||||
|
<text v-if="subLine" class="mini-sub" :style="subTextStyle">{{ subLine }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 资料行(可折叠) -->
|
||||||
|
<view v-if="showRowsBlock && shownRows.length" class="mini-rows">
|
||||||
|
<view v-for="(row, i) in shownRows" :key="i" class="mini-row">
|
||||||
|
<text class="row-label" :style="labelStyle">{{ row.label }}</text>
|
||||||
|
<text class="row-value" :style="valueStyle">{{ row.value }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="hasMoreRows" class="row-toggle" hover-class="btn-press" @tap="expanded = !expanded">
|
||||||
|
<text class="toggle-text" :style="toggleStyle">{{ expanded ? '收起资料' : '展开全部资料(' + hiddenCount + ')' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 快捷操作(芯片, 最小触达 72rpx; 首个为主动作) -->
|
||||||
|
<view v-if="showActs && acts.length" class="mini-acts">
|
||||||
|
<view v-for="(act, i) in acts" :key="i" class="act-chip"
|
||||||
|
:style="i === 0 ? actPrimaryStyle : actGhostStyle" hover-class="btn-press" @tap.stop="onAct(act)">
|
||||||
|
<text class="act-text" :style="i === 0 ? actPrimaryTextStyle : actGhostTextStyle">{{ act.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 二维码(可选) -->
|
||||||
|
<view v-if="showQr && card.qr_url" class="mini-qr">
|
||||||
|
<image class="qr-img" :src="$util.img(card.qr_url)" mode="aspectFit" />
|
||||||
|
<text class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 查看完整名片 -->
|
||||||
|
<view v-if="showDetailBtn" class="mini-detail" :style="detailBtnStyle" hover-class="btn-press" @tap.stop="onDetail">
|
||||||
|
<text class="detail-text">{{ detailBtnText || '查看完整名片' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 数字名片 · 名片迷你卡基件(show/self 共用, 设计系统单一事实来源)
|
||||||
|
// 消费名片自身设计资产: cover 封面 / logo / theme_color / 渐变末色与角度 / card_title
|
||||||
|
import {
|
||||||
|
CardTokens, px2rpx, flagOn, num, initialOf, avatarBg, themeGradient, visibleRows,
|
||||||
|
} from './diy-card-common.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'diy-card-mini',
|
||||||
|
props: {
|
||||||
|
card: { type: Object, default: null },
|
||||||
|
config: { type: Object, default: () => ({}) },
|
||||||
|
loading: { type: Boolean, default: false },
|
||||||
|
emptyTip: { type: String, default: '' },
|
||||||
|
emptyActionText: { type: String, default: '' },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { expanded: false };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
c() {
|
||||||
|
return this.config || {};
|
||||||
|
},
|
||||||
|
wrapStyle() {
|
||||||
|
const c = this.c;
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
background: flagOn(c.useCover, false) && this.card && this.card.cover ? 'transparent' : (c.bgColor || CardTokens.color.bg),
|
||||||
|
borderRadius: px2rpx(num(c.borderRadius, CardTokens.radius.md)) + 'rpx',
|
||||||
|
padding: px2rpx(num(c.padding, CardTokens.space.lg)) + 'rpx',
|
||||||
|
boxShadow: flagOn(c.showShadow, true) ? CardTokens.shadow.card : 'none',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showCover() {
|
||||||
|
return flagOn(this.c.useCover, false) && !!(this.card && this.card.cover);
|
||||||
|
},
|
||||||
|
showGradient() {
|
||||||
|
return flagOn(this.c.showGradient, false);
|
||||||
|
},
|
||||||
|
showLogo() {
|
||||||
|
return flagOn(this.c.showLogo, false);
|
||||||
|
},
|
||||||
|
showActs() {
|
||||||
|
return flagOn(this.c.showQuick, true);
|
||||||
|
},
|
||||||
|
showRowsBlock() {
|
||||||
|
// 资料行总开关(面板「显示资料行」); 历史配置无此字段时默认显示, 兼容 1.0
|
||||||
|
return flagOn(this.c.showRows, true);
|
||||||
|
},
|
||||||
|
showQr() {
|
||||||
|
return flagOn(this.c.showQr, false);
|
||||||
|
},
|
||||||
|
showDetailBtn() {
|
||||||
|
return flagOn(this.c.showDetailBtn, true);
|
||||||
|
},
|
||||||
|
qrTip() {
|
||||||
|
return this.c.qrTip || '';
|
||||||
|
},
|
||||||
|
detailBtnText() {
|
||||||
|
return this.c.detailBtnText || '';
|
||||||
|
},
|
||||||
|
maxRows() {
|
||||||
|
return num(this.c.maxRows, 4);
|
||||||
|
},
|
||||||
|
rowsInfo() {
|
||||||
|
if (!this.card) return { shown: [], hasMore: false };
|
||||||
|
return visibleRows(this.card.rows, this.expanded ? 0 : this.maxRows);
|
||||||
|
},
|
||||||
|
shownRows() {
|
||||||
|
return this.rowsInfo.shown;
|
||||||
|
},
|
||||||
|
hasMoreRows() {
|
||||||
|
return !this.expanded && this.rowsInfo.hasMore;
|
||||||
|
},
|
||||||
|
hiddenCount() {
|
||||||
|
return this.rowsInfo.rest.length;
|
||||||
|
},
|
||||||
|
acts() {
|
||||||
|
return this.card && Array.isArray(this.card.acts) ? this.card.acts : [];
|
||||||
|
},
|
||||||
|
isEnterprise() {
|
||||||
|
return this.card && this.card.card_type === 'enterprise';
|
||||||
|
},
|
||||||
|
subLine() {
|
||||||
|
const c = this.card || {};
|
||||||
|
return [c.job_title, c.company].filter(Boolean).join(' · ');
|
||||||
|
},
|
||||||
|
gradientStyle() {
|
||||||
|
return this.$util.objToStyle({ background: themeGradient(this.card) });
|
||||||
|
},
|
||||||
|
avatarStyle() {
|
||||||
|
return this.$util.objToStyle({ background: avatarBg(this.card) });
|
||||||
|
},
|
||||||
|
nameStyle() {
|
||||||
|
return this.$util.objToStyle({ color: this.c.nameColor || CardTokens.color.text });
|
||||||
|
},
|
||||||
|
subTextStyle() {
|
||||||
|
return this.$util.objToStyle({ color: this.c.infoColor || CardTokens.color.textSub });
|
||||||
|
},
|
||||||
|
labelStyle() {
|
||||||
|
return this.$util.objToStyle({ color: this.c.labelColor || CardTokens.color.textLabel });
|
||||||
|
},
|
||||||
|
valueStyle() {
|
||||||
|
return this.$util.objToStyle({ color: this.c.infoColor || CardTokens.color.textSub });
|
||||||
|
},
|
||||||
|
badgeStyle() {
|
||||||
|
return this.$util.objToStyle({ background: avatarBg(this.card) });
|
||||||
|
},
|
||||||
|
toggleStyle() {
|
||||||
|
return this.$util.objToStyle({ color: (this.card && this.card.theme_color) || CardTokens.color.primary });
|
||||||
|
},
|
||||||
|
actPrimaryStyle() {
|
||||||
|
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
background: theme,
|
||||||
|
height: px2rpx(CardTokens.touch.chipHeight) + 'rpx',
|
||||||
|
padding: '0 ' + px2rpx(CardTokens.space.xl) + 'rpx',
|
||||||
|
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
actPrimaryTextStyle() {
|
||||||
|
return this.$util.objToStyle({ color: '#FFFFFF' });
|
||||||
|
},
|
||||||
|
actGhostStyle() {
|
||||||
|
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
background: 'transparent',
|
||||||
|
border: '2rpx solid ' + theme,
|
||||||
|
height: px2rpx(CardTokens.touch.chipHeight) + 'rpx',
|
||||||
|
padding: '0 ' + px2rpx(CardTokens.space.lg) + 'rpx',
|
||||||
|
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
actGhostTextStyle() {
|
||||||
|
return this.$util.objToStyle({ color: (this.card && this.card.theme_color) || CardTokens.color.primary });
|
||||||
|
},
|
||||||
|
detailBtnStyle() {
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
background: themeGradient(this.card),
|
||||||
|
height: px2rpx(CardTokens.touch.btnHeight) + 'rpx',
|
||||||
|
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
emptyBtnStyle() {
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
background: CardTokens.color.primary,
|
||||||
|
height: px2rpx(CardTokens.touch.btnHeight) + 'rpx',
|
||||||
|
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
|
||||||
|
padding: '0 ' + px2rpx(CardTokens.space.xxl) + 'rpx',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onAct(act) {
|
||||||
|
this.$emit('act', act);
|
||||||
|
},
|
||||||
|
onDetail() {
|
||||||
|
this.$emit('detail', this.card);
|
||||||
|
},
|
||||||
|
onEmptyAction() {
|
||||||
|
this.$emit('empty-action');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card-mini {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mini-card {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.mini-badge-slot {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-cover {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-cover-scrim {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-gradient {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 10rpx;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-logo {
|
||||||
|
position: absolute;
|
||||||
|
right: 24rpx;
|
||||||
|
top: 24rpx;
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-head {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mini-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 104rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-initial {
|
||||||
|
font-size: 42rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-head-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mini-name {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 2rpx 12rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-card-title {
|
||||||
|
font-size: 26rpx;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-sub {
|
||||||
|
font-size: 24rpx;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-rows {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
|
||||||
|
.mini-row {
|
||||||
|
display: flex;
|
||||||
|
line-height: 2;
|
||||||
|
|
||||||
|
.row-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 96rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-value {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 24rpx;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-toggle {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.toggle-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-acts {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.act-chip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0 16rpx 16rpx 0;
|
||||||
|
|
||||||
|
.act-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-qr {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.qr-img {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 180rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-tip {
|
||||||
|
font-size: 22rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-detail {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.detail-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空态 */
|
||||||
|
.mini-empty {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48rpx 24rpx;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 64rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
font-size: 24rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-btn {
|
||||||
|
margin-top: 28rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.empty-btn-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏 */
|
||||||
|
.mini-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.sk-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sk-avatar {
|
||||||
|
width: 104rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-col {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-btn {
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk {
|
||||||
|
background: linear-gradient(90deg, #f2f3f5 25%, #e8eaee 37%, #f2f3f5 63%);
|
||||||
|
background-size: 400rpx 100%;
|
||||||
|
animation: sk-shimmer 1.2s linear infinite;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
|
||||||
|
&.sk-line {
|
||||||
|
height: 28rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入场动效(逐项延迟由父组件传 animation-delay) */
|
||||||
|
.fade-up {
|
||||||
|
animation: card-fade-up 240ms ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按压反馈(芯片/按钮/空态按钮共用) */
|
||||||
|
.btn-press {
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sk-shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -200rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 200rpx 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes card-fade-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(12rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,65 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<view data-component-name="diy-card-self" class="diy-card-self">
|
<view data-component-name="diy-card-self" class="diy-card-self">
|
||||||
<!-- 未登录 + 登录引导兜底 -->
|
<!-- 未登录 + 登录引导兜底 -->
|
||||||
<view v-if="!hasToken && fallbackMode === 'none'" class="self-login" :style="boxStyle" @tap="goLogin">
|
<view v-if="!hasToken && fallbackMode === 'none'" class="self-login" :style="boxStyle" @tap="goLogin" hover-class="btn-press">
|
||||||
<text class="login-tip" :style="nameStyle">{{ value.loginTip || '登录后这里展示我的名片' }}</text>
|
<text class="login-tip" :style="nameStyle">{{ value.loginTip || '登录后这里展示我的名片' }}</text>
|
||||||
<view class="login-btn" :style="detailStyle">
|
<view class="login-btn" :style="loginBtnStyle">
|
||||||
<text class="login-btn-text">{{ value.loginBtnText || '去登录' }}</text>
|
<text class="login-btn-text">{{ value.loginBtnText || '去登录' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 名片卡(已登录=本人名片 / 未登录=兜底名片) -->
|
<!-- 名片卡(已登录=本人名片 / 未登录=兜底名片) -->
|
||||||
<view v-else-if="card" class="self-box" :style="boxStyle">
|
<diy-card-mini
|
||||||
<view v-if="isSelf" class="self-badge" :style="badgeStyle">
|
v-else-if="card"
|
||||||
<text class="badge-text">我的名片</text>
|
:card="card"
|
||||||
</view>
|
:config="miniConfig"
|
||||||
|
:loading="loading"
|
||||||
<view class="card-head">
|
@act="doAct"
|
||||||
<view class="head-avatar" :style="avatarStyle">
|
@detail="goDetail"
|
||||||
<image v-if="card.avatar" class="avatar-img" :src="$util.img(card.avatar)" mode="aspectFill" />
|
>
|
||||||
<text v-else class="avatar-initial">{{ (card.name || '名').substring(0, 1) }}</text>
|
<template v-if="isSelf" slot="badge">
|
||||||
|
<view class="self-badge" :style="badgeStyle">
|
||||||
|
<text class="badge-text">我的名片</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="head-info">
|
</template>
|
||||||
<text class="head-name" :style="nameStyle">{{ card.name || '未命名名片' }}</text>
|
</diy-card-mini>
|
||||||
<text v-if="subLine" class="head-sub" :style="infoStyle">{{ subLine }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="showQuick && card.acts && card.acts.length" class="card-acts">
|
|
||||||
<view v-for="(act, index) in card.acts" :key="index" class="act-btn" :style="actStyle" @tap.stop="doAct(act)">
|
|
||||||
<text class="act-text" :style="actTextStyle">{{ act.label }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="showDetailBtn" class="detail-btn" :style="detailStyle" @tap.stop="goDetail">
|
|
||||||
<text class="detail-text">{{ value.detailBtnText || '查看完整名片' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 已登录但没有任何名片可显示 -->
|
<!-- 已登录但没有任何名片可显示 -->
|
||||||
<view v-else class="self-empty" :style="boxStyle">
|
<view v-else-if="!loading" class="self-empty" :style="boxStyle">
|
||||||
<text class="empty-tip" :style="infoStyle">登录后即可展示我的名片</text>
|
<text class="empty-icon">🪪</text>
|
||||||
|
<text class="empty-tip" :style="subTextStyle">{{ value.noCardTip || '登录后即可展示我的名片' }}</text>
|
||||||
|
<view class="empty-btn" :style="loginBtnStyle" hover-class="btn-press" @tap="goCardList">
|
||||||
|
<text class="empty-btn-text">{{ value.noCardBtnText || '去看看名片' }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 数字名片 · 我的名片(装修组件)
|
// 数字名片 · 我的名片(装修组件)
|
||||||
// 运行时判定: 操作者已登录 → 本人名片(optionalAuth); 未登录 → 配置的兜底名片
|
// 运行时判定: 已登录 → 本人名片(optionalAuth); 未登录 → 配置的兜底名片
|
||||||
// fallbackMode: enterprise=店铺企业名片 / card=指定 fallbackCardId / none=登录引导
|
// fallbackMode: enterprise=店铺企业名片 / card=指定 fallbackCardId / none=登录引导
|
||||||
import DiyMinx from './minx.js';
|
// 视觉基件 diy-card-mini; 本文件管登录态/兜底/空态 CTA
|
||||||
import golbalConfig from '@/common/js/golbalConfig.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'diy-card-self',
|
name: 'diy-card-self',
|
||||||
props: {
|
props: {
|
||||||
value: { type: Object }
|
value: { type: Object }
|
||||||
},
|
},
|
||||||
mixins: [DiyMinx, golbalConfig],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
card: null,
|
card: null,
|
||||||
isSelf: false
|
isSelf: false,
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -74,48 +64,55 @@ export default {
|
|||||||
const m = String((this.value || {}).fallbackMode || 'enterprise');
|
const m = String((this.value || {}).fallbackMode || 'enterprise');
|
||||||
return ['enterprise', 'card', 'none'].includes(m) ? m : 'enterprise';
|
return ['enterprise', 'card', 'none'].includes(m) ? m : 'enterprise';
|
||||||
},
|
},
|
||||||
showQuick() {
|
miniConfig() {
|
||||||
const flag = this.getDiyToggle(['showQuick']);
|
const v = this.value || {};
|
||||||
return flag === undefined ? true : flag;
|
return {
|
||||||
},
|
bgColor: v.bgColor,
|
||||||
showDetailBtn() {
|
nameColor: v.nameColor,
|
||||||
const flag = this.getDiyToggle(['showDetailBtn']);
|
infoColor: v.infoColor,
|
||||||
return flag === undefined ? true : flag;
|
labelColor: v.labelColor,
|
||||||
},
|
borderRadius: v.borderRadius,
|
||||||
subLine() {
|
padding: v.padding,
|
||||||
const c = this.card || {};
|
showQuick: v.showQuick,
|
||||||
return [c.job_title, c.company].filter(Boolean).join(' · ');
|
showDetailBtn: v.showDetailBtn,
|
||||||
|
detailBtnText: v.detailBtnText,
|
||||||
|
// 2.0 新增视觉开关
|
||||||
|
useCover: v.useCover,
|
||||||
|
showGradient: v.showGradient,
|
||||||
|
showLogo: v.showLogo,
|
||||||
|
showShadow: v.showShadow,
|
||||||
|
showRows: v.showRows,
|
||||||
|
maxRows: v.maxRows,
|
||||||
|
showQr: v.showQr,
|
||||||
|
qrTip: v.qrTip,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
boxStyle() {
|
boxStyle() {
|
||||||
const v = this.value || {};
|
const v = this.value || {};
|
||||||
return this.$util.objToStyle({
|
return this.$util.objToStyle({
|
||||||
background: v.bgColor || '#FFFFFF',
|
background: v.bgColor || '#FFFFFF',
|
||||||
borderRadius: this.px2rpx(v.borderRadius || 16) + 'rpx',
|
borderRadius: this.px2rpx(v.borderRadius || 16) + 'rpx',
|
||||||
padding: this.px2rpx(v.padding || 16) + 'rpx'
|
padding: this.px2rpx(v.padding || 16) + 'rpx',
|
||||||
|
boxShadow: '0 4rpx 16rpx rgba(15,23,42,0.06)',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
avatarStyle() {
|
|
||||||
return this.$util.objToStyle({ background: (this.card || {}).theme_color || '#2F6BFF' });
|
|
||||||
},
|
|
||||||
nameStyle() {
|
nameStyle() {
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).nameColor || '#1F2329' });
|
return this.$util.objToStyle({ color: (this.value || {}).nameColor || '#1F2329' });
|
||||||
},
|
},
|
||||||
infoStyle() {
|
subTextStyle() {
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).infoColor || '#888888' });
|
return this.$util.objToStyle({ color: (this.value || {}).infoColor || '#8A8F99' });
|
||||||
},
|
|
||||||
actStyle() {
|
|
||||||
const theme = (this.card || {}).theme_color || '#2F6BFF';
|
|
||||||
return this.$util.objToStyle({ borderColor: theme });
|
|
||||||
},
|
|
||||||
actTextStyle() {
|
|
||||||
return this.$util.objToStyle({ color: (this.card || {}).theme_color || '#2F6BFF' });
|
|
||||||
},
|
|
||||||
detailStyle() {
|
|
||||||
return this.$util.objToStyle({ background: (this.card || {}).theme_color || '#2F6BFF' });
|
|
||||||
},
|
},
|
||||||
badgeStyle() {
|
badgeStyle() {
|
||||||
return this.$util.objToStyle({ background: (this.card || {}).theme_color || '#2F6BFF' });
|
return this.$util.objToStyle({ background: (this.card || {}).theme_color || '#2F6BFF' });
|
||||||
}
|
},
|
||||||
|
loginBtnStyle() {
|
||||||
|
return this.$util.objToStyle({
|
||||||
|
background: (this.card || {}).theme_color || '#2F6BFF',
|
||||||
|
height: '80rpx',
|
||||||
|
borderRadius: '999rpx',
|
||||||
|
padding: '0 48rpx',
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
hasToken() {
|
hasToken() {
|
||||||
@@ -126,7 +123,7 @@ export default {
|
|||||||
},
|
},
|
||||||
'value.fallbackCardId'() {
|
'value.fallbackCardId'() {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
@@ -141,13 +138,15 @@ export default {
|
|||||||
this.card = null;
|
this.card = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.loading = true;
|
||||||
this.$api.sendRequest({
|
this.$api.sendRequest({
|
||||||
url: '/card/api/diy/mycard',
|
url: '/card/api/diy/mycard',
|
||||||
data: {
|
data: {
|
||||||
fallback_mode: this.fallbackMode,
|
fallback_mode: this.fallbackMode,
|
||||||
fallback_card_id: String((this.value || {}).fallbackCardId || '').trim()
|
fallback_card_id: String((this.value || {}).fallbackCardId || '').trim(),
|
||||||
},
|
},
|
||||||
success: res => {
|
success: res => {
|
||||||
|
this.loading = false;
|
||||||
if (res.code == 0 && res.data) {
|
if (res.code == 0 && res.data) {
|
||||||
this.card = res.data.card || null;
|
this.card = res.data.card || null;
|
||||||
this.isSelf = res.data.is_self == 1;
|
this.isSelf = res.data.is_self == 1;
|
||||||
@@ -155,12 +154,20 @@ export default {
|
|||||||
this.card = null;
|
this.card = null;
|
||||||
this.isSelf = false;
|
this.isSelf = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
fail: () => {
|
||||||
|
this.loading = false;
|
||||||
|
this.card = null;
|
||||||
|
this.isSelf = false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
goLogin() {
|
goLogin() {
|
||||||
this.$util.redirectTo('/pages_tool/login/login');
|
this.$util.redirectTo('/pages_tool/login/login');
|
||||||
},
|
},
|
||||||
|
goCardList() {
|
||||||
|
this.$util.redirectTo('/pages_tool/card/list');
|
||||||
|
},
|
||||||
doAct(act) {
|
doAct(act) {
|
||||||
if (!act || !act.value) return;
|
if (!act || !act.value) return;
|
||||||
if (act.type === 'tel') {
|
if (act.type === 'tel') {
|
||||||
@@ -168,7 +175,7 @@ export default {
|
|||||||
} else if (act.type === 'wx') {
|
} else if (act.type === 'wx') {
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: act.value,
|
data: act.value,
|
||||||
success: () => uni.showToast({ title: '微信号已复制', icon: 'none' })
|
success: () => uni.showToast({ title: '微信号已复制', icon: 'none' }),
|
||||||
});
|
});
|
||||||
} else if (act.type === 'mail') {
|
} else if (act.type === 'mail') {
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
@@ -177,21 +184,21 @@ export default {
|
|||||||
// #ifndef H5
|
// #ifndef H5
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: act.value,
|
data: act.value,
|
||||||
success: () => uni.showToast({ title: '邮箱已复制', icon: 'none' })
|
success: () => uni.showToast({ title: '邮箱已复制', icon: 'none' }),
|
||||||
});
|
});
|
||||||
// #endif
|
// #endif
|
||||||
} else {
|
} else {
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: act.value,
|
data: act.value,
|
||||||
success: () => uni.showToast({ title: '已复制', icon: 'none' })
|
success: () => uni.showToast({ title: '已复制', icon: 'none' }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goDetail() {
|
goDetail(card) {
|
||||||
if (!this.card || !this.card.card_id) return;
|
if (!card || !card.card_id) return;
|
||||||
this.$util.redirectTo('/pages_tool/card/detail', { card_id: this.card.card_id });
|
this.$util.redirectTo('/pages_tool/card/detail', { card_id: card.card_id });
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -200,107 +207,19 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.self-box {
|
/* 「我的名片」角标: 覆在基件右上(基件已留 logo 位, badge 靠左一点避免重叠) */
|
||||||
width: 100%;
|
.self-badge {
|
||||||
box-sizing: border-box;
|
position: absolute;
|
||||||
|
top: 10rpx;
|
||||||
|
left: 10rpx;
|
||||||
|
z-index: 10;
|
||||||
|
border-radius: 999rpx 0 16rpx 0;
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
|
||||||
.self-badge {
|
.badge-text {
|
||||||
position: absolute;
|
font-size: 20rpx;
|
||||||
top: 0;
|
color: #ffffff;
|
||||||
right: 0;
|
font-weight: 600;
|
||||||
border-radius: 0 16rpx 0 16rpx;
|
|
||||||
padding: 6rpx 20rpx;
|
|
||||||
|
|
||||||
.badge-text {
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.head-avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 104rpx;
|
|
||||||
height: 104rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.avatar-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-initial {
|
|
||||||
font-size: 44rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.head-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
margin-left: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.head-name {
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.head-sub {
|
|
||||||
font-size: 24rpx;
|
|
||||||
margin-top: 6rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-acts {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.act-btn {
|
|
||||||
border: 2rpx solid;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
padding: 10rpx 28rpx;
|
|
||||||
margin: 0 16rpx 16rpx 0;
|
|
||||||
|
|
||||||
.act-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-btn {
|
|
||||||
margin-top: 12rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
padding: 16rpx 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.detail-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +230,12 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48rpx 24rpx;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 64rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.login-tip,
|
.login-tip,
|
||||||
.empty-tip {
|
.empty-tip {
|
||||||
@@ -319,17 +244,25 @@ export default {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-btn {
|
.login-btn,
|
||||||
margin-top: 24rpx;
|
.empty-btn {
|
||||||
border-radius: 999rpx;
|
margin-top: 28rpx;
|
||||||
padding: 14rpx 48rpx;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
.login-btn-text {
|
.login-btn-text,
|
||||||
font-size: 24rpx;
|
.empty-btn-text {
|
||||||
color: #ffffff;
|
font-size: 26rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-press {
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,138 +1,82 @@
|
|||||||
<template>
|
<template>
|
||||||
<view data-component-name="diy-card-show" class="diy-card-show">
|
<view data-component-name="diy-card-show" class="diy-card-show">
|
||||||
<view v-if="card" class="card-box" :style="boxStyle">
|
<diy-card-mini
|
||||||
<view class="card-head">
|
:card="card"
|
||||||
<view class="head-avatar" :style="avatarStyle">
|
:config="miniConfig"
|
||||||
<image v-if="card.avatar" class="avatar-img" :src="$util.img(card.avatar)" mode="aspectFill" />
|
:loading="loading"
|
||||||
<text v-else class="avatar-initial">{{ (card.name || '名').substring(0, 1) }}</text>
|
empty-tip="商家还未选择名片"
|
||||||
</view>
|
@act="doAct"
|
||||||
<view class="head-info">
|
@detail="goDetail"
|
||||||
<text class="head-name" :style="nameStyle">{{ card.name || '未命名名片' }}</text>
|
></diy-card-mini>
|
||||||
<text v-if="subLine" class="head-sub" :style="infoStyle">{{ subLine }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="showRows && card.rows && card.rows.length" class="card-rows">
|
|
||||||
<view v-for="(row, index) in card.rows" :key="index" class="row-line">
|
|
||||||
<text class="row-label" :style="labelStyle">{{ row.label }}</text>
|
|
||||||
<text class="row-value" :style="infoStyle">{{ row.value }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="showQuick && card.acts && card.acts.length" class="card-acts">
|
|
||||||
<view v-for="(act, index) in card.acts" :key="index" class="act-btn" :style="actStyle" @tap.stop="doAct(act)">
|
|
||||||
<text class="act-text" :style="actTextStyle">{{ act.label }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="showDetailBtn" class="detail-btn" :style="detailStyle" @tap.stop="goDetail">
|
|
||||||
<text class="detail-text">{{ value.detailBtnText || '查看完整名片' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-else class="card-empty" :style="boxStyle">
|
|
||||||
<text class="empty-tip" :style="infoStyle">{{ emptyTip }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 数字名片 · 精选名片(装修组件): 单张名片的原生迷你卡
|
// 数字名片 · 精选名片(装修组件)
|
||||||
// 数据口径: /card/api/diy/card?card_id=.. 公开摘要(与 wap 公开页同源)
|
// 视觉基件 diy-card-mini(封面/渐变/Logo/主题色/折叠/芯片/二维码), 本文件只管数据
|
||||||
import DiyMinx from './minx.js';
|
|
||||||
import golbalConfig from '@/common/js/golbalConfig.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'diy-card-show',
|
name: 'diy-card-show',
|
||||||
props: {
|
props: {
|
||||||
value: { type: Object }
|
value: { type: Object }
|
||||||
},
|
},
|
||||||
mixins: [DiyMinx, golbalConfig],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
card: null
|
card: null,
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showRows() {
|
miniConfig() {
|
||||||
const flag = this.getDiyToggle(['showRows']);
|
|
||||||
return flag === undefined ? true : flag;
|
|
||||||
},
|
|
||||||
showQuick() {
|
|
||||||
const flag = this.getDiyToggle(['showQuick']);
|
|
||||||
return flag === undefined ? true : flag;
|
|
||||||
},
|
|
||||||
showDetailBtn() {
|
|
||||||
const flag = this.getDiyToggle(['showDetailBtn']);
|
|
||||||
return flag === undefined ? true : flag;
|
|
||||||
},
|
|
||||||
emptyTip() {
|
|
||||||
return String((this.value || {}).cardId || '').trim() === '' ? '商家还未选择名片' : '名片不存在或已删除';
|
|
||||||
},
|
|
||||||
subLine() {
|
|
||||||
const c = this.card || {};
|
|
||||||
return [c.job_title, c.company].filter(Boolean).join(' · ');
|
|
||||||
},
|
|
||||||
boxStyle() {
|
|
||||||
const v = this.value || {};
|
const v = this.value || {};
|
||||||
return this.$util.objToStyle({
|
return {
|
||||||
background: v.bgColor || '#FFFFFF',
|
bgColor: v.bgColor,
|
||||||
borderRadius: this.px2rpx(v.borderRadius || 16) + 'rpx',
|
nameColor: v.nameColor,
|
||||||
padding: this.px2rpx(v.padding || 16) + 'rpx'
|
infoColor: v.infoColor,
|
||||||
});
|
labelColor: v.labelColor,
|
||||||
|
borderRadius: v.borderRadius,
|
||||||
|
padding: v.padding,
|
||||||
|
showQuick: v.showQuick,
|
||||||
|
showDetailBtn: v.showDetailBtn,
|
||||||
|
detailBtnText: v.detailBtnText,
|
||||||
|
// 2.0 新增视觉开关
|
||||||
|
useCover: v.useCover,
|
||||||
|
showGradient: v.showGradient,
|
||||||
|
showLogo: v.showLogo,
|
||||||
|
showShadow: v.showShadow,
|
||||||
|
showRows: v.showRows,
|
||||||
|
maxRows: v.maxRows,
|
||||||
|
showQr: v.showQr,
|
||||||
|
qrTip: v.qrTip,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
avatarStyle() {
|
|
||||||
return this.$util.objToStyle({ background: (this.card || {}).theme_color || '#2F6BFF' });
|
|
||||||
},
|
|
||||||
nameStyle() {
|
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).nameColor || '#1F2329' });
|
|
||||||
},
|
|
||||||
infoStyle() {
|
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).infoColor || '#888888' });
|
|
||||||
},
|
|
||||||
labelStyle() {
|
|
||||||
return this.$util.objToStyle({ color: (this.value || {}).labelColor || '#999999' });
|
|
||||||
},
|
|
||||||
actStyle() {
|
|
||||||
const theme = (this.card || {}).theme_color || '#2F6BFF';
|
|
||||||
return this.$util.objToStyle({ borderColor: theme });
|
|
||||||
},
|
|
||||||
actTextStyle() {
|
|
||||||
return this.$util.objToStyle({ color: (this.card || {}).theme_color || '#2F6BFF' });
|
|
||||||
},
|
|
||||||
detailStyle() {
|
|
||||||
return this.$util.objToStyle({ background: (this.card || {}).theme_color || '#2F6BFF' });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'value.cardId'() {
|
'value.cardId'() {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
px2rpx(v) {
|
|
||||||
const n = Number(v);
|
|
||||||
return isNaN(n) ? 0 : Math.round(n * 2);
|
|
||||||
},
|
|
||||||
fetchData() {
|
fetchData() {
|
||||||
const cardId = String((this.value || {}).cardId || '').trim();
|
const cardId = String((this.value || {}).cardId || '').trim();
|
||||||
if (cardId === '') {
|
if (cardId === '') {
|
||||||
this.card = null;
|
this.card = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.loading = true;
|
||||||
this.$api.sendRequest({
|
this.$api.sendRequest({
|
||||||
url: '/card/api/diy/card',
|
url: '/card/api/diy/card',
|
||||||
data: { card_id: cardId },
|
data: { card_id: cardId },
|
||||||
success: res => {
|
success: res => {
|
||||||
if (res.code == 0 && res.data && res.data.card) {
|
this.loading = false;
|
||||||
this.card = res.data.card;
|
this.card = (res.code == 0 && res.data && res.data.card) ? res.data.card : null;
|
||||||
} else {
|
},
|
||||||
this.card = null;
|
fail: () => {
|
||||||
}
|
this.loading = false;
|
||||||
}
|
this.card = null;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
doAct(act) {
|
doAct(act) {
|
||||||
@@ -142,7 +86,7 @@ export default {
|
|||||||
} else if (act.type === 'wx') {
|
} else if (act.type === 'wx') {
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: act.value,
|
data: act.value,
|
||||||
success: () => uni.showToast({ title: '微信号已复制', icon: 'none' })
|
success: () => uni.showToast({ title: '微信号已复制', icon: 'none' }),
|
||||||
});
|
});
|
||||||
} else if (act.type === 'mail') {
|
} else if (act.type === 'mail') {
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
@@ -151,151 +95,26 @@ export default {
|
|||||||
// #ifndef H5
|
// #ifndef H5
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: act.value,
|
data: act.value,
|
||||||
success: () => uni.showToast({ title: '邮箱已复制', icon: 'none' })
|
success: () => uni.showToast({ title: '邮箱已复制', icon: 'none' }),
|
||||||
});
|
});
|
||||||
// #endif
|
// #endif
|
||||||
} else {
|
} else {
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: act.value,
|
data: act.value,
|
||||||
success: () => uni.showToast({ title: '已复制', icon: 'none' })
|
success: () => uni.showToast({ title: '已复制', icon: 'none' }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goDetail() {
|
goDetail(card) {
|
||||||
if (!this.card || !this.card.card_id) return;
|
if (!card || !card.card_id) return;
|
||||||
this.$util.redirectTo('/pages_tool/card/detail', { card_id: this.card.card_id });
|
this.$util.redirectTo('/pages_tool/card/detail', { card_id: card.card_id });
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.diy-card-show {
|
.diy-card-show {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.card-box {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.card-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.head-avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 104rpx;
|
|
||||||
height: 104rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.avatar-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-initial {
|
|
||||||
font-size: 44rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.head-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
margin-left: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.head-name {
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.head-sub {
|
|
||||||
font-size: 24rpx;
|
|
||||||
margin-top: 6rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-rows {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
padding-top: 20rpx;
|
|
||||||
border-top: 2rpx solid #f0f0f0;
|
|
||||||
|
|
||||||
.row-line {
|
|
||||||
display: flex;
|
|
||||||
line-height: 1.9;
|
|
||||||
|
|
||||||
.row-label {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 24rpx;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-value {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 24rpx;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-acts {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.act-btn {
|
|
||||||
border: 2rpx solid;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
padding: 10rpx 28rpx;
|
|
||||||
margin: 0 16rpx 16rpx 0;
|
|
||||||
|
|
||||||
.act-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-btn {
|
|
||||||
margin-top: 12rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
padding: 16rpx 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.detail-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-empty {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.empty-tip {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
padding: 30rpx 0;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user