Files
2026-07-13 12:55:07 +08:00

227 lines
7.8 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>
<view class="action-btn primary" @click="goCommission(item)">服务费与结算</view>
<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>