Files
lucky_shop/pages_tool/card/enterprise.vue

831 lines
23 KiB
Vue

<template>
<view class="ent-card" :style="[designVars, { fontFamily: ds.fontFamily }]">
<view class="page-layer">
<!-- 顶部英雄区: 蓝色渐变圆角卡(对齐后端 wap 参考图) -->
<view class="hero" v-if="headerSrc || staffList.length">
<view class="hero-pill">
<view class="hero-pill-dot"></view>
<text>数字名片</text>
</view>
<!-- staff 模式: 员工名片轮播 -->
<swiper v-if="headerMode === 'staff' && staffList.length" class="hero-swiper"
:indicator-dots="staffList.length > 1" indicator-active-color="rgba(255,255,255,0.9)"
indicator-color="rgba(255,255,255,0.4)" :autoplay="staffList.length > 1" circular
@change="onStaffChange">
<swiper-item v-for="(s, i) in staffList" :key="i">
<view class="hero-id">
<image v-if="s.avatar" class="hi-avatar" :src="$util.img(s.avatar)" mode="aspectFill" />
<view v-else class="hi-avatar hi-avatar-ph">
<text class="hi-avatar-char">企</text>
</view>
<view class="hi-info">
<view class="hi-name">{{ s.name }}</view>
<view class="hi-job" v-if="s.job_title">{{ s.job_title }}</view>
</view>
</view>
</swiper-item>
</swiper>
<!-- official / self: 单人信息 -->
<view v-else class="hero-id">
<image v-if="headerSrc && headerSrc.avatar" class="hi-avatar" :src="$util.img(headerSrc.avatar)" mode="aspectFill" />
<view v-else class="hi-avatar hi-avatar-ph">
<text class="hi-avatar-char">企</text>
</view>
<view class="hi-info">
<view class="hi-name">{{ headerSrc.name || '企业名片' }}</view>
<view class="hi-job" v-if="headerSrc.job_title">{{ headerSrc.job_title }}</view>
</view>
</view>
</view>
<!-- 快捷操作条: 压在英雄卡下沿(参考图: 打电话/发邮件/官方网站/存通讯录) -->
<view class="quick" v-if="headerSrc">
<view class="q-item" @tap="onQuickCall">
<view class="q-icon q-icon-blue">
<text class="iconfont icon-dianhua"></text>
</view>
<text class="q-label">打电话</text>
</view>
<view class="q-item" @tap="onQuickEmail">
<view class="q-icon q-icon-cyan">
<text class="q-char">@</text>
</view>
<text class="q-label">发邮件</text>
</view>
<view class="q-item" @tap="onQuickWebsite">
<view class="q-icon q-icon-blue">
<text class="q-char">网</text>
</view>
<text class="q-label">官方网站</text>
</view>
<view class="q-item" @tap="onQuickSave">
<view class="q-icon q-icon-orange">
<text class="iconfont icon-yonghu"></text>
</view>
<text class="q-label">存通讯录</text>
</view>
</view>
<!-- 企业简介 -->
<view class="bio" v-if="fields.bio" :style="{ backgroundColor: ds.bgColor, borderRadius: ds.cardRadius + 'px' }">
<text class="bio-text" :style="{ color: ds.textColor }">{{ fields.bio }}</text>
<text class="bio-en" v-if="fields.bio_en" :style="{ color: ds.textSoft }">{{ fields.bio_en }}</text>
</view>
<!-- 联系资料(企业字段) -->
<view class="contact" v-if="contentRows.length" :style="{ backgroundColor: ds.bgColor, borderRadius: ds.cardRadius + 'px' }">
<view class="ct-title" :style="{ borderLeftColor: ds.themeColor }">联系资料</view>
<view v-for="(row, idx) in contentRows" :key="idx" class="ct-row">
<view class="ct-icon" :style="{ backgroundColor: ds.themeSoft }">
<text v-if="row.icon" class="iconfont" :class="row.icon" :style="{ color: ds.themeColor }"></text>
<text v-else class="ct-char" :style="{ color: ds.themeColor }">{{ row.label.slice(0, 1) }}</text>
</view>
<text class="ct-label" :style="{ color: ds.textSoft }">{{ row.label }}</text>
<text class="ct-value" :class="{ 'ct-multi': row.multiline }" :style="{ color: ds.textColor }">{{ row.value }}</text>
<text v-if="row.copyable" class="ct-copy" @tap="copyText(row.value)">复制</text>
</view>
</view>
<!-- self 模式未自选提示 -->
<view class="self-hint" v-if="headerMode === 'self' && headerSelf && headerSelf.chosen === 0" @tap="openChooseSelf">
<text class="sh-icon iconfont icon-gonggao" :style="{ color: ds.themeColor }"></text>
<text class="sh-text">你还没有选定展示名片,当前自动取最新一张</text>
<text class="sh-btn" :style="{ color: ds.themeColor }">选择</text>
</view>
<!-- 加载中 -->
<view class="ent-loading" v-if="isLoading">加载中…</view>
<!-- 不存在 / 无权查看 -->
<ns-empty v-if="!isLoading && !headerSrc && !staffList.length" :isIndex="false"
:text="errorMsg || '企业名片不存在或未生效,请在管理端完善资料并设为生效'"></ns-empty>
</view>
<!-- 底部操作条(参考图: 保存到通讯录 + 分享; 收藏/交换名片为企业卡无对应后端能力, 暂不提供) -->
<view class="bottom-bar" v-if="headerSrc">
<view class="bb-btn bb-main" @tap="onQuickSave">保存到通讯录</view>
<!-- #ifdef MP-WEIXIN -->
<button class="bb-btn bb-share" open-type="share">分享</button>
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS || MP-HARMONY -->
<view class="bb-btn bb-share" @tap="openShare">分享</view>
<!-- #endif -->
</view>
<!-- 员工自选展示名片弹层 -->
<view class="choose-mask" v-if="showChoose" @tap="closeChoose"></view>
<view class="choose-sheet" :class="{ 'choose-show': showChoose }">
<view class="cs-bar"></view>
<view class="cs-title">选择展示名片</view>
<view class="cs-list">
<view v-for="(c, idx) in myCards" :key="idx" class="cs-item" @tap="chooseSelf(c)">
<view class="cs-avatar" :style="{ backgroundColor: ds.themeColor }">{{ (c.name || '?').slice(0, 1) }}</view>
<view class="cs-info">
<view class="cs-name">{{ c.name }}</view>
<view class="cs-sub" v-if="c.job_title || c.company">{{ [c.job_title, c.company].filter(Boolean).join(' · ') }}</view>
</view>
<text v-if="headerSelf && headerSelf.card_id === c.card_id" class="cs-chosen iconfont icon-dui" :style="{ color: ds.themeColor }"></text>
</view>
<view class="cs-empty" v-if="!myCards.length">你还没有个人名片</view>
</view>
</view>
</view>
</template>
<script>
function mixHex(a, b, t) {
const toRgb = h => {
h = (h || '').replace('#', '');
if (h.length === 3) h = h.split('').map(c => c + c).join('');
const n = parseInt(h, 16);
if (isNaN(n)) return [0, 0, 0];
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
};
const [r1, g1, b1] = toRgb(a);
const [r2, g2, b2] = toRgb(b);
const r = Math.round(r1 + (r2 - r1) * t);
const g = Math.round(g1 + (g2 - g1) * t);
const bl = Math.round(b1 + (b2 - b1) * t);
return '#' + [r, g, bl].map(x => x.toString(16).padStart(2, '0')).join('');
}
export default {
data() {
return {
cardId: '',
isLoading: true,
errorMsg: '',
data: null, // 企业名片详情(后端 ENTERPRISE_UNIAPP_GUIDE §三)
header: null, // {mode,official_card_id,official,self,staff}
fields: {}, // 企业信息(内容区)
design: null, // {theme_color,bg_color,text_color,template_id,canvas,header_mode,official_card_id}
staffIndex: 0, // staff 轮播当前项(存通讯录按当前展示的员工取字段)
showChoose: false,
myCards: []
};
},
computed: {
headerMode() {
return (this.header && this.header.mode) || 'official';
},
headerSrc() {
const h = this.header || {};
let src;
if (this.headerMode === 'official') src = h.official || this.fields;
else if (this.headerMode === 'self') src = h.self || this.fields;
else return null; // staff 走轮播
// 注意: 空对象 {} 在 JS 里是真值, 必须显式判空,
// 否则后端返回空数据时会渲染出"有壳没内容"的空页面且空态提示也不出现
return src && Object.keys(src).length ? src : null;
},
staffList() {
return (this.header && this.header.staff) || [];
},
staffCurrent() {
return this.staffList[this.staffIndex] || null;
},
headerSelf() {
return (this.header && this.header.self) || null;
},
// 名片区当前展示的那个人(official/self 单人 或 staff 当前轮播项)
displayPerson() {
if (this.headerMode === 'staff') return this.staffCurrent || {};
return this.headerSrc || {};
},
contentRows() {
const f = this.fields || {};
const rows = [];
const push = (label, value, opts) => {
if (value !== undefined && value !== null && String(value).trim() !== '') {
rows.push(Object.assign({ label, value: String(value) }, opts || {}));
}
};
push('手机', f.mobile, { icon: 'icon-shouji1', copyable: true });
push('邮箱', f.email, { copyable: true });
push('网站', f.website, { copyable: true });
push('地址', f.address, { icon: 'icon-dizhi' });
push('企业名称', f.name);
return rows;
},
/* ============ design 归一化(与 detail.vue 同源, 仅做有限主题化) ============ */
ds() {
const d = this.design || {};
const FONTS = {
system: '-apple-system,BlinkMacSystemFont,"PingFang SC","Helvetica Neue",Arial,sans-serif',
sans: '"Source Han Sans SC","Noto Sans SC","PingFang SC",Helvetica,Arial,sans-serif',
serif: '"Source Han Serif SC","Songti SC",SimSun,Georgia,serif',
rounded: '"Yuanti SC","YouYuan","Quicksand",-apple-system,sans-serif'
};
const isHex = v => /^#[0-9a-fA-F]{3,8}$/.test(v || '');
const theme = isHex(d.theme_color) ? d.theme_color : '#119dff';
const text = isHex(d.text_color) ? d.text_color : '#1f2329';
const bg = isHex(d.bg_color) ? d.bg_color : '#ffffff';
const fontKey = FONTS[d.font_family] ? d.font_family : 'system';
const fontScale = Math.min(160, Math.max(60, Number(d.font_scale) || 100));
return {
themeColor: theme,
textColor: text,
bgColor: bg,
fontFamily: FONTS[fontKey],
cardRadius: Number(d.card_radius) || 16,
fontScaleNum: fontScale / 100,
textSoft: mixHex(text, bg, 0.55),
themeSoft: mixHex(theme, '#ffffff', 0.85),
themeBorder: mixHex(theme, '#ffffff', 0.55),
headerGradientEnd: typeof d.header_gradient_end === 'string' ? d.header_gradient_end : ''
};
},
designVars() {
const s = this.ds;
return {
'--card-theme': s.themeColor,
'--card-theme-end': s.headerGradientEnd || s.themeColor,
'--card-theme-soft': s.themeSoft,
'--card-theme-border': s.themeBorder,
'--card-text': s.textColor,
'--card-text-soft': s.textSoft,
'--card-bg': s.bgColor,
'--card-radius': s.cardRadius + 'px',
'--card-font': s.fontFamily,
'--card-font-scale': s.fontScaleNum
};
}
},
onLoad(options) {
this.cardId = (options && options.card_id) ? decodeURIComponent(options.card_id) : '';
this.loadData();
},
onReady() {
uni.setNavigationBarTitle({ title: '企业名片' });
},
onShow() {
// 每次显示都兜底标题(微信返回本页时可能恢复成旧值); 从自选弹层返回也刷新
uni.setNavigationBarTitle({ title: '企业名片' });
// #ifdef H5
document.title = '企业名片';
// #endif
if (this._loadedOnce) this.loadData();
this._loadedOnce = true;
},
onPullDownRefresh() {
this.loadData();
setTimeout(() => uni.stopPullDownRefresh(), 300);
},
onShareAppMessage() {
const name = (this.fields && this.fields.name) || '企业名片';
return {
title: name + ' - 企业名片',
path: '/pages_tool/card/enterprise' + (this.cardId ? '?card_id=' + encodeURIComponent(this.cardId) : ''),
imageUrl: this.displayPerson && this.displayPerson.avatar ? this.$util.img(this.displayPerson.avatar) : ''
};
},
methods: {
loadData(retry) {
this.isLoading = true;
this.$cardApi.enterpriseDetail({
data: this.cardId ? { card_id: this.cardId } : {},
success: res => {
// 输出原始响应, 便于真机排查"刷新后数据变空"类问题(控制台看后端到底回了什么)
console.log('[enterprise] detail resp', this.cardId || '(无card_id)', res);
if (res.code == 0) {
const d = res.data || {};
const emptyCard = !d.header && (!d.fields || !Object.keys(d.fields).length);
if (emptyCard && (this.header || Object.keys(this.fields).length)) {
// 已有数据时刷新却拿到空卡(多为后端"当前生效卡"与所查卡不一致):
// 保留原数据, 避免"刷新一下内容就没了"
console.warn('[enterprise] 刷新返回空卡, 保留原数据', d);
} else {
this.data = d;
this.header = d.header || null;
this.fields = d.fields || {};
this.design = d.design || null;
}
this.errorMsg = '';
this.isLoading = false;
} else if (res.code == -1 && !retry) {
// -1 = 尚未登录: 冷启动/刚返回本页时 store 的 token 可能还没恢复完成,
// 属瞬时态, 静默重试一次再决定是否展示错误(与 detail.vue 同策略)
setTimeout(() => this.loadData(true), 800);
} else {
// 登录类错误(-1/-10009 等)不展示"去登录"死路:
// 本页是客户视角分享页, 按后端对接文档 §五 游客也应可看(待后端放开),
// 这里给中性可重试文案; 其余错误原样带出
const msg = res.message || '';
const isLoginErr = res.code == -1 || /登录/.test(msg);
this.errorMsg = isLoginErr ? '名片加载失败,请下拉重试' : (msg || '加载失败');
this.isLoading = false;
}
},
fail: () => {
// 失败时保留原数据(不清空), 仅在首次无数据时展示错误
if (!this.header && !Object.keys(this.fields).length) {
this.errorMsg = '网络异常,请下拉重试';
}
this.isLoading = false;
}
});
},
onStaffChange(e) {
this.staffIndex = (e.detail && e.detail.current) || 0;
},
copyText(v) {
uni.setClipboardData({
data: String(v || ''),
success: () => uni.showToast({ title: '已复制', icon: 'success' })
});
},
/* ===== 快捷操作(参考图四项) ===== */
onQuickCall() {
const m = this.displayPerson.mobile || this.fields.mobile;
if (!m) return uni.showToast({ title: '未填写手机号', icon: 'none' });
uni.makePhoneCall({ phoneNumber: String(m) });
},
onQuickEmail() {
const e = this.displayPerson.email || this.fields.email;
if (!e) return uni.showToast({ title: '未填写邮箱', icon: 'none' });
this.copyText(e);
},
onQuickWebsite() {
const w = this.displayPerson.website || this.fields.website;
if (!w) return uni.showToast({ title: '未填写官网', icon: 'none' });
// 小程序内无法直接打开外部网页(web-view 中转体验差), 复制链接到浏览器打开
this.copyText(w);
uni.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none' });
},
onQuickSave() {
// 跳原生页直接调 addPhoneContact(web-view 内 H5 才受限, 原生页可直存)
const p = this.displayPerson || {};
const q = [
'name=' + encodeURIComponent(p.name || this.fields.name || ''),
'mobile=' + encodeURIComponent(p.mobile || this.fields.mobile || ''),
'company=' + encodeURIComponent(this.fields.name || ''),
'job_title=' + encodeURIComponent(p.job_title || ''),
'email=' + encodeURIComponent(p.email || this.fields.email || ''),
'website=' + encodeURIComponent(p.website || this.fields.website || ''),
'address=' + encodeURIComponent(p.address || this.fields.address || ''),
'wechat=' + encodeURIComponent(p.wechat || '')
].join('&');
uni.navigateTo({ url: '/pages_tool/card/save-contact?' + q });
},
openChooseSelf() {
this.$cardApi.cardLists({
data: { page: 1, page_size: 100 },
success: res => {
const d = res.data || {};
this.myCards = d.list || [];
this.showChoose = true;
},
fail: () => uni.showToast({ title: '加载名片失败', icon: 'none' })
});
},
closeChoose() {
this.showChoose = false;
},
chooseSelf(c) {
if (!c || !c.card_id) return;
this.$cardApi.enterpriseChooseSelf({
data: { card_id: c.card_id },
success: res => {
if (res.code == 0) {
uni.showToast({ title: '已选择展示名片', icon: 'success' });
this.showChoose = false;
this.loadData();
} else {
uni.showToast({ title: res.message || '选择失败', icon: 'none' });
}
},
fail: () => uni.showToast({ title: '网络异常', icon: 'none' })
});
},
openShare() {
uni.showToast({ title: '请点击右上角或浏览器菜单分享', icon: 'none' });
}
}
};
</script>
<style lang="scss" scoped>
.ent-card {
min-height: 100vh;
background-color: #f5f6fa;
padding-bottom: 140rpx;
}
.page-layer {
padding: 24rpx;
}
/* ===== 英雄区 ===== */
.hero {
position: relative;
border-radius: 24rpx;
padding: 32rpx 32rpx 140rpx;
background:
radial-gradient(circle at 88% 18%, rgba(255, 255, 255, 0.14) 0, rgba(255, 255, 255, 0.14) 120rpx, transparent 121rpx),
linear-gradient(150deg, var(--card-theme, #1e6fd9) 0%, var(--card-theme-end, #123f8f) 100%);
overflow: visible;
margin-bottom: 90rpx;
}
.hero-pill {
display: inline-flex;
align-items: center;
padding: 6rpx 20rpx;
border: 1rpx solid rgba(255, 255, 255, 0.5);
border-radius: 26rpx;
color: #ffffff;
font-size: 22rpx;
.hero-pill-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #6cf9c0;
margin-right: 10rpx;
}
}
.hero-swiper {
height: 180rpx;
margin-top: 24rpx;
}
.hero-id {
display: flex;
align-items: center;
margin-top: 28rpx;
.hi-avatar {
width: 128rpx;
height: 128rpx;
border-radius: 50%;
background: #ffffff;
border: 6rpx solid rgba(255, 255, 255, 0.85);
flex-shrink: 0;
&.hi-avatar-ph {
display: flex;
align-items: center;
justify-content: center;
.hi-avatar-char {
font-size: 56rpx;
font-weight: 600;
color: var(--card-theme, #1e6fd9);
}
}
}
.hi-info {
margin-left: 28rpx;
min-width: 0;
.hi-name {
font-size: 40rpx;
font-weight: 600;
color: #ffffff;
}
.hi-job {
margin-top: 10rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.85);
}
}
}
/* ===== 快捷操作条: 上移压住英雄卡下沿 ===== */
.quick {
position: relative;
z-index: 2;
margin: -130rpx 24rpx 24rpx;
background: #ffffff;
border-radius: 20rpx;
box-shadow: 0 8rpx 30rpx rgba(30, 111, 217, 0.10);
display: flex;
padding: 28rpx 0;
.q-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.q-icon {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.q-icon-blue {
background: #e3f0ff;
.iconfont,
.q-char {
color: #3d8bff;
font-size: 40rpx;
}
}
&.q-icon-cyan {
background: #e2f7f4;
.q-char {
color: #2bb99e;
font-size: 40rpx;
font-weight: 600;
}
}
&.q-icon-orange {
background: #fff3e0;
.iconfont {
color: #f5a623;
font-size: 40rpx;
}
}
}
.q-label {
margin-top: 14rpx;
font-size: 24rpx;
color: #444;
}
}
}
/* ===== 企业简介 ===== */
.bio {
margin: 0 0 24rpx;
padding: 28rpx 28rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
.bio-text {
display: block;
font-size: 28rpx;
line-height: 1.6;
word-break: break-all;
}
.bio-en {
display: block;
margin-top: 12rpx;
font-size: 24rpx;
line-height: 1.5;
word-break: break-all;
}
}
/* ===== 联系资料 ===== */
.contact {
padding: 28rpx 28rpx 12rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
margin-bottom: 24rpx;
.ct-title {
font-size: 30rpx;
font-weight: 600;
color: #1f2329;
padding-left: 16rpx;
border-left-width: 6rpx;
border-left-style: solid;
margin-bottom: 20rpx;
}
.ct-row {
display: flex;
align-items: center;
padding: 18rpx 0;
border-bottom: 1rpx solid #f2f3f5;
&:last-child {
border-bottom: none;
}
.ct-icon {
width: 56rpx;
height: 56rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
.iconfont {
font-size: 30rpx;
}
.ct-char {
font-size: 26rpx;
font-weight: 600;
}
}
.ct-label {
width: 88rpx;
flex-shrink: 0;
font-size: 26rpx;
}
.ct-value {
flex: 1;
min-width: 0;
font-size: 28rpx;
word-break: break-all;
&.ct-multi {
white-space: pre-wrap;
}
}
.ct-copy {
margin-left: 16rpx;
flex-shrink: 0;
padding: 6rpx 20rpx;
border-radius: 22rpx;
background: #f2f3f5;
font-size: 22rpx;
color: #666;
}
}
}
/* ===== self 未自选提示 ===== */
.self-hint {
display: flex;
align-items: center;
padding: 20rpx 24rpx;
background-color: var(--card-theme-soft, #e8f1fb);
border: 1rpx solid var(--card-theme-border, #cfe3fb);
border-radius: 16rpx;
margin-bottom: 24rpx;
.sh-icon {
margin-right: 12rpx;
font-size: 30rpx;
}
.sh-text {
flex: 1;
min-width: 0;
font-size: 24rpx;
color: #666;
}
.sh-btn {
font-size: 26rpx;
margin-left: 12rpx;
}
}
.ent-loading {
text-align: center;
padding: 40rpx 0;
color: #999;
font-size: 26rpx;
}
/* ===== 底部操作条 ===== */
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
gap: 20rpx;
padding: 20rpx 24rpx calc(20rpx + env(safe-area-inset-bottom));
background: #ffffff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
z-index: 10;
.bb-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 44rpx;
font-size: 28rpx;
&.bb-main {
background: linear-gradient(150deg, #3d8bff, #1e6fd9);
color: #ffffff;
font-weight: 600;
}
&.bb-share {
background: #f2f3f5;
color: #333;
border: none;
padding: 0;
margin: 0;
&::after {
border: none;
}
}
}
}
/* ===== 员工自选弹层 ===== */
.choose-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 99;
}
.choose-sheet {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 28rpx 28rpx 0 0;
z-index: 100;
transform: translateY(100%);
transition: transform 0.25s ease;
max-height: 70vh;
display: flex;
flex-direction: column;
&.choose-show {
transform: translateY(0);
}
.cs-bar {
width: 64rpx;
height: 8rpx;
background: #ddd;
border-radius: 4rpx;
margin: 16rpx auto 8rpx;
}
.cs-title {
text-align: center;
font-size: 30rpx;
font-weight: 600;
padding: 12rpx 0 20rpx;
}
.cs-list {
overflow-y: auto;
padding: 0 24rpx 32rpx;
}
.cs-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f2f2f2;
}
.cs-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
color: #fff;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.cs-info {
flex: 1;
min-width: 0;
.cs-name {
font-size: 30rpx;
color: #333;
}
.cs-sub {
font-size: 24rpx;
color: #999;
margin-top: 4rpx;
}
}
.cs-chosen {
font-size: 36rpx;
}
.cs-empty {
text-align: center;
color: #999;
font-size: 26rpx;
padding: 40rpx 0;
}
}
</style>