Files

227 lines
7.8 KiB
Vue
Raw Permalink Normal View History

2026-06-09 17:02:13 +08:00
<template>
<page-layout :is-platform="ctx.isPlatform" title="推广员管理" :show-back="true">
<view class="page">
<view v-if="ctx.isPlatform" class="filter-panel">
<input class="filter-input" v-model="filters.store_keyword" placeholder="诊所名称" />
<input class="filter-input" v-model="filters.nick_name" placeholder="推广员名称" />
<input class="filter-input" v-model="filters.phone" placeholder="手机号" />
<view class="filter-btn" @click="reload">查询</view>
</view>
<view v-if="!ctx.isPlatform" class="see-price-row">
<text>推广员可见价格</text>
<switch :checked="seePrice === 1" @change="onToggleSeePrice" color="#6ACDBB" />
</view>
<view class="add-bar">
<view class="add-btn" @click="goCreate">+ 新增推广员</view>
</view>
<view v-if="loading" class="loading-tip">加载中...</view>
<view v-else-if="!list.length" class="empty">暂无推广员</view>
<view v-for="item in list" :key="item.id" class="card">
<view class="card-head">
<view>
<text class="name">{{ item.nick_name || '未知' }}</text>
<text v-if="item.store_name" class="store">{{ item.store_name }}</text>
</view>
<view class="tags">
<text class="tag">{{ splitLabel(item.split_type) }}</text>
<text v-if="Number(item.tcm_split) > 0" class="tag tcm">中药 {{ item.tcm_split }}%</text>
</view>
</view>
<view class="card-body">
<text class="phone">{{ item.phone }}</text>
<view class="stat-row">
<text>累计获客</text>
<text class="num">{{ (item.count && item.count.user_number) || 0 }}</text>
</view>
<view class="stat-row">
<text>待结算</text>
<text class="num pending">¥{{ (item.count && item.count.pending_amount) || '0.00' }}</text>
</view>
<view class="stat-row">
<text>后台账号</text>
<text :class="item.has_admin_account ? 'ok' : 'warn'">{{ item.has_admin_account ? '已开通' : '未开通' }}</text>
</view>
</view>
<view class="actions">
<view class="action-btn" @click="goEdit(item)">编辑</view>
2026-07-13 12:55:07 +08:00
<view class="action-btn primary" @click="goCommission(item)">服务费与结算</view>
2026-06-09 17:02:13 +08:00
<view v-if="item.qr_code && item.qr_code.qr_code" class="action-btn" @click="openQr(item)">二维码</view>
<view class="action-btn" @click="goLeads(item)">获客</view>
<view
v-if="!item.has_admin_account"
class="action-btn warn"
@click="openAdminAccount(item)"
>开通后台</view>
</view>
</view>
</view>
</page-layout>
</template>
<script>
import PageLayout from './components/PageLayout.vue';
import { getSalespersonManageContext, resolveManageMode } from './common/context.js';
import { splitTypeLabel } from './common/labels.js';
import { openQrCodePreview } from '@/utils/qrCodePreview.js';
const ROOT = '/subPackages/sub_salesperson_manage';
export default {
components: { PageLayout },
data() {
return {
ctx: getSalespersonManageContext('clinic'),
filters: { store_keyword: '', nick_name: '', phone: '' },
presetStoreId: 0,
list: [],
loading: false,
seePrice: 0,
openingId: 0,
};
},
onLoad(options) {
this.ctx = getSalespersonManageContext(resolveManageMode(options));
this.presetStoreId = Number(options.store_id || 0);
if (this.presetStoreId > 0) {
this.filters.store_keyword = '';
}
},
onShow() {
this.reload();
if (!this.ctx.isPlatform) this.loadSeePrice();
},
methods: {
splitLabel: splitTypeLabel,
async reload() {
this.loading = true;
try {
const params = {
page: 1,
pageSize: 50,
...this.filters,
};
if (this.presetStoreId > 0) params.store_id = this.presetStoreId;
const wrap = await this.ctx.api.getList(params);
this.list = (wrap && wrap.ok && wrap.data && wrap.data.items) ? wrap.data.items : [];
} finally {
this.loading = false;
}
},
async loadSeePrice() {
const wrap = await this.ctx.api.getSeePriceDetail();
if (wrap && wrap.ok && wrap.data) {
this.seePrice = Number(wrap.data.see_price) || 0;
}
},
async onToggleSeePrice(e) {
const wrap = await this.ctx.api.toggleSeePrice();
if (wrap && wrap.ok && wrap.data) {
this.seePrice = Number(wrap.data.see_price) || 0;
uni.showToast({ title: this.seePrice === 1 ? '已开启可见价格' : '已关闭可见价格', icon: 'none' });
}
},
goCreate() {
uni.navigateTo({ url: `${ROOT}/form?mode=${this.ctx.mode}` });
},
goEdit(item) {
uni.navigateTo({
url: `${ROOT}/form?mode=${this.ctx.mode}&id=${item.id}&store_id=${(item.qr_code && item.qr_code.store_id) || ''}`,
});
},
goCommission(item) {
const storeId = (item.qr_code && item.qr_code.store_id) || '';
uni.navigateTo({
url: `${ROOT}/commission/index?mode=${this.ctx.mode}&salesperson_id=${item.id}&nick_name=${encodeURIComponent(item.nick_name || '')}&store_id=${storeId}`,
});
},
goLeads(item) {
uni.navigateTo({
url: `${ROOT}/leads/index?mode=${this.ctx.mode}&salesperson_id=${item.id}&nick_name=${encodeURIComponent(item.nick_name || '')}`,
});
},
openQr(item) {
openQrCodePreview({
type: 'salesperson',
title: item.nick_name || '推广员二维码',
payload: {
nick_name: item.nick_name || '',
qr_code: {
qr_code: (item.qr_code && item.qr_code.qr_code) || '',
store: { name: item.store_name || '' },
},
},
});
},
async openAdminAccount(item) {
if (this.openingId) return;
this.openingId = item.id;
try {
const wrap = await this.ctx.api.openAdminAccount({
salesperson_id: item.id,
store_id: item.qr_code && item.qr_code.store_id,
});
if (wrap && wrap.ok) {
uni.showModal({
title: '开通成功',
content: '默认密码Xk123456@',
showCancel: false,
});
this.reload();
}
} finally {
this.openingId = 0;
}
},
},
};
</script>
<style lang="scss" scoped>
.page { padding: 24rpx; padding-bottom: 48rpx; }
.filter-panel { display: flex; flex-wrap: wrap; gap: 12rpx; margin-bottom: 20rpx; }
.filter-input {
flex: 1; min-width: 200rpx; background: #fff; border-radius: 12rpx;
padding: 16rpx 20rpx; font-size: 26rpx;
}
.filter-btn {
background: #6ACDBB; color: #fff; padding: 16rpx 32rpx; border-radius: 12rpx; font-size: 26rpx;
}
.see-price-row {
display: flex; justify-content: space-between; align-items: center;
background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; font-size: 28rpx;
}
.add-bar { margin-bottom: 20rpx; }
.add-btn {
background: linear-gradient(90deg, #00BFA6, #6ACDBB); color: #fff;
text-align: center; padding: 20rpx; border-radius: 16rpx; font-size: 28rpx;
}
.loading-tip, .empty { text-align: center; color: #999; padding: 80rpx 0; }
.card {
background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04);
}
.card-head { display: flex; justify-content: space-between; margin-bottom: 16rpx; }
.name { display: block; font-size: 32rpx; font-weight: 600; }
.store { font-size: 24rpx; color: #999; }
.tags { display: flex; flex-direction: column; gap: 8rpx; align-items: flex-end; }
.tag { font-size: 22rpx; background: #EFF6FF; color: #3B82F6; padding: 4rpx 12rpx; border-radius: 8rpx; }
.tag.tcm { background: #F0FDF4; color: #22C55E; }
.phone { font-size: 26rpx; color: #666; display: block; margin-bottom: 12rpx; }
.stat-row { display: flex; justify-content: space-between; font-size: 26rpx; margin-bottom: 8rpx; }
.num { font-weight: 600; }
.pending { color: #F97316; }
.ok { color: #22C55E; }
.warn { color: #EF4444; }
.actions { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 20rpx; }
.action-btn {
font-size: 24rpx; padding: 10rpx 20rpx; border-radius: 10rpx;
background: #f5f5f5; color: #333;
}
.action-btn.primary { background: #6ACDBB; color: #fff; }
.action-btn.warn { background: #FEF3C7; color: #D97706; }
</style>