662 lines
15 KiB
Vue
662 lines
15 KiB
Vue
<template>
|
||
<view class="cs-page">
|
||
<!-- 顶部段控: 名片模板 / 分享封面 -->
|
||
<view class="cs-seg">
|
||
<view class="cs-seg-item" :class="{ active: seg === 'tpl' }" @tap="switchSeg('tpl')">名片模板</view>
|
||
<view class="cs-seg-item" :class="{ active: seg === 'cover' }" @tap="switchSeg('cover')">分享封面</view>
|
||
</view>
|
||
|
||
<!-- ============ 名片模板 ============ -->
|
||
<block v-if="seg === 'tpl'">
|
||
<!-- 名片预览: 姓名/资料内容一律保持原样, 只改布局与配色
|
||
- 卡面底色跟随「选择背景」
|
||
- 选中的模板决定版式(pv-classic = 第一个模板, 参考图的纸感居中版) -->
|
||
<view class="cs-preview" :class="'pv-' + previewTplType" :style="previewStyle">
|
||
<!-- 特种纸肌理层: 真实可平铺纤维纹理图(参考图同款棉絮纸), 置于文字之下 -->
|
||
<image v-if="previewBgTexture" class="cs-preview-texture" :src="paperTexture" />
|
||
<text class="cs-name">{{ card.name }}</text>
|
||
<text class="cs-job">{{ card.job_title }}</text>
|
||
<text class="cs-company">{{ card.company }}</text>
|
||
|
||
<view class="cs-divider"></view>
|
||
|
||
<view class="cs-row" v-if="card.mobile">
|
||
<text class="cs-row-icon iconfont icon-dianhua"></text>
|
||
<text class="cs-row-text">{{ card.mobile }}</text>
|
||
</view>
|
||
<view class="cs-row" v-if="card.email">
|
||
<text class="cs-row-icon">@</text>
|
||
<text class="cs-row-text">{{ card.email }}</text>
|
||
</view>
|
||
<view class="cs-row" v-if="card.address">
|
||
<text class="cs-row-icon iconfont icon-dizhi"></text>
|
||
<text class="cs-row-text">{{ card.address }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 解锁更多 -->
|
||
<view class="cs-unlock" @tap="onUnlock">
|
||
<text class="cs-unlock-icon iconfont icon-huiyuan"></text>
|
||
<text class="cs-unlock-text">解锁更多新模板、个性化logo,自由排版</text>
|
||
<text class="cs-unlock-arrow iconfont icon-right"></text>
|
||
</view>
|
||
|
||
<!-- 选择模板 -->
|
||
<view class="cs-section">
|
||
<text class="cs-title">选择模板</text>
|
||
<scroll-view scroll-x class="cs-scroll">
|
||
<view class="cs-thumbs">
|
||
<view v-for="tpl in templates" :key="tpl.id" class="cs-thumb"
|
||
:class="'tpl-' + tpl.type" @tap="selectTpl(tpl.id)">
|
||
<view class="tpl-inner">
|
||
<view class="tpl-bar w70"></view>
|
||
<view class="tpl-bar w45"></view>
|
||
<view class="tpl-bar w55"></view>
|
||
</view>
|
||
<view v-if="selectedTpl === tpl.id" class="cs-check">
|
||
<view class="cs-check-mark"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 选择背景 -->
|
||
<view class="cs-section">
|
||
<text class="cs-title">选择背景</text>
|
||
<scroll-view scroll-x class="cs-scroll">
|
||
<view class="cs-thumbs">
|
||
<view v-for="bg in backgrounds" :key="bg.id" class="cs-thumb"
|
||
:style="bgStyle(bg)"
|
||
@tap="selectBg(bg.id)">
|
||
<image v-if="bg.texture" class="cs-thumb-texture" :src="paperTexture" />
|
||
<view v-if="selectedBg === bg.id" class="cs-check">
|
||
<view class="cs-check-mark"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</block>
|
||
|
||
<!-- ============ 分享封面 ============ -->
|
||
<block v-else>
|
||
<view class="cs-cover-preview">
|
||
<view class="cs-cover-inner">
|
||
<text class="cs-cover-name">{{ card.name }}</text>
|
||
<text class="cs-cover-job">{{ card.job_title }}</text>
|
||
<text class="cs-cover-company">{{ card.company }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="cs-section">
|
||
<text class="cs-title">选择封面</text>
|
||
<scroll-view scroll-x class="cs-scroll">
|
||
<view class="cs-thumbs">
|
||
<view v-for="cover in covers" :key="cover.id" class="cs-thumb"
|
||
:style="'background-color:' + cover.color" @tap="selectCover(cover.id)">
|
||
<view v-if="selectedCover === cover.id" class="cs-check">
|
||
<view class="cs-check-mark"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</block>
|
||
|
||
<!-- 底部保存 -->
|
||
<view class="cs-footer">
|
||
<view class="cs-save" @tap="onSave">保存</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 名片样式: 本页只改样式(模板/背景), 名片资料一律取 card/detail 的真实数据回填, 不做任何改动。
|
||
// 「保存」待后端提供样式保存端点后再接入(card/style/save 之类)。
|
||
export default {
|
||
data() {
|
||
return {
|
||
seg: 'tpl',
|
||
cardId: '',
|
||
// 特种纸肌理图(可平铺棉絮纸纹理, scripts/gen-paper-texture.js 生成)
|
||
paperTexture: '/static/images/paper-fiber.png',
|
||
// 名片资料(真实数据, 由 loadCard 回填)
|
||
card: {
|
||
name: '',
|
||
job_title: '',
|
||
company: '',
|
||
mobile: '',
|
||
email: '',
|
||
address: ''
|
||
},
|
||
// 模板缩略图示意: type 决定缩略图配色(classic = 参考图的纸感米色块)
|
||
templates: [
|
||
{ id: 't1', type: 'classic' },
|
||
{ id: 't2', type: 'photo' },
|
||
{ id: 't3', type: 'lines' },
|
||
{ id: 't4', type: 'dark' },
|
||
{ id: 't5', type: 'navy' }
|
||
],
|
||
// 默认选中第一个(参考图)
|
||
selectedTpl: 't1',
|
||
backgrounds: [
|
||
// 第一个背景 = 参考图样式: 暖调奶油本白的特种纸(棉絮纸/肌理纸),
|
||
// texture: true 时叠加云絮状纤维斑纹(无规则网点), 选中后改变卡面底色
|
||
{ id: 'b1', color: '#F3EFE7', texture: true },
|
||
{ id: 'b2', color: '#c9ced6' },
|
||
{ id: 'b3', color: '#243b5e' },
|
||
{ id: 'b4', color: '#8b95a3' },
|
||
{ id: 'b5', color: '#ffffff', border: '#2aa5ff' }
|
||
],
|
||
selectedBg: 'b1',
|
||
covers: [
|
||
{ id: 'c1', color: '#5a6473' },
|
||
{ id: 'c2', color: '#c9ced6' },
|
||
{ id: 'c3', color: '#243b5e' },
|
||
{ id: 'c4', color: '#8b95a3' },
|
||
{ id: 'c5', color: '#ffffff' }
|
||
],
|
||
selectedCover: 'c1'
|
||
};
|
||
},
|
||
onLoad(options) {
|
||
this.cardId = decodeURIComponent(options.card_id || '');
|
||
this.loadCard();
|
||
},
|
||
computed: {
|
||
// 卡面底色跟随「选择背景」; 姓名/资料内容始终原样, 不随模板改变
|
||
previewStyle() {
|
||
const bg = this.backgrounds.find(item => item.id === this.selectedBg);
|
||
return bg ? this.bgStyle(bg) : '';
|
||
},
|
||
// 当前选中模板的类型(classic/photo/lines/dark/navy), 决定预览的版式类 pv-*
|
||
previewTplType() {
|
||
const tpl = this.templates.find(item => item.id === this.selectedTpl);
|
||
return tpl ? tpl.type : 'classic';
|
||
},
|
||
// 选中背景是否带特种纸肌理(叠加 paper-fiber 纹理图)
|
||
previewBgTexture() {
|
||
const bg = this.backgrounds.find(item => item.id === this.selectedBg);
|
||
return !!(bg && bg.texture);
|
||
}
|
||
},
|
||
methods: {
|
||
// 拉取名片真实资料回填预览(只读展示, 本页不改资料)
|
||
loadCard() {
|
||
if (!this.cardId) return;
|
||
this.$cardApi.cardDetail({
|
||
data: { card_id: this.cardId },
|
||
success: res => {
|
||
if (res.code == 0 && res.data && res.data.relation) {
|
||
this.card = this.pickCard(res.data.relation);
|
||
}
|
||
},
|
||
fail: () => {
|
||
uni.showToast({ title: '名片资料加载失败', icon: 'none' });
|
||
}
|
||
});
|
||
},
|
||
// relation → 预览字段(口径与详情页 fieldRows 一致)
|
||
pickCard(r) {
|
||
return {
|
||
name: r.name || '',
|
||
job_title: r.job_title || '',
|
||
company: r.company || '',
|
||
mobile: r.mobile || r.phone || '',
|
||
email: r.email || '',
|
||
address: [r.country, r.province, r.city, r.address].filter(Boolean).join(' ')
|
||
};
|
||
},
|
||
switchSeg(value) {
|
||
if (this.seg === value) return;
|
||
this.seg = value;
|
||
},
|
||
selectTpl(id) {
|
||
this.selectedTpl = id;
|
||
},
|
||
selectBg(id) {
|
||
this.selectedBg = id;
|
||
},
|
||
/**
|
||
* 背景色块/预览卡共用的内联样式(只管底色与描边)。
|
||
* texture: true 的肌理由 paper-fiber 纹理图以 <image> 层叠加(见模板),
|
||
* 不再用 CSS 渐变模拟 —— 真实纤维纹理才能和参考图一致。
|
||
*/
|
||
bgStyle(bg) {
|
||
if (!bg) return '';
|
||
let style = 'background-color:' + bg.color + ';';
|
||
if (bg.border) {
|
||
style += 'border:2rpx solid ' + bg.border + ';';
|
||
}
|
||
return style;
|
||
},
|
||
selectCover(id) {
|
||
this.selectedCover = id;
|
||
},
|
||
onUnlock() {
|
||
uni.showToast({ title: '解锁更多模板(接口待接入)', icon: 'none' });
|
||
},
|
||
onSave() {
|
||
// 接口待接入: 先给反馈, 保证交互可验证
|
||
uni.showToast({ title: '已保存(接口待接入)', icon: 'none' });
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.cs-page {
|
||
min-height: 100vh;
|
||
background-color: #f6f7f9;
|
||
// 底部给固定保存条留位
|
||
padding: 0 24rpx 200rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
// 段控: 灰底轨道 + 白色选中胶囊
|
||
.cs-seg {
|
||
display: flex;
|
||
margin-top: 20rpx;
|
||
padding: 6rpx;
|
||
background-color: #eceef1;
|
||
border-radius: 16rpx;
|
||
|
||
.cs-seg-item {
|
||
flex: 1;
|
||
height: 68rpx;
|
||
line-height: 68rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #8a94a6;
|
||
border-radius: 12rpx;
|
||
|
||
&.active {
|
||
background-color: #ffffff;
|
||
color: #333333;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 名片预览: 白底 + 蓝色粗描边(姓名/资料原样, 仅底色可被背景覆盖)
|
||
.cs-preview {
|
||
position: relative;
|
||
margin-top: 24rpx;
|
||
padding: 32rpx 28rpx;
|
||
background-color: #ffffff;
|
||
border: 6rpx solid #2aa5ff;
|
||
border-radius: 12rpx;
|
||
box-sizing: border-box;
|
||
|
||
// 特种纸肌理层: 铺满卡面, 压在底色上、文字下(后续兄弟节点设 relative 保证盖在纹理上)
|
||
.cs-preview-texture {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
> .cs-name,
|
||
> .cs-job,
|
||
> .cs-company,
|
||
> .cs-divider,
|
||
> .cs-row {
|
||
position: relative;
|
||
}
|
||
|
||
.cs-name {
|
||
display: block;
|
||
font-size: 44rpx;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.cs-job {
|
||
display: block;
|
||
margin-top: 12rpx;
|
||
font-size: 26rpx;
|
||
color: #555555;
|
||
}
|
||
|
||
.cs-company {
|
||
display: block;
|
||
margin-top: 6rpx;
|
||
font-size: 26rpx;
|
||
color: #555555;
|
||
}
|
||
|
||
.cs-divider {
|
||
height: 2rpx;
|
||
margin: 26rpx 0;
|
||
background-color: #eef0f3;
|
||
}
|
||
|
||
.cs-row {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
& + .cs-row {
|
||
margin-top: 14rpx;
|
||
}
|
||
}
|
||
|
||
.cs-row-icon {
|
||
width: 34rpx;
|
||
flex-shrink: 0;
|
||
margin-right: 12rpx;
|
||
font-size: 26rpx;
|
||
line-height: 1.2;
|
||
color: #4a5568;
|
||
text-align: center;
|
||
}
|
||
|
||
.cs-row-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 26rpx;
|
||
color: #555555;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
// ===== 第一个模板「纸感版」(参考图) =====
|
||
// 只改版式与配色: 资料内容(姓名/职位/公司/手机号...)一律原样
|
||
// 参考图配色: 分割线及以上 = 古铜金; 分割线以下 = 暖灰褐
|
||
&.pv-classic {
|
||
// 参考图是无描边的纸卡, 只用柔和投影; 保留边框宽度避免尺寸跳动
|
||
border-color: transparent;
|
||
box-shadow: 0 8rpx 24rpx rgba(122, 104, 78, 0.16);
|
||
|
||
// 版式: 资料全部居中
|
||
.cs-name,
|
||
.cs-job,
|
||
.cs-company,
|
||
.cs-row-text {
|
||
text-align: center;
|
||
}
|
||
|
||
// 分割线以上: 姓名最大最粗, 职位/公司同色次之
|
||
.cs-name {
|
||
font-size: 42rpx;
|
||
font-weight: 700;
|
||
color: #b5813c;
|
||
}
|
||
|
||
.cs-job {
|
||
margin-top: 14rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #b5813c;
|
||
}
|
||
|
||
.cs-company {
|
||
margin-top: 4rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #b5813c;
|
||
}
|
||
|
||
// 分割线: 参考图是一条很淡的浅茶色细线
|
||
.cs-divider {
|
||
margin: 5rpx 0;
|
||
background-color: rgba(181, 129, 60, 0.3);
|
||
}
|
||
|
||
// 分割线以下: 居中纯文字(参考图无行首图标), 暖灰褐色, 间距紧凑
|
||
.cs-row {
|
||
justify-content: center;
|
||
|
||
& + .cs-row {
|
||
margin-top: -10rpx;
|
||
}
|
||
|
||
.cs-row-icon {
|
||
display: none;
|
||
}
|
||
|
||
.cs-row-text {
|
||
flex: none;
|
||
font-size: 30rpx;
|
||
color: #000000;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 解锁更多新模板横幅
|
||
.cs-unlock {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-top: 24rpx;
|
||
padding: 24rpx;
|
||
background-color: #eaf4ff;
|
||
border-radius: 12rpx;
|
||
|
||
.cs-unlock-icon {
|
||
flex-shrink: 0;
|
||
margin-right: 14rpx;
|
||
font-size: 32rpx;
|
||
color: #2aa5ff;
|
||
}
|
||
|
||
.cs-unlock-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 24rpx;
|
||
color: #333333;
|
||
}
|
||
|
||
.cs-unlock-arrow {
|
||
flex-shrink: 0;
|
||
margin-left: 10rpx;
|
||
font-size: 26rpx;
|
||
color: #9aa4b0;
|
||
}
|
||
}
|
||
|
||
// 分区标题 + 横向滚动缩略图
|
||
.cs-section {
|
||
margin-top: 32rpx;
|
||
|
||
.cs-title {
|
||
display: block;
|
||
margin-bottom: 20rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
}
|
||
}
|
||
|
||
.cs-scroll {
|
||
white-space: nowrap;
|
||
|
||
.cs-thumbs {
|
||
display: inline-flex;
|
||
}
|
||
}
|
||
|
||
.cs-thumb {
|
||
position: relative;
|
||
width: 152rpx;
|
||
height: 100rpx;
|
||
flex-shrink: 0;
|
||
margin-right: 20rpx;
|
||
border-radius: 10rpx;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
background-color: #ffffff;
|
||
border: 2rpx solid #e8ebee;
|
||
|
||
// 特种纸肌理层: 铺满色块, 选中角标(.cs-check)在 DOM 之后仍盖在其上
|
||
.cs-thumb-texture {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
// 模板内部线条示意
|
||
.tpl-inner {
|
||
padding: 18rpx 16rpx;
|
||
|
||
.tpl-bar {
|
||
height: 8rpx;
|
||
border-radius: 4rpx;
|
||
background-color: #dfe3e8;
|
||
|
||
& + .tpl-bar {
|
||
margin-top: 10rpx;
|
||
}
|
||
}
|
||
|
||
.w70 {
|
||
width: 70%;
|
||
}
|
||
|
||
.w45 {
|
||
width: 45%;
|
||
}
|
||
|
||
.w55 {
|
||
width: 55%;
|
||
}
|
||
}
|
||
|
||
// 深色模板示意
|
||
&.tpl-dark,
|
||
&.tpl-navy {
|
||
border-color: transparent;
|
||
|
||
.tpl-bar {
|
||
background-color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
}
|
||
|
||
&.tpl-dark {
|
||
background-color: #4a5260;
|
||
}
|
||
|
||
&.tpl-navy {
|
||
background-color: #1f2a44;
|
||
}
|
||
|
||
// 模板一(参考图): 纸感米色纯色块
|
||
&.tpl-classic {
|
||
border-color: transparent;
|
||
background-color: #ece5db;
|
||
|
||
.tpl-inner {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
// 带人像的模板示意
|
||
&.tpl-photo {
|
||
.tpl-inner {
|
||
padding: 0;
|
||
|
||
.tpl-bar {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 56rpx;
|
||
height: 100%;
|
||
background-color: #6b7280;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 选中角标(参考图): 白底蓝勾圆, 居中压在缩略图上
|
||
.cs-check {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 34rpx;
|
||
height: 34rpx;
|
||
border-radius: 50%;
|
||
background-color: #ffffff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.cs-check-mark {
|
||
width: 14rpx;
|
||
height: 7rpx;
|
||
margin-top: -4rpx;
|
||
border-left: 3rpx solid #2aa5ff;
|
||
border-bottom: 3rpx solid #2aa5ff;
|
||
transform: rotate(-45deg);
|
||
}
|
||
}
|
||
|
||
// 分享封面预览
|
||
.cs-cover-preview {
|
||
margin-top: 24rpx;
|
||
padding: 32rpx;
|
||
height: 320rpx;
|
||
background-color: #ffffff;
|
||
border: 6rpx solid #2aa5ff;
|
||
border-radius: 12rpx;
|
||
box-sizing: border-box;
|
||
|
||
.cs-cover-inner {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
height: 100%;
|
||
}
|
||
|
||
.cs-cover-name {
|
||
font-size: 40rpx;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
}
|
||
|
||
.cs-cover-job {
|
||
margin-top: 10rpx;
|
||
font-size: 26rpx;
|
||
color: #555555;
|
||
}
|
||
|
||
.cs-cover-company {
|
||
margin-top: 6rpx;
|
||
font-size: 26rpx;
|
||
color: #555555;
|
||
}
|
||
}
|
||
|
||
// 底部固定保存条
|
||
.cs-footer {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
padding: 20rpx 24rpx;
|
||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||
background-color: #ffffff;
|
||
|
||
.cs-save {
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
text-align: center;
|
||
border-radius: 44rpx;
|
||
background-color: #2aa5ff;
|
||
color: #ffffff;
|
||
font-size: 32rpx;
|
||
}
|
||
}
|
||
</style>
|