fix: 推广员
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
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
CI / CI OK (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:
2025-09-23 14:08:26 +08:00
parent 3e3f0519ad
commit e637a46889
12 changed files with 37268 additions and 48 deletions

View File

@@ -137,44 +137,44 @@ const viewFriendProfile = () => {
</div>
<div class="header-actions">
<!-- 语音通话按钮 -->
<button
class="action-btn voice-call-btn"
title="语音通话"
@click="startVoiceCall"
>
<i class="fas fa-phone"></i>
</button>
<!-- &lt;!&ndash; 语音通话按钮 &ndash;&gt;-->
<!-- <button-->
<!-- class="action-btn voice-call-btn"-->
<!-- title="语音通话"-->
<!-- @click="startVoiceCall"-->
<!-- >-->
<!-- <i class="fas fa-phone"></i>-->
<!-- </button>-->
<!-- 视频通话按钮 -->
<button
class="action-btn video-call-btn"
title="视频通话"
@click="startVideoCall"
>
<i class="fas fa-video"></i>
</button>
<!-- &lt;!&ndash; 视频通话按钮 &ndash;&gt;-->
<!-- <button-->
<!-- class="action-btn video-call-btn"-->
<!-- title="视频通话"-->
<!-- @click="startVideoCall"-->
<!-- >-->
<!-- <i class="fas fa-video"></i>-->
<!-- </button>-->
<!-- 更多操作 -->
<button
class="action-btn more-btn"
title="更多操作"
@click="showMoreActions = !showMoreActions"
>
<i class="fas fa-ellipsis-v"></i>
</button>
<!-- <button-->
<!-- class="action-btn more-btn"-->
<!-- title="更多操作"-->
<!-- @click="showMoreActions = !showMoreActions"-->
<!-- >-->
<!-- <i class="fas fa-ellipsis-v"></i>-->
<!-- </button>-->
<!-- 更多操作菜单 -->
<div v-if="showMoreActions" class="more-actions-menu">
<div class="menu-item" @click="clearChatHistory">
<i class="fas fa-trash"></i>
<span>清空聊天记录</span>
</div>
<div class="menu-item" @click="viewFriendProfile">
<i class="fas fa-user"></i>
<span>查看资料</span>
</div>
</div>
<!-- &lt;!&ndash; 更多操作菜单 &ndash;&gt;-->
<!-- <div v-if="showMoreActions" class="more-actions-menu">-->
<!--&lt;!&ndash; <div class="menu-item" @click="clearChatHistory">&ndash;&gt;-->
<!--&lt;!&ndash; <i class="fas fa-trash"></i>&ndash;&gt;-->
<!--&lt;!&ndash; <span>清空聊天记录</span>&ndash;&gt;-->
<!--&lt;!&ndash; </div>&ndash;&gt;-->
<!-- <div class="menu-item" @click="viewFriendProfile">-->
<!-- <i class="fas fa-user"></i>-->
<!-- <span>查看资料</span>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -50,3 +50,20 @@ export async function saveNavUrlApi(data) {
export async function deleteNavApi(data) {
return requestClient.post<any>(`${prefix}delete-nav`, data);
}
/*
----------------------------推广员---------------------------------
*/
/**
* 推广员列表
*/
export async function salespersonListApi() {
return requestClient.get<any>(`salesperson/list`);
}
/**
* 创建推广员
* @param data
*/
export async function salespersonCreateApi(data) {
return requestClient.post<any>(`salesperson/create`, data);
}

View File

@@ -0,0 +1,124 @@
<script setup lang="ts">
import { useVbenModal } from '@vben/common-ui';
import { Button, Card, message } from 'ant-design-vue';
import { PlusOutlined } from '@ant-design/icons-vue';
import QrCodePreview from '#/views/business/store/settings/components/SalespersonQrCodePreview.vue';
import salespersonCreateModal from "#/views/business/store/settings/components/SalespersonCreateModal.vue";
import {ref} from "vue";
import {salespersonListApi} from "#/views/business/store/settings/api";
// defineProps<{
// data: any;
// }>();
const data = ref([]);
const getList = () => {
salespersonListApi().then((res) => {
data.value = res;
})
}
getList();
const [SalespersonCreateModal, SalespersonCreateModalApi] = useVbenModal({
connectedComponent: salespersonCreateModal,
});
const [QrCodePreviewModal, QrCodePreviewApi] = useVbenModal({
connectedComponent: QrCodePreview,
});
const openDoctorQrCode = (data) => {
QrCodePreviewApi.setData({
values: data,
});
QrCodePreviewApi.open();
};
const openSalespersonModal = () => {
SalespersonCreateModalApi.open();
};
</script>
<template>
<div
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<SalespersonCreateModal />
<QrCodePreviewModal />
<Card
class="rounded-xl shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer border border-gray-200 p-6 items-center justify-center"
@click="openSalespersonModal"
>
<!-- 内容容器居中布局 + 垂直排列 -->
<div class="flex flex-col items-center justify-center gap-3">
<!-- 图标增强视觉引导 -->
<PlusOutlined class="text-blue-500 text-2xl transition-transform duration-200 hover:scale-110" />
<!-- 按钮文字居中 + 优化字体样式 -->
<Button
type="link"
@click.stop="openSalespersonModal"
class="
text-sm font-medium text-blue-600
hover:text-blue-700 hover:underline-offset-4
underline-decoration-clone
"
>
添加推广员
</Button>
</div>
</Card>
<Card
v-for="(item, index) in data.items"
:key="index"
class="rounded-lg shadow-sm"
>
<!-- 顶部信息栏 -->
<div class="mb-4 flex items-start justify-between">
<h3 class="text-lg font-semibold text-gray-800">
{{ item.nick_name || '未知医生' }}
</h3>
</div>
<!-- 医生信息 -->
<div class="mb-4 space-y-2 text-sm text-gray-600">
<div class="flex justify-between">
<span>手机号:</span>
<span class="text-gray-800">{{ item.phone }}</span>
</div>
<div class="flex justify-between">
<span>加入时间:</span>
<span>{{
item.created_at
}}</span>
</div>
<div class="flex justify-between">
<span>累计获客:</span>
<span class="font-medium text-blue-600">100</span>
</div>
<div class="flex justify-between">
<span>本月获客:</span>
<span class="font-medium text-blue-600">10</span>
</div>
</div>
<!-- 二维码区域 -->
<div class="border-t border-gray-100 pt-4">
<p class="mb-2 text-sm text-gray-600">联系二维码:</p>
<div
v-if="item.qr_code.qr_code"
class="cursor-pointer"
@click="openDoctorQrCode(item)"
>
<Button class="w-full" size="small" type="primary">查看二维码</Button>
</div>
<div v-else class="py-2 text-center text-sm text-gray-400">
暂无二维码
</div>
</div>
</Card>
</div>
</template>

View File

@@ -0,0 +1,65 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {salespersonCreateApi, saveNavUrlApi} from '#/views/business/store/settings/api';
import { salespersonModalFormProps } from '../config/form';
const isUpdate = ref(false);
const gridApi = ref();
const getMyInfoApi = ref();
const [Form, formApi] = useVbenForm(salespersonModalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = salespersonCreateApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
getMyInfoApi.value();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
getMyInfoApi.value = isOpen ? modalApi.getData()?.getMyInfo : null;
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}推广员`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,142 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Avatar, Button, Image } from 'ant-design-vue';
import html2canvas from 'html2canvas';
import { getDoctorInfoByStoreApi } from '#/views/doctor/doctor/api';
const data = ref({});
const qrCodeUrl = ref('');
const htmlToImage = ref('');
const previewAddress = ref('');
const goodAt = ref('');
const doctorInfo = ref();
const doctorId = ref(0);
const storeId = ref(0);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
footer: false,
showCancelButton: true,
showConfirmButton: true,
confirmText: '保存医生卡片图片',
cancelText: '保存医生二维码图片',
draggable: true,
onCancel() {
doctorInfo.value = null;
modalApi.close();
},
onConfirm: async () => {
doctorInfo.value = null;
modalApi.close();
},
onOpenChange(isOpen: boolean) {
modalApi.setState({
loading: true,
});
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
console.log('values', values);
data.value = values;
// getDoctorInfoByStore();
modalApi.setState({
loading: false,
});
}
}
},
});
/**
* 下载二维码
* @param name
* @param isQr
*/
const downloadQRCode = async (name: string, isQr = false) => {
const element = isQr ? qrCodeUrl.value : htmlToImage.value;
html2canvas(element, {
useCORS: true,
allowTaint: true,
logging: false,
}).then((canvas) => {
// 创建a标签下载
const link = document.createElement('a'); // 创建a标签
link.href = canvas.toDataURL(); // 是canvas对象的一种方法用于将canvas对象转换为base64位编码
link.setAttribute('download', `${doctorInfo.value.doctor.name}${name}.png`); // 利用了a标签的download 来下载 canvas图片
link.style.display = 'none'; // 将图片隐藏起来
document.body.append(link); // 插入到其中
link.click();
});
};
</script>
<template>
<Modal title="推广二维码" class="w-[50%]">
<template #footer>
<Button type="primary" @click="downloadQRCode('二维码卡片下载')">
二维码卡片下载
</Button>
<Button type="primary" @click="downloadQRCode('二维码下载', true)">
二维码下载
</Button>
</template>
<div class="qrBox" style="text-align: center">
<div ref="htmlToImage" class="qrModal">
<div id="qrcode" ref="qrCodeUrl" class="qrcode">
<!-- <Avatar :src="doctorInfo.doctor.avatar" />-->
<div class="qrTitle">
{{ data.qr_code.store.name }}
</div>
<div class="qrAddress">
<span class="addressTitle">诊所地址</span>{{ data.qr_code.store.position }}
</div>
<Image :preview="false" :src="data.qr_code.qr_code" class="mt-3" height="30" width="30" />
<div class="qrAddress">
<span class="addressTitle">推广员</span>{{ data.nick_name }}
</div>
</div>
</div>
</div>
</Modal>
</template>
<style lang="scss" scoped>
.qrModal {
// 宽度适应文本长度
margin: 0 auto;
text-align: center;
background: radial-gradient(
circle at center,
rgba(173, 216, 230, 0.8),
rgba(135, 206, 235, 0.8),
rgba(100, 149, 237, 0.8)
);
padding: 20px 30px;
.qrTitle {
color: #333;
}
.qrAddress {
font-size: 12px;
margin-top: 10px;
color: #333;
font-weight: bold;
}
.addressTitle {
color: #3a8ee6;
}
.qrcode {
width: fit-content;
//width: 50%;
margin: 0 auto;
text-align: center;
padding: 20px 30px;
background-color: #fff;
border-radius: 20px;
}
}
</style>

View File

@@ -24,3 +24,45 @@ export const modalFormProps: VbenFormProps = {
],
showDefaultActions: false,
};
export const salespersonModalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'Input',
fieldName: 'phone',
label: '手机号',
rules: 'required',
componentProps: {
minLength: 11,
},
},
{
component: 'Input',
fieldName: 'nick_name',
label: '名称',
rules: 'required',
componentProps: {
minLength: 11,
},
},
// {
// component: 'UploadImage',
// fieldName: 'url',
// label: '图片',
// rules: 'required',
// componentProps: {
// maxCount: 5,
// },
// },
],
showDefaultActions: false,
};

View File

@@ -27,6 +27,7 @@ import {
} from '#/views/business/store/settings/api';
import UploadModal from './components/uploadModal.vue';
import SalespersonCard from "#/views/business/store/settings/components/SalespersonCard.vue";
// 定义类型
interface TabBarItem {
@@ -89,6 +90,7 @@ const tabBar: TabBarItem[] = [
{ value: 3, label: '诊所二维码' },
{ value: 4, label: '我的医生团队' },
{ value: 5, label: '轮播图' },
{ value: 6, label: '推广员' },
];
// 格式化日期
@@ -476,6 +478,10 @@ const deleteNavItem = (id: number) => {
</Card>
</div>
</Card>
<Card v-else-if="activeTabBar === 6" title="推广员">
<SalespersonCard :data="data" />
</Card>
</Page>
</template>
<style lang="scss" scoped>

View File

@@ -94,3 +94,11 @@ export async function importUpdatePriceApi(data: Record<string, any>) {
export async function syncDrugApi(data: Record<string, any>) {
return requestClient.get(`${prefix}sync-drug`, data);
}
/**
* 同步总仓库价格api
* @param data
*/
export async function syncDrugPriceApi(data: Record<string, any>) {
return requestClient.get(`${prefix}sync-drug-price`, data);
}

View File

@@ -33,7 +33,8 @@ export const gridOptions: VxeGridProps<RowType> = {
slots: { default: 'image' },
width: 130,
},
{ field: 'drug.drug_store_drug.market_price', title: '供货价' },
// { field: 'drug.drug_store_drug.market_price', title: '供货价' },
{ field: 'buy_price', title: '供货价' },
{ field: 'drug.drug_store_drug.price', title: '建议售价' },
{ field: 'price', title: '售价' },
{ field: 'status', title: '状态', slots: { default: 'status' } },

View File

@@ -13,7 +13,7 @@ import { downloadByData } from '#/util/tool';
import {
deleteWarehouseDrugManagementStore,
exportWarehouseDrugManagementStoreApi, syncDrugApi,
exportWarehouseDrugManagementStoreApi, syncDrugApi, syncDrugPriceApi,
updateWarehouseDrugManagementStoreStatusApi
} from './api';
import FormModalDemo from './components/modal.vue';
@@ -106,6 +106,16 @@ const updateStatus = (id) => {
gridApi.reload();
});
}
const syncDrugPrice = () => {
gridApi.setLoading(true);
syncDrugPriceApi().then((res) => {
console.log(res, '同步结果');
message.success('同步成功!');
gridApi.setLoading(false);
gridApi.reload();
});
}
</script>
<template>
@@ -115,6 +125,7 @@ const updateStatus = (id) => {
<Grid>
<template #toolbar-buttons>
<TableAction
:flex="false"
:actions="[
{
label: '批量修改价格',
@@ -126,10 +137,17 @@ const updateStatus = (id) => {
{
label: '更新总仓库商品(只更新未同步的商品)',
type: 'primary',
icon: 'ix:export-check',
icon: 'material-symbols:sync-alt',
// auth: ['超级西(中成)药', 'sys:user:save'],
onClick: syncDrugs,
},
{
label: '同步总仓库的价格',
type: 'primary',
icon: 'material-symbols:sync-alt',
// auth: ['超级西(中成)药', 'sys:user:save'],
onClick: syncDrugPrice,
},
]"
:drop-down-actions="[
{

View File

@@ -1,4 +1,5 @@
import { requestClient } from '#/api/request';
const prefix = 'doctor-reception/';
/**
* 分页查询用户列表
@@ -18,7 +19,7 @@ export async function getPatientItem(id: number) {
/**
* 获取商品列表
* @param id
* @param data
*/
export async function getProductListDoctorReception(data: any) {
return requestClient.get<any>(`${prefix}product-list`, { params: data });
@@ -26,7 +27,7 @@ export async function getProductListDoctorReception(data: any) {
/**
* 获取中药规格
* @param id
* @param data
*/
export async function getProcessRuleList(data: any) {
return requestClient.get<any>(`${prefix}process-rule-list`, { params: data });
@@ -49,9 +50,10 @@ export async function getUseWayList() {
/**
* 获取疾病列表
*/
export async function getDiseaseList(name:string) {
return requestClient.get<any>(`${prefix}disease-list`, { params: {
name
export async function getDiseaseList(name: string) {
return requestClient.get<any>(`${prefix}disease-list`, {
params: {
name,
},
});
}
@@ -73,15 +75,15 @@ export async function getMyStoreListApi() {
/**
* 接诊
*/
export async function receptionApi(id) {
return requestClient.post<any>(`${prefix}reception`, {id:id});
export async function receptionApi(id: any) {
return requestClient.post<any>(`${prefix}reception`, { id });
}
/**
* 接诊
*/
export async function endOfDiagnosisApi(id) {
return requestClient.post<any>(`${prefix}end-of-diagnosis`, {id:id});
export async function endOfDiagnosisApi(id: any) {
return requestClient.post<any>(`${prefix}end-of-diagnosis`, { id });
}
/**
@@ -122,11 +124,22 @@ export async function refuseReceptionApi(data: any) {
return requestClient.post<any>(`${prefix}refuse-reception`, data);
}
/**
* 检查是否有药物相冲
* @param data
*/
export async function checkChineseMedicineConflictApi(data: any) {
return requestClient.post<any>(`${prefix}check-chinese-medicine-conflict`, data);
return requestClient.post<any>(
`${prefix}check-chinese-medicine-conflict`,
data,
);
}
/**
* 获取监管平台所需的数据
*/
export async function getTraditionalChineseMedicineAllApi() {
return requestClient.get<any>(
`${prefix}get-traditional-chinese-medicine-all`,
);
}