1. 推广员
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
35
apps/web-antd/src/views/system/store/api/salesperson.ts
Normal file
35
apps/web-antd/src/views/system/store/api/salesperson.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
const prefix = 'salesperson/';
|
||||
|
||||
export function getSalespersonList(params: { store_id: number; page?: number; pageSize?: number }) {
|
||||
return requestClient.get(`${prefix}list`, { params });
|
||||
}
|
||||
|
||||
export function getCommissionList(params: Record<string, any>) {
|
||||
return requestClient.get(`${prefix}commission-list`, { params });
|
||||
}
|
||||
|
||||
export function getSettlementPreview(params: Record<string, any>) {
|
||||
return requestClient.get(`${prefix}settlement-preview`, { params });
|
||||
}
|
||||
|
||||
export function confirmSettlement(data: Record<string, any>) {
|
||||
return requestClient.post(`${prefix}settlement`, data);
|
||||
}
|
||||
|
||||
export function getSettlementList(params: Record<string, any>) {
|
||||
return requestClient.get(`${prefix}settlement-list`, { params });
|
||||
}
|
||||
|
||||
export function getSettlementDetail(params: { id: number; store_id: number }) {
|
||||
return requestClient.get(`${prefix}settlement-detail`, { params });
|
||||
}
|
||||
|
||||
export function getDrugCommissionList(params: Record<string, any>) {
|
||||
return requestClient.get(`${prefix}drug-commission-list`, { params });
|
||||
}
|
||||
|
||||
export function saveDrugCommission(data: Record<string, any>) {
|
||||
return requestClient.post(`${prefix}save-drug-commission`, data);
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
DatePicker,
|
||||
Drawer,
|
||||
Image,
|
||||
Input,
|
||||
message,
|
||||
Select,
|
||||
Table,
|
||||
Tabs,
|
||||
} from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import SalespersonSettlementConfirmModal from '#/components/salesperson/SalespersonSettlementConfirmModal.vue';
|
||||
|
||||
import {
|
||||
confirmSettlement,
|
||||
getCommissionList,
|
||||
getSalespersonList,
|
||||
getSettlementDetail,
|
||||
getSettlementList,
|
||||
getSettlementPreview,
|
||||
} from '../api/salesperson';
|
||||
|
||||
const visible = ref(false);
|
||||
const storeId = ref(0);
|
||||
const storeName = ref('');
|
||||
const salespersonId = ref<number | undefined>();
|
||||
const salespersonOptions = ref<any[]>([]);
|
||||
const activeTab = ref('pending');
|
||||
const loading = ref(false);
|
||||
const commissionData = ref<any[]>([]);
|
||||
const commissionTotal = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const orderNo = ref('');
|
||||
const dateRange = ref<any[]>([]);
|
||||
const selectedOrderIds = ref<number[]>([]);
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
const settlementList = ref<any[]>([]);
|
||||
const settlementDetail = ref<any>(null);
|
||||
const detailVisible = ref(false);
|
||||
const periodRange = ref<any[]>([]);
|
||||
const settlementConfirmRef = ref<InstanceType<typeof SalespersonSettlementConfirmModal>>();
|
||||
|
||||
const commissionColumns = [
|
||||
{ title: '订单号', dataIndex: 'order_no', width: 160 },
|
||||
{ title: '药品信息', dataIndex: 'drug_info', ellipsis: true },
|
||||
{ title: '分成金额', dataIndex: 'commission_amount', width: 100 },
|
||||
{ title: '分成时间', dataIndex: 'order_pay_at_text', width: 170 },
|
||||
];
|
||||
|
||||
const settlementColumns = [
|
||||
{ title: '结算单号', dataIndex: 'settlement_no', width: 160 },
|
||||
{ title: '方式', dataIndex: 'settlement_type_text', width: 100 },
|
||||
{ title: '金额', dataIndex: 'amount', width: 100 },
|
||||
{ title: '明细数', dataIndex: 'record_count', width: 80 },
|
||||
{ title: '时间', dataIndex: 'created_at_text', width: 170 },
|
||||
{ title: '操作', key: 'action', width: 80 },
|
||||
];
|
||||
|
||||
const status = computed(() => (activeTab.value === 'pending' ? 0 : activeTab.value === 'settled' ? 1 : undefined));
|
||||
|
||||
const rowSelection = computed(() =>
|
||||
activeTab.value === 'pending'
|
||||
? {
|
||||
selectedRowKeys: selectedRowKeys.value,
|
||||
onChange: (keys: number[], rows: any[]) => {
|
||||
selectedRowKeys.value = keys as number[];
|
||||
selectedOrderIds.value = [...new Set(rows.map((r) => r.order_id))];
|
||||
},
|
||||
getCheckboxProps: () => ({ disabled: false }),
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
function open(row: { id: number; name: string }) {
|
||||
storeId.value = row.id;
|
||||
storeName.value = row.name;
|
||||
salespersonId.value = undefined;
|
||||
selectedOrderIds.value = [];
|
||||
selectedRowKeys.value = [];
|
||||
visible.value = true;
|
||||
activeTab.value = 'pending';
|
||||
loadSalespersons();
|
||||
}
|
||||
|
||||
function onPageChange(p: number) {
|
||||
page.value = p;
|
||||
loadCommission();
|
||||
}
|
||||
|
||||
async function loadSalespersons() {
|
||||
const res = await getSalespersonList({ store_id: storeId.value, page: 1, pageSize: 100 });
|
||||
salespersonOptions.value = res?.items || [];
|
||||
if (!salespersonId.value && salespersonOptions.value.length) {
|
||||
salespersonId.value = salespersonOptions.value[0].id;
|
||||
}
|
||||
loadCommission();
|
||||
}
|
||||
|
||||
async function loadCommission() {
|
||||
if (!storeId.value || !salespersonId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: any = {
|
||||
store_id: storeId.value,
|
||||
salesperson_id: salespersonId.value,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
order_no: orderNo.value,
|
||||
};
|
||||
if (status.value !== undefined) params.status = status.value;
|
||||
if (dateRange.value?.length === 2) {
|
||||
params.start_at = dayjs(dateRange.value[0]).startOf('day').unix();
|
||||
params.end_at = dayjs(dateRange.value[1]).endOf('day').unix();
|
||||
}
|
||||
const res = await getCommissionList(params);
|
||||
commissionData.value = res?.items || [];
|
||||
commissionTotal.value = res?.total || 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSettlements() {
|
||||
if (!storeId.value || !salespersonId.value) return;
|
||||
const res = await getSettlementList({
|
||||
store_id: storeId.value,
|
||||
salesperson_id: salespersonId.value,
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
});
|
||||
settlementList.value = res?.items || [];
|
||||
}
|
||||
|
||||
async function handleSettleByPeriod() {
|
||||
if (!periodRange.value?.length || periodRange.value.length !== 2) {
|
||||
message.warning('请选择结算时间段');
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
store_id: storeId.value,
|
||||
salesperson_id: salespersonId.value,
|
||||
settlement_type: 1,
|
||||
period_start: dayjs(periodRange.value[0]).startOf('day').unix(),
|
||||
period_end: dayjs(periodRange.value[1]).endOf('day').unix(),
|
||||
};
|
||||
const preview = await getSettlementPreview(params);
|
||||
settlementConfirmRef.value?.open('确认按时间段结算', preview, params);
|
||||
}
|
||||
|
||||
function onSettlementSuccess() {
|
||||
selectedOrderIds.value = [];
|
||||
selectedRowKeys.value = [];
|
||||
loadCommission();
|
||||
loadSettlements();
|
||||
}
|
||||
|
||||
async function handleSettleByOrders() {
|
||||
if (!selectedOrderIds.value.length) {
|
||||
message.warning('请勾选订单');
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
store_id: storeId.value,
|
||||
salesperson_id: salespersonId.value,
|
||||
settlement_type: 2,
|
||||
order_ids: selectedOrderIds.value,
|
||||
};
|
||||
const preview = await getSettlementPreview(params);
|
||||
settlementConfirmRef.value?.open('确认结算选中订单', preview, params);
|
||||
}
|
||||
|
||||
async function showSettlementDetail(id: number) {
|
||||
settlementDetail.value = await getSettlementDetail({ id, store_id: storeId.value });
|
||||
detailVisible.value = true;
|
||||
}
|
||||
|
||||
watch(activeTab, (tab) => {
|
||||
page.value = 1;
|
||||
if (tab === 'history') loadSettlements();
|
||||
else loadCommission();
|
||||
});
|
||||
|
||||
watch(salespersonId, () => {
|
||||
page.value = 1;
|
||||
loadCommission();
|
||||
if (activeTab.value === 'history') loadSettlements();
|
||||
});
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer v-model:open="visible" :title="`推广员分成 - ${storeName}`" width="920" destroy-on-close>
|
||||
<div class="mb-4 flex flex-wrap items-center gap-3">
|
||||
<span>推广员</span>
|
||||
<Select
|
||||
v-model:value="salespersonId"
|
||||
:options="salespersonOptions.map((s) => ({ label: s.nick_name, value: s.id }))"
|
||||
style="width: 200px"
|
||||
/>
|
||||
<Input v-model:value="orderNo" allow-clear placeholder="订单号" style="width: 180px" @press-enter="loadCommission" />
|
||||
<DatePicker.RangePicker v-model:value="dateRange" />
|
||||
<Button type="primary" @click="loadCommission">查询</Button>
|
||||
</div>
|
||||
|
||||
<Tabs v-model:activeKey="activeTab">
|
||||
<Tabs.TabPane key="pending" tab="待结算">
|
||||
<div class="mb-3 flex flex-wrap gap-2">
|
||||
<DatePicker.RangePicker v-model:value="periodRange" placeholder="['开始','结束']" />
|
||||
<Button type="primary" @click="handleSettleByPeriod">按时间段结算</Button>
|
||||
<Button type="primary" ghost @click="handleSettleByOrders">结算选中订单</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="commissionColumns"
|
||||
:data-source="commissionData"
|
||||
:loading="loading"
|
||||
:pagination="{ current: page, pageSize, total: commissionTotal, onChange: onPageChange }"
|
||||
:row-key="(r) => r.id"
|
||||
:row-selection="rowSelection"
|
||||
size="small"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="settled" tab="已结算">
|
||||
<Table
|
||||
:columns="commissionColumns"
|
||||
:data-source="commissionData"
|
||||
:loading="loading"
|
||||
:pagination="{ current: page, pageSize, total: commissionTotal, onChange: onPageChange }"
|
||||
:row-key="(r) => r.id"
|
||||
size="small"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="history" tab="结算记录">
|
||||
<Table :columns="settlementColumns" :data-source="settlementList" :row-key="(r) => r.id" size="small">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<Button type="link" size="small" @click="showSettlementDetail(record.id)">详情</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
|
||||
<SalespersonSettlementConfirmModal
|
||||
ref="settlementConfirmRef"
|
||||
:confirm-fn="confirmSettlement"
|
||||
@success="onSettlementSuccess"
|
||||
/>
|
||||
|
||||
<Drawer v-model:open="detailVisible" title="结算单详情" width="720" destroy-on-close>
|
||||
<div v-if="settlementDetail" class="mb-3">
|
||||
<p>单号:{{ settlementDetail.settlement_no }}</p>
|
||||
<p>金额:¥{{ settlementDetail.amount }}({{ settlementDetail.record_count }} 条)</p>
|
||||
<p v-if="settlementDetail.remark">备注:{{ settlementDetail.remark }}</p>
|
||||
<div v-if="settlementDetail.proof_images?.length" class="mt-3">
|
||||
<p class="mb-2 text-sm text-gray-600">结算凭证</p>
|
||||
<Image.PreviewGroup>
|
||||
<Image
|
||||
v-for="(url, idx) in settlementDetail.proof_images"
|
||||
:key="idx"
|
||||
:src="url"
|
||||
:width="96"
|
||||
:height="96"
|
||||
class="mr-2 rounded object-cover"
|
||||
/>
|
||||
</Image.PreviewGroup>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
:columns="commissionColumns"
|
||||
:data-source="settlementDetail?.records || []"
|
||||
:row-key="(r) => r.id"
|
||||
size="small"
|
||||
/>
|
||||
</Drawer>
|
||||
</Drawer>
|
||||
</template>
|
||||
@@ -77,8 +77,8 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
},
|
||||
// { field: 'shouzimu', title: '诊所首字母' },
|
||||
{ field: 'position', title: '详细地址' },
|
||||
{ field: 'new_admin.nick_name', title: '业务员' },
|
||||
// { field: 'contact', title: '联系人' },
|
||||
{ field: 'new_admin.nick_name', title: '业务员', slots: { default: 'sales_admin' } },
|
||||
{ field: 'store_admin.phone', title: '管理员手机', slots: { default: 'store_admin_phone' } },
|
||||
{ field: 'mobile', title: '联系人、电话', slots: { default: 'mobile' } },
|
||||
{
|
||||
field: 'start_time',
|
||||
|
||||
@@ -28,6 +28,7 @@ import FormModalDemo from './components/modal.vue';
|
||||
import DrugPriceModal from './components/DrugPriceModal.vue';
|
||||
import BindConsultationModal from './components/BindConsultationModal.vue';
|
||||
import StoreExternalFieldModal from './components/StoreExternalFieldModal.vue';
|
||||
import SalespersonCommissionDrawer from './components/SalespersonCommissionDrawer.vue';
|
||||
import { formOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
|
||||
@@ -177,6 +178,12 @@ function copyText(source) {
|
||||
}
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
|
||||
const salespersonDrawerRef = ref<InstanceType<typeof SalespersonCommissionDrawer>>();
|
||||
|
||||
const openSalespersonCommission = (row: { id: number; name: string }) => {
|
||||
salespersonDrawerRef.value?.open(row);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新包邮状态
|
||||
*/
|
||||
@@ -255,6 +262,7 @@ const handleSwitchClinicType = (row: any) => {
|
||||
<DrugPriceModalComponent />
|
||||
<BindConsultationModalComponent />
|
||||
<StoreExternalFieldModalComponent />
|
||||
<SalespersonCommissionDrawer ref="salespersonDrawerRef" />
|
||||
<Grid>
|
||||
<template #toolbar-buttons>
|
||||
<TableAction
|
||||
@@ -306,6 +314,15 @@ const handleSwitchClinicType = (row: any) => {
|
||||
</template>
|
||||
</TableAction>
|
||||
</template>
|
||||
<template #sales_admin="{ row }">
|
||||
<div>
|
||||
<div>{{ row.new_admin?.nick_name || '-' }}</div>
|
||||
<div v-if="row.new_admin?.phone" class="text-xs text-gray-500">{{ row.new_admin.phone }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #store_admin_phone="{ row }">
|
||||
{{ row.store_admin?.phone || '-' }}
|
||||
</template>
|
||||
<template #mobile="{ row }">
|
||||
<div>
|
||||
诊所联系人:{{ row.contact }}
|
||||
@@ -486,6 +503,14 @@ const handleSwitchClinicType = (row: any) => {
|
||||
ifShow: isPlatformAdmin,
|
||||
onClick: showDrugPriceModal.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '推广员分成',
|
||||
type: 'link',
|
||||
icon: 'ant-design:account-book-outlined',
|
||||
size: 'small',
|
||||
auth: ['Super Admin', 'Admin'],
|
||||
onClick: () => openSalespersonCommission(row),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user