chore:名片样式的名片模板第一个模板
This commit is contained in:
450
pages_tool/card/admin/contacts.vue
Normal file
450
pages_tool/card/admin/contacts.vue
Normal file
@@ -0,0 +1,450 @@
|
||||
<template>
|
||||
<view class="admin-contacts">
|
||||
<!-- 商家端三大入口平级切换(鸿蒙安全: $util.redirectTo) -->
|
||||
<view class="page-switch">
|
||||
<view class="switch-item" @tap="goManage">名片管理</view>
|
||||
<view class="switch-item active" @tap="goSelf">人脉管理</view>
|
||||
<view class="switch-item" @tap="goLeads">潜在客户</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 + 添加入口 -->
|
||||
<view class="search-bar">
|
||||
<input class="search-input" :value="keyword" placeholder="搜索姓名 / 手机 / 公司"
|
||||
confirm-type="search" @input="e => keyword = e.detail.value" @confirm="doSearch" />
|
||||
<view class="search-btn" @tap="doSearch">搜索</view>
|
||||
<view class="add-btn" @tap="goEdit()">+ 添加</view>
|
||||
</view>
|
||||
|
||||
<!-- 来源筛选(与后端 Contact::SOURCES 对齐) -->
|
||||
<scroll-view scroll-x class="tab-bar">
|
||||
<view class="tab-inner">
|
||||
<view v-for="tab in tabs" :key="tab.value" class="tab-item"
|
||||
:class="{ active: source === tab.value }" @tap="switchTab(tab.value)">
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 人脉列表 -->
|
||||
<view class="contact-list">
|
||||
<view v-for="item in list" :key="item.id" class="contact-item">
|
||||
<view class="item-top" @tap="callPhone(item.mobile)">
|
||||
<view class="item-name">{{ item.name }}</view>
|
||||
<view class="item-source" :class="'src-' + item.source">{{ item.source_text }}</view>
|
||||
</view>
|
||||
<view class="item-company" v-if="item.company || item.job_title">
|
||||
{{ item.company }}<text v-if="item.company && item.job_title"> · </text>{{ item.job_title }}
|
||||
</view>
|
||||
<view class="item-mobile" v-if="item.mobile" @tap="callPhone(item.mobile)">{{ item.mobile }}</view>
|
||||
<view class="item-remark" v-if="item.remark">「{{ item.remark }}」</view>
|
||||
<view class="item-meta">
|
||||
<text class="meta-time">{{ item.create_time_txt }}</text>
|
||||
<text class="meta-status" v-if="item.status == 1">已归档</text>
|
||||
</view>
|
||||
<view class="item-actions">
|
||||
<view class="action-btn plain" @tap="goEdit(item.id)">编辑</view>
|
||||
<view class="action-btn primary" @tap="convertToLead(item)">转为潜在客户</view>
|
||||
<view class="action-btn danger" @tap="delContact(item)">删除</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="load-more" v-if="list.length > 0">
|
||||
<text v-if="isLoading">加载中…</text>
|
||||
<text v-else-if="page < pageCount">上拉加载更多</text>
|
||||
<text v-else class="load-end">— 没有更多了 —</text>
|
||||
</view>
|
||||
|
||||
<ns-empty v-if="!isLoading && list.length === 0" :isIndex="false"
|
||||
:text="'暂无人脉,点右上角添加'"></ns-empty>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 商家端 · 人脉管理
|
||||
*
|
||||
* 接口全部来自后端已就绪的 /card/api/contact/*(收口在 common/js/card-api.js):
|
||||
* contact/lists 列表 / contact/add+update 增改(复用 contact-edit 页) /
|
||||
* contact/toLead 转线索 / contact/delete 真删。
|
||||
* 与员工端「我的人脉」的区别: 本页是商家后台视角, 带来源筛选与三大入口互链;
|
||||
* 编辑页共用 contact-edit.vue(from=admin 时保存后回本页)。
|
||||
*/
|
||||
import { CONTACT_SOURCES } from '@/common/js/card-api.js';
|
||||
|
||||
// 来源筛选: scan/exchange 暂无产生入口, 只放出 manual / lead_in 两个 tab
|
||||
const SOURCE_TABS = [{ value: '', label: '全部' }].concat(
|
||||
CONTACT_SOURCES.filter(s => s.value === 'manual' || s.value === 'lead_in')
|
||||
);
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tabs: SOURCE_TABS,
|
||||
source: '',
|
||||
keyword: '',
|
||||
list: [],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
pageCount: 1,
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.refresh();
|
||||
},
|
||||
onShow() {
|
||||
// 从编辑页保存返回(redirectTo 回本页)时刷新
|
||||
if (this._loadedOnce) this.refresh();
|
||||
this._loadedOnce = true;
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.refresh(() => uni.stopPullDownRefresh());
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.isLoading) return;
|
||||
if (this.page >= this.pageCount) return;
|
||||
this.loadPage(this.page + 1);
|
||||
},
|
||||
methods: {
|
||||
// 平级入口: 鸿蒙禁裸 uni.switchTab, 走 $util.redirectTo
|
||||
goManage() {
|
||||
this.$util.redirectTo('/pages_tool/card/admin/manage');
|
||||
},
|
||||
goLeads() {
|
||||
this.$util.redirectTo('/pages_tool/card/admin/leads');
|
||||
},
|
||||
goSelf() {},
|
||||
// 编辑页共用 contact-edit; from=admin 时保存后回本页(见 contact-edit.vue#backToList)
|
||||
goEdit(id) {
|
||||
const url = id
|
||||
? '/pages_tool/card/contact-edit?from=admin&id=' + id
|
||||
: '/pages_tool/card/contact-edit?from=admin';
|
||||
this.$util.redirectTo(url);
|
||||
},
|
||||
switchTab(value) {
|
||||
if (this.source === value) return;
|
||||
this.source = value;
|
||||
this.refresh();
|
||||
},
|
||||
doSearch() {
|
||||
this.refresh();
|
||||
},
|
||||
refresh(done) {
|
||||
this.page = 1;
|
||||
this.loadPage(1, done);
|
||||
},
|
||||
loadPage(pageNo, done) {
|
||||
this.isLoading = true;
|
||||
const data = { page: pageNo, page_size: this.pageSize };
|
||||
// 空字符串 = 全部, 后端对 '' 不加 source 条件
|
||||
if (this.source !== '') data.source = this.source;
|
||||
if (this.keyword.trim() !== '') data.keyword = this.keyword.trim();
|
||||
|
||||
this.$cardApi.contactLists({
|
||||
data: data,
|
||||
success: res => {
|
||||
this.isLoading = false;
|
||||
if (res.code == 0) {
|
||||
const d = res.data || {};
|
||||
const rows = d.list || [];
|
||||
this.pageCount = d.page_count || 1;
|
||||
this.page = pageNo;
|
||||
this.list = pageNo === 1 ? rows : this.list.concat(rows);
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '加载失败', icon: 'none' });
|
||||
}
|
||||
typeof done === 'function' && done();
|
||||
},
|
||||
fail: () => {
|
||||
this.isLoading = false;
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
typeof done === 'function' && done();
|
||||
}
|
||||
});
|
||||
},
|
||||
convertToLead(item) {
|
||||
uni.showModal({
|
||||
title: '转为潜在客户',
|
||||
content: '将「' + (item.name || '') + '」转入潜在客户跟进?人脉记录会保留。',
|
||||
success: res => {
|
||||
if (!res.confirm) return;
|
||||
this.doConvert(item);
|
||||
}
|
||||
});
|
||||
},
|
||||
doConvert(item) {
|
||||
uni.showLoading({ title: '转换中…', mask: true });
|
||||
// 后端: 写一条线索且不删人脉; 同员工同手机号已转过则报错(message 可直接展示)
|
||||
this.$cardApi.contactToLead({
|
||||
data: { id: item.id },
|
||||
success: res => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 0) {
|
||||
uni.showToast({ title: '已转入潜在客户', icon: 'success' });
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '转换失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
}
|
||||
});
|
||||
},
|
||||
delContact(item) {
|
||||
// 真删不可恢复, 二次确认
|
||||
uni.showModal({
|
||||
title: '删除人脉',
|
||||
content: '删除后不可恢复,确定删除「' + (item.name || '') + '」?',
|
||||
confirmColor: '#f5222d',
|
||||
success: res => {
|
||||
if (!res.confirm) return;
|
||||
this.doDelete(item);
|
||||
}
|
||||
});
|
||||
},
|
||||
doDelete(item) {
|
||||
this.$cardApi.contactDelete({
|
||||
data: { id: item.id },
|
||||
success: res => {
|
||||
if (res.code == 0) {
|
||||
this.list = this.list.filter(c => c.id !== item.id);
|
||||
uni.showToast({ title: '已删除', icon: 'success' });
|
||||
} else {
|
||||
// 后端对"不存在"与"非本人"同一口径, 按"不存在或已删除"提示
|
||||
uni.showToast({ title: res.message || '删除失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
}
|
||||
});
|
||||
},
|
||||
callPhone(mobile) {
|
||||
if (!mobile) return;
|
||||
uni.makePhoneCall({ phoneNumber: mobile });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.admin-contacts {
|
||||
min-height: 100vh;
|
||||
background-color: #f6f7f9;
|
||||
padding-bottom: 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page-switch {
|
||||
display: flex;
|
||||
background-color: #ffffff;
|
||||
padding: 0 12rpx;
|
||||
|
||||
.switch-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: var(--main-color, #0054a5);
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 8rpx;
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 3rpx;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
padding: 0 28rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
border-radius: 32rpx;
|
||||
padding: 12rpx 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
background-color: #ffffff;
|
||||
white-space: nowrap;
|
||||
|
||||
.tab-inner {
|
||||
display: inline-flex;
|
||||
padding: 0 12rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
padding: 24rpx 28rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: var(--main-color, #0054a5);
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 3rpx;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact-list {
|
||||
padding: 20rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.contact-item {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.item-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.item-name {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-source {
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 18rpx;
|
||||
border-radius: 22rpx;
|
||||
|
||||
&.src-manual {
|
||||
color: var(--main-color, #0054a5);
|
||||
background-color: var(--main-color-shallow, #e8f1fb);
|
||||
}
|
||||
|
||||
&.src-lead_in {
|
||||
color: #1daa39;
|
||||
background-color: #e8f8ea;
|
||||
}
|
||||
|
||||
&.src-scan,
|
||||
&.src-exchange {
|
||||
color: #999;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-company {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.item-mobile {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.item-remark {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-top: 12rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.action-btn {
|
||||
font-size: 24rpx;
|
||||
border-radius: 30rpx;
|
||||
padding: 10rpx 26rpx;
|
||||
margin-left: 16rpx;
|
||||
|
||||
&.plain {
|
||||
color: #666;
|
||||
border: 1rpx solid #ddd;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: #ffffff;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #f5222d;
|
||||
border: 1rpx solid #f5222d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
padding: 24rpx 0;
|
||||
|
||||
.load-end {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
398
pages_tool/card/admin/leads.vue
Normal file
398
pages_tool/card/admin/leads.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<view class="admin-leads">
|
||||
<!-- 商家端三大入口平级切换(鸿蒙安全: $util.redirectTo) -->
|
||||
<view class="page-switch">
|
||||
<view class="switch-item" @tap="goManage">名片管理</view>
|
||||
<view class="switch-item" @tap="goContacts">人脉管理</view>
|
||||
<view class="switch-item active" @tap="goSelf">潜在客户</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input class="search-input" :value="keyword" placeholder="搜索姓名 / 手机"
|
||||
confirm-type="search" @input="e => keyword = e.detail.value" @confirm="doSearch" />
|
||||
<view class="search-btn" @tap="doSearch">搜索</view>
|
||||
</view>
|
||||
|
||||
<!-- 跟进状态筛选(与后端 Lead::STATUSES 对齐, setStatus 仅接受 0-3) -->
|
||||
<scroll-view scroll-x class="tab-bar">
|
||||
<view class="tab-inner">
|
||||
<view v-for="tab in tabs" :key="tab.value" class="tab-item"
|
||||
:class="{ active: status === tab.value }" @tap="switchTab(tab.value)">
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 线索列表 -->
|
||||
<view class="lead-list">
|
||||
<view v-for="(item, index) in list" :key="item.id || index" class="lead-item">
|
||||
<view class="lead-top" @tap="callPhone(item.mobile)">
|
||||
<view class="lead-name">{{ item.name || '未留姓名' }}</view>
|
||||
<view class="lead-status" :class="'st-' + item.status">{{ item.status_text || '新客户' }}</view>
|
||||
</view>
|
||||
<view class="lead-mobile" v-if="item.mobile" @tap="callPhone(item.mobile)">📱 {{ item.mobile }}</view>
|
||||
<view class="lead-note" v-if="item.note">「{{ item.note }}」</view>
|
||||
<view class="lead-meta">
|
||||
<text class="meta-card">来自名片:{{ item.card_name }}</text>
|
||||
<text class="meta-time">{{ item.create_time_txt }}</text>
|
||||
</view>
|
||||
<view class="lead-actions">
|
||||
<view class="action-btn copy" v-if="item.wechat" @tap="copyText(item.wechat)">复制微信</view>
|
||||
<view class="action-btn primary" @tap="changeStatus(item, index)">更新状态</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="load-more" v-if="list.length > 0">
|
||||
<text v-if="isLoading">加载中…</text>
|
||||
<text v-else-if="page < pageCount">上拉加载更多</text>
|
||||
<text v-else class="load-end">— 没有更多了 —</text>
|
||||
</view>
|
||||
|
||||
<ns-empty v-if="!isLoading && list.length === 0" :isIndex="false"
|
||||
:text="emptyText"></ns-empty>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 商家端 · 潜在客户
|
||||
*
|
||||
* 接口全部来自后端已就绪的 /card/api/lead/*(收口在 common/js/card-api.js):
|
||||
* lead/lists 列表(可见范围=自己名下名片收到的线索 + 自己从人脉转入的线索;
|
||||
* 没有名片时列表为空属正常现象) / lead/setStatus 更新跟进状态(0-3)。
|
||||
*/
|
||||
import { LEAD_STATUS } from '@/common/js/card-api.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tabs: [{ value: '', label: '全部' }].concat(LEAD_STATUS),
|
||||
status: '',
|
||||
keyword: '',
|
||||
list: [],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
pageCount: 1,
|
||||
isLoading: false,
|
||||
emptyText: '暂无意向客户,访客在你的名片页留资后会出现在这里'
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.refresh();
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.refresh(() => uni.stopPullDownRefresh());
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.isLoading) return;
|
||||
if (this.page >= this.pageCount) return;
|
||||
this.loadPage(this.page + 1);
|
||||
},
|
||||
methods: {
|
||||
// 平级入口: 鸿蒙禁裸 uni.switchTab, 走 $util.redirectTo
|
||||
goManage() {
|
||||
this.$util.redirectTo('/pages_tool/card/admin/manage');
|
||||
},
|
||||
goContacts() {
|
||||
this.$util.redirectTo('/pages_tool/card/admin/contacts');
|
||||
},
|
||||
goSelf() {},
|
||||
switchTab(value) {
|
||||
if (this.status === value) return;
|
||||
this.status = value;
|
||||
this.refresh();
|
||||
},
|
||||
doSearch() {
|
||||
this.refresh();
|
||||
},
|
||||
refresh(done) {
|
||||
this.page = 1;
|
||||
this.loadPage(1, done);
|
||||
},
|
||||
loadPage(pageNo, done) {
|
||||
this.isLoading = true;
|
||||
const data = { page: pageNo, page_size: this.pageSize };
|
||||
// 空字符串 = 全部, 后端对 '' 不加 status 条件; status 只传 0-3
|
||||
if (this.status !== '') data.status = this.status;
|
||||
if (this.keyword.trim() !== '') data.keyword = this.keyword.trim();
|
||||
|
||||
this.$cardApi.leadLists({
|
||||
data: data,
|
||||
success: res => {
|
||||
this.isLoading = false;
|
||||
if (res.code == 0) {
|
||||
const d = res.data || {};
|
||||
const rows = d.list || [];
|
||||
this.pageCount = d.page_count || 1;
|
||||
this.page = pageNo;
|
||||
this.list = pageNo === 1 ? rows : this.list.concat(rows);
|
||||
this.emptyText = this.keyword.trim() || this.status !== ''
|
||||
? '未找到匹配的客户'
|
||||
: '暂无意向客户,访客在你的名片页留资后会出现在这里';
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '加载失败', icon: 'none' });
|
||||
}
|
||||
typeof done === 'function' && done();
|
||||
},
|
||||
fail: () => {
|
||||
this.isLoading = false;
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
typeof done === 'function' && done();
|
||||
}
|
||||
});
|
||||
},
|
||||
changeStatus(item, index) {
|
||||
uni.showActionSheet({
|
||||
itemList: LEAD_STATUS.map(s => s.label),
|
||||
success: res => {
|
||||
const target = LEAD_STATUS[res.tapIndex];
|
||||
if (!target || target.value === item.status) return;
|
||||
this.doSetStatus(item, index, target.value);
|
||||
}
|
||||
});
|
||||
},
|
||||
doSetStatus(item, index, status) {
|
||||
this.$cardApi.leadSetStatus({
|
||||
data: { id: item.id, status: status },
|
||||
success: res => {
|
||||
if (res.code == 0) {
|
||||
// 就地更新, 不整页刷新
|
||||
this.list[index].status = status;
|
||||
this.list[index].status_text = LEAD_STATUS.find(s => s.value === status).label;
|
||||
uni.showToast({ title: '已更新', icon: 'success' });
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '更新失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
}
|
||||
});
|
||||
},
|
||||
callPhone(mobile) {
|
||||
if (!mobile) return;
|
||||
uni.makePhoneCall({ phoneNumber: mobile });
|
||||
},
|
||||
copyText(text) {
|
||||
uni.setClipboardData({
|
||||
data: text,
|
||||
success: () => uni.showToast({ title: '已复制', icon: 'success' })
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.admin-leads {
|
||||
min-height: 100vh;
|
||||
background-color: #f6f7f9;
|
||||
padding-bottom: 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page-switch {
|
||||
display: flex;
|
||||
background-color: #ffffff;
|
||||
padding: 0 12rpx;
|
||||
|
||||
.switch-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: var(--main-color, #0054a5);
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 8rpx;
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 3rpx;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
padding: 0 28rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
background-color: #ffffff;
|
||||
white-space: nowrap;
|
||||
|
||||
.tab-inner {
|
||||
display: inline-flex;
|
||||
padding: 0 12rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
padding: 24rpx 28rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: var(--main-color, #0054a5);
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 3rpx;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lead-list {
|
||||
padding: 20rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.lead-item {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.lead-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.lead-name {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.lead-status {
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 18rpx;
|
||||
border-radius: 22rpx;
|
||||
|
||||
&.st-0 {
|
||||
color: var(--main-color, #0054a5);
|
||||
background-color: var(--main-color-shallow, #e8f1fb);
|
||||
}
|
||||
|
||||
&.st-1 {
|
||||
color: #e6a23c;
|
||||
background-color: #fdf3e6;
|
||||
}
|
||||
|
||||
&.st-2 {
|
||||
color: #1daa39;
|
||||
background-color: #e8f8ea;
|
||||
}
|
||||
|
||||
&.st-3 {
|
||||
color: #999;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lead-mobile {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.lead-note {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-top: 12rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.lead-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 14rpx;
|
||||
|
||||
.meta-card {
|
||||
max-width: 60%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.lead-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.action-btn {
|
||||
font-size: 24rpx;
|
||||
border-radius: 30rpx;
|
||||
padding: 10rpx 30rpx;
|
||||
margin-left: 20rpx;
|
||||
|
||||
&.copy {
|
||||
color: #666;
|
||||
border: 1rpx solid #ddd;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: #ffffff;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
padding: 24rpx 0;
|
||||
|
||||
.load-end {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
409
pages_tool/card/admin/manage.vue
Normal file
409
pages_tool/card/admin/manage.vue
Normal file
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<view class="admin-manage">
|
||||
<!-- 商家端三大入口平级切换(鸿蒙安全: $util.redirectTo, 禁裸 uni.switchTab) -->
|
||||
<view class="page-switch">
|
||||
<view class="switch-item active" @tap="goSelf">名片管理</view>
|
||||
<view class="switch-item" @tap="goContacts">人脉管理</view>
|
||||
<view class="switch-item" @tap="goLeads">潜在客户</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能预告: 查重合并/批量制作/导出等依赖后台接口, 就绪后开放 -->
|
||||
<view class="notice-bar">
|
||||
<text class="notice-icon">i</text>
|
||||
<text class="notice-text">查重合并、批量制作、导出 CSV/vCard 等功能即将上线</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索区 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrap">
|
||||
<input class="search-input" confirm-type="search" :value="keyword"
|
||||
placeholder="搜索姓名 / 手机 / 公司" @confirm="onSearch" @input="onKeywordInput" />
|
||||
<text v-if="keyword" class="search-clear" @tap="clearKeyword">✕</text>
|
||||
<text class="iconfont icon-sousuo3" @tap="onSearch"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 名片列表 -->
|
||||
<view class="card-list">
|
||||
<view v-for="(item, index) in list" :key="item.card_id || index" class="card-item"
|
||||
@tap="goDetail(item)">
|
||||
<view class="card-avatar">
|
||||
<image v-if="item.avatar" :src="$util.img(item.avatar)" class="avatar-img" mode="aspectFill" />
|
||||
<text v-else>{{ avatarText(item.name) }}</text>
|
||||
</view>
|
||||
<view class="card-info">
|
||||
<view class="card-name line1">{{ item.name || '未命名名片' }}</view>
|
||||
<view class="card-sub line1" v-if="item.job_title || item.company">
|
||||
{{ [item.job_title, item.company].filter(Boolean).join(' · ') }}
|
||||
</view>
|
||||
<view class="card-mobile" v-if="item.mobile">📱 {{ item.mobile }}</view>
|
||||
</view>
|
||||
<view class="card-side">
|
||||
<text v-if="sourceLabel(item)" class="source-tag">{{ sourceLabel(item) }}</text>
|
||||
<view class="card-actions" @tap.stop="">
|
||||
<text class="act-btn" @tap="goEdit(item)">编辑</text>
|
||||
<text class="act-btn primary" @tap="goDetail(item)">详情</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="load-more" v-if="list.length > 0">
|
||||
<text v-if="isLoading">加载中…</text>
|
||||
<text v-else-if="page < pageCount">上拉加载更多</text>
|
||||
<text v-else class="load-end">— 没有更多了 —</text>
|
||||
</view>
|
||||
|
||||
<!-- 空态 -->
|
||||
<ns-empty v-if="!isLoading && list.length === 0" :isIndex="false" :text="emptyText"></ns-empty>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 商家端 · 名片管理(基础版)
|
||||
*
|
||||
* 范围: 本轮仅开放现有接口可支撑的能力——搜索 / 详情 / 编辑。
|
||||
* 列表/详情/编辑复用 card/lists + 既有 /pages_tool/card/detail、edit 页
|
||||
* (路由参数 card_id, 字符串主键)。
|
||||
* 查重合并 / 批量制作 / 新建数字名片 / 导出 CSV/vCard / 标签·状态·类型·跟进·收藏
|
||||
* 筛选依赖后台未就绪接口, 后台文档(CARD_API_REFERENCE.md)到位后在本页扩展,
|
||||
* 端点届时收口进 common/js/card-api.js, 页面只改 this.$cardApi 调用。
|
||||
*/
|
||||
import { CARD_SOURCE_TYPES } from '@/common/js/card-api.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
keyword: '',
|
||||
list: [],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
pageCount: 1,
|
||||
isLoading: false,
|
||||
emptyText: '暂无名片刻'
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.refresh();
|
||||
},
|
||||
onShow() {
|
||||
// 从编辑页返回时刷新(编辑成功后数据已变)
|
||||
if (this._loadedOnce) this.refresh();
|
||||
this._loadedOnce = true;
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.refresh(() => uni.stopPullDownRefresh());
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.isLoading) return;
|
||||
if (this.page >= this.pageCount) return;
|
||||
this.loadPage(this.page + 1);
|
||||
},
|
||||
methods: {
|
||||
avatarText(name) {
|
||||
return (name || '?').trim().charAt(0).toUpperCase();
|
||||
},
|
||||
// 来源标签: 优先后端译文(source_type_text), 缺失时按字典兜底(与后端 source_type 对齐)
|
||||
sourceLabel(item) {
|
||||
if (!item) return '';
|
||||
if (item.source_type_text) return item.source_type_text;
|
||||
const hit = CARD_SOURCE_TYPES.find(s => s.value === item.source_type);
|
||||
return hit ? hit.label : '';
|
||||
},
|
||||
onKeywordInput(e) {
|
||||
this.keyword = e.detail.value;
|
||||
},
|
||||
onSearch() {
|
||||
this.refresh();
|
||||
},
|
||||
clearKeyword() {
|
||||
this.keyword = '';
|
||||
this.refresh();
|
||||
},
|
||||
refresh(done) {
|
||||
this.page = 1;
|
||||
this.loadPage(1, done);
|
||||
},
|
||||
loadPage(pageNo, done) {
|
||||
this.isLoading = true;
|
||||
// page_size 上限 100(后端硬限制), 这里 20 足够
|
||||
this.$cardApi.cardLists({
|
||||
data: {
|
||||
page: pageNo,
|
||||
page_size: this.pageSize,
|
||||
keyword: this.keyword.trim()
|
||||
},
|
||||
success: res => {
|
||||
this.isLoading = false;
|
||||
if (res.code == 0) {
|
||||
const d = res.data || {};
|
||||
const rows = d.list || [];
|
||||
this.pageCount = d.page_count || 1;
|
||||
this.page = pageNo;
|
||||
this.list = pageNo === 1 ? rows : this.list.concat(rows);
|
||||
this.emptyText = this.keyword.trim() ? '未找到匹配的名片' : '暂无名片刻';
|
||||
} else {
|
||||
// 后端 code!=0 的 message 可直接 toast
|
||||
uni.showToast({ title: res.message || '加载失败', icon: 'none' });
|
||||
}
|
||||
typeof done === 'function' && done();
|
||||
},
|
||||
fail: () => {
|
||||
this.isLoading = false;
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
typeof done === 'function' && done();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* ---------- 导航 ---------- */
|
||||
// 名片路由参数一律 card_id(字符串), encodeURIComponent 幂等
|
||||
goDetail(item) {
|
||||
if (!item || !item.card_id) return;
|
||||
uni.navigateTo({
|
||||
url: '/pages_tool/card/detail?card_id=' + encodeURIComponent(item.card_id)
|
||||
});
|
||||
},
|
||||
goEdit(item) {
|
||||
if (!item || !item.card_id) return;
|
||||
uni.navigateTo({
|
||||
url: '/pages_tool/card/edit?card_id=' + encodeURIComponent(item.card_id)
|
||||
});
|
||||
},
|
||||
// 平级入口: 鸿蒙元服务上 uni.switchTab 静默失败, 必须走 $util.redirectTo
|
||||
goContacts() {
|
||||
this.$util.redirectTo('/pages_tool/card/admin/contacts');
|
||||
},
|
||||
goLeads() {
|
||||
this.$util.redirectTo('/pages_tool/card/admin/leads');
|
||||
},
|
||||
goSelf() {
|
||||
// 当前页, 无需跳转
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.admin-manage {
|
||||
min-height: 100vh;
|
||||
background-color: #f6f7f9;
|
||||
padding-bottom: 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 顶部平级切换(三页互链) */
|
||||
.page-switch {
|
||||
display: flex;
|
||||
background-color: #ffffff;
|
||||
padding: 0 12rpx;
|
||||
|
||||
.switch-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: var(--main-color, #0054a5);
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 8rpx;
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 3rpx;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 功能预告条 */
|
||||
.notice-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 20rpx 24rpx 0;
|
||||
padding: 16rpx 20rpx;
|
||||
background-color: var(--main-color-shallow, #e8f1fb);
|
||||
border-radius: 12rpx;
|
||||
|
||||
.notice-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
line-height: 32rpx;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background-color: var(--main-color, #0054a5);
|
||||
color: #ffffff;
|
||||
font-size: 22rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
font-size: 24rpx;
|
||||
color: var(--main-color, #0054a5);
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx 0;
|
||||
|
||||
.search-input-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 24rpx;
|
||||
height: 72rpx;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-clear {
|
||||
padding: 0 12rpx;
|
||||
color: #bbb;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
color: #999;
|
||||
font-size: 32rpx;
|
||||
padding-left: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-list {
|
||||
padding: 20rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:active {
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.card-avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
background-color: var(--main-color-shallow, #e8f1fb);
|
||||
color: var(--main-color, #0054a5);
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: 24rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.card-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-sub {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.card-mobile {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.card-side {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
|
||||
.source-tag {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
background-color: #f2f2f2;
|
||||
border-radius: 20rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
|
||||
.act-btn {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 28rpx;
|
||||
padding: 8rpx 22rpx;
|
||||
|
||||
& + .act-btn {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: var(--main-color, #0054a5);
|
||||
border-color: var(--main-color, #0054a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
padding: 24rpx 0;
|
||||
|
||||
.load-end {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.line1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
327
pages_tool/card/exchange.vue
Normal file
327
pages_tool/card/exchange.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<view class="ex-page">
|
||||
<!-- 状态筛选: 全部 / 待处理(N) / 已同意 -->
|
||||
<view class="ex-tabs">
|
||||
<view v-for="tab in tabs" :key="tab.value" class="ex-tab"
|
||||
:class="{ active: current === tab.value }" @tap="switchTab(tab.value)">
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 交换请求列表 -->
|
||||
<view class="ex-list">
|
||||
<view v-for="item in shownList" :key="item.id" class="ex-item">
|
||||
<view class="ex-top">
|
||||
<view class="ex-avatar-wrap" @tap.stop="viewCard(item)">
|
||||
<image v-if="item.avatar" class="ex-avatar" :src="item.avatar" mode="aspectFill" />
|
||||
<view v-else class="ex-avatar ex-avatar-ph">{{ item.name.charAt(0) }}</view>
|
||||
<view class="ex-avatar-tag">示例交换</view>
|
||||
</view>
|
||||
<view class="ex-info">
|
||||
<view class="ex-name-row">
|
||||
<text class="ex-name">{{ item.name }}</text>
|
||||
<text class="ex-badge iconfont icon-dui"></text>
|
||||
<text class="ex-badge iconfont icon-dui"></text>
|
||||
</view>
|
||||
<text class="ex-job">{{ item.job_title }}</text>
|
||||
<view class="ex-company-row">
|
||||
<text class="ex-tag">示例交换</text>
|
||||
<text class="ex-company">{{ item.company }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.status === 'pending'" class="ex-agree" @tap.stop="onAgree(item)">同意</view>
|
||||
<text v-else class="ex-agreed">已同意</text>
|
||||
</view>
|
||||
|
||||
<view class="ex-reason">对方发起了名片交换请求</view>
|
||||
|
||||
<view class="ex-time">{{ item.create_time_txt }} 向您发起名片交换</view>
|
||||
</view>
|
||||
|
||||
<ns-empty v-if="shownList.length === 0" :isIndex="false" :text="emptyText"></ns-empty>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 名片交换请求列表
|
||||
// ⚠️ 目前为静态占位数据: 后端 /card/api/exchange/* 接口就绪后,
|
||||
// 改为在 onLoad 里用 this.$cardApi.exchangeLists({ data: { page: 1 }, ... }) 拉取,
|
||||
// 「同意」改成 cardApi.exchangeAgree({ data: { id: item.id } })。
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
current: 'pending',
|
||||
cardId: '',
|
||||
list: [],
|
||||
page: 1,
|
||||
pageCount: 1,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.cardId = decodeURIComponent(options.card_id || '');
|
||||
this.loadList();
|
||||
},
|
||||
computed: {
|
||||
tabs() {
|
||||
const pending = this.list.filter(i => i.status === 'pending').length;
|
||||
return [
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'pending', label: '待处理(' + pending + ')' },
|
||||
{ value: 'agreed', label: '已同意' }
|
||||
];
|
||||
},
|
||||
shownList() {
|
||||
if (this.current === 'all') return this.list;
|
||||
return this.list.filter(item => item.status === this.current);
|
||||
},
|
||||
emptyText() {
|
||||
return this.current === 'agreed' ? '暂无已同意的交换' : '暂无名片交换请求';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
switchTab(value) {
|
||||
if (this.current === value) return;
|
||||
this.current = value;
|
||||
},
|
||||
// 待处理交换请求(后端只返回 status=0); 归一化 status 供模板区分
|
||||
loadList() {
|
||||
this.loading = true;
|
||||
this.$cardApi.exchangeLists({
|
||||
data: { page: this.page, page_size: 20 },
|
||||
success: res => {
|
||||
this.loading = false;
|
||||
if (res.code == 0 && res.data) {
|
||||
this.list = (res.data.list || []).map(it => ({
|
||||
id: it.id,
|
||||
from_card_id: it.from_card_id,
|
||||
name: it.name,
|
||||
avatar: it.avatar,
|
||||
job_title: it.job_title,
|
||||
company: it.company,
|
||||
create_time_txt: it.create_time_txt,
|
||||
status: it.status === 0 ? 'pending' : 'agreed'
|
||||
}));
|
||||
this.pageCount = res.data.page_count || 1;
|
||||
} else if (res.message) {
|
||||
uni.showToast({ title: res.message, icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
// 点对方名片 → 跳转对方名片详情(用 from_card_id 字符串)
|
||||
viewCard(item) {
|
||||
if (!item || !item.from_card_id) return;
|
||||
uni.navigateTo({
|
||||
url: '/pages_tool/card/detail?card_id=' + encodeURIComponent(item.from_card_id)
|
||||
});
|
||||
},
|
||||
onAgree(item) {
|
||||
this.$cardApi.exchangeAgree({
|
||||
data: { id: item.id },
|
||||
success: res => {
|
||||
if (res.code == 0) {
|
||||
// receiver_created=0 也是正常成功(同人脉已存在被跳过), 不当失败
|
||||
const d = res.data || {};
|
||||
const txt = d.receiver_created === 0 ? '已交换(人脉已存在)' : '已交换,人脉已互加';
|
||||
uni.showToast({ title: txt, icon: 'none' });
|
||||
this.loadList();
|
||||
} else if (res.message) {
|
||||
uni.showToast({ title: res.message, icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
});
|
||||
},
|
||||
onIgnore(item) {
|
||||
this.$cardApi.exchangeIgnore({
|
||||
data: { id: item.id },
|
||||
success: res => {
|
||||
if (res.code == 0) {
|
||||
uni.showToast({ title: '已忽略', icon: 'none' });
|
||||
this.loadList();
|
||||
} else if (res.message) {
|
||||
uni.showToast({ title: res.message, icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ex-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f6f7f9;
|
||||
padding-bottom: 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 筛选胶囊: 选中为浅蓝底蓝字, 未选中白底灰字
|
||||
.ex-tabs {
|
||||
display: flex;
|
||||
padding: 20rpx 24rpx;
|
||||
|
||||
.ex-tab {
|
||||
padding: 12rpx 28rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 32rpx;
|
||||
background-color: #ffffff;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
|
||||
&.active {
|
||||
background-color: #e8f3ff;
|
||||
color: #2aa5ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ex-list {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.ex-item {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.ex-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ex-avatar-wrap {
|
||||
position: relative;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ex-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
background-color: #2aa5ff;
|
||||
}
|
||||
|
||||
.ex-avatar-ph {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ex-avatar-tag {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -10rpx;
|
||||
transform: translateX(-50%);
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
background-color: #2aa5ff;
|
||||
color: #ffffff;
|
||||
font-size: 16rpx;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ex-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0 16rpx 0 20rpx;
|
||||
}
|
||||
|
||||
.ex-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ex-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.ex-badge {
|
||||
margin-left: 6rpx;
|
||||
font-size: 26rpx;
|
||||
color: #2aa5ff;
|
||||
}
|
||||
|
||||
.ex-job {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #7b8794;
|
||||
}
|
||||
|
||||
.ex-company-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.ex-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 2rpx 12rpx;
|
||||
margin-right: 10rpx;
|
||||
border-radius: 6rpx;
|
||||
background-color: #2aa5ff;
|
||||
color: #ffffff;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ex-company {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ex-agree {
|
||||
flex-shrink: 0;
|
||||
padding: 12rpx 34rpx;
|
||||
border-radius: 34rpx;
|
||||
background-color: #2aa5ff;
|
||||
color: #ffffff;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.ex-agreed {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #9aa4b0;
|
||||
}
|
||||
|
||||
.ex-reason {
|
||||
margin-top: 20rpx;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #f5f6f8;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ex-time {
|
||||
margin-top: 14rpx;
|
||||
font-size: 22rpx;
|
||||
color: #9aa4b0;
|
||||
}
|
||||
</style>
|
||||
609
pages_tool/card/style.vue
Normal file
609
pages_tool/card/style.vue
Normal file
@@ -0,0 +1,609 @@
|
||||
<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">
|
||||
<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="'background-color:' + bg.color + ';' + (bg.border ? 'border:2rpx solid ' + bg.border + ';' : '')"
|
||||
@tap="selectBg(bg.id)">
|
||||
<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: '',
|
||||
// 名片资料(真实数据, 由 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: [
|
||||
// 第一个背景 = 参考图样式(纸感米色), 选中后改变卡面底色
|
||||
{ id: 'b1', color: '#ece5db' },
|
||||
{ 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 ? 'background-color:' + bg.color + ';' : '';
|
||||
},
|
||||
// 当前选中模板的类型(classic/photo/lines/dark/navy), 决定预览的版式类 pv-*
|
||||
previewTplType() {
|
||||
const tpl = this.templates.find(item => item.id === this.selectedTpl);
|
||||
return tpl ? tpl.type : 'classic';
|
||||
}
|
||||
},
|
||||
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;
|
||||
},
|
||||
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 {
|
||||
margin-top: 24rpx;
|
||||
padding: 32rpx 28rpx;
|
||||
background-color: #ffffff;
|
||||
border: 6rpx solid #2aa5ff;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.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;
|
||||
|
||||
// 模板内部线条示意
|
||||
.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>
|
||||
Reference in New Issue
Block a user