fix: 常用方、患者管理
This commit is contained in:
@@ -50,6 +50,13 @@ export async function saveNavUrlApi(data) {
|
||||
export async function deleteNavApi(data) {
|
||||
return requestClient.post<any>(`${prefix}delete-nav`, data);
|
||||
}
|
||||
/**
|
||||
* 更新轮播图排序
|
||||
* @param ids 按顺序排列的轮播图ID数组
|
||||
*/
|
||||
export async function updateNavSortApi(ids: number[]) {
|
||||
return requestClient.post<any>(`${prefix}update-nav-sort`, { ids });
|
||||
}
|
||||
|
||||
/*
|
||||
----------------------------推广员---------------------------------
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { EllipsisText, Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { DeleteOutlined, EyeOutlined, HolderOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
Image,
|
||||
ImagePreviewGroup,
|
||||
InputNumber,
|
||||
message,
|
||||
notification,
|
||||
Popconfirm,
|
||||
RadioButton,
|
||||
RadioGroup,
|
||||
Tag,
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
deleteNavApi,
|
||||
getStoreInfoApi,
|
||||
saveZSalePercentApi,
|
||||
updateNavSortApi,
|
||||
} from '#/views/business/store/settings/api';
|
||||
|
||||
import UploadModal from './components/uploadModal.vue';
|
||||
@@ -37,6 +40,7 @@ interface TabBarItem {
|
||||
|
||||
interface StoreInfo {
|
||||
name: string;
|
||||
type: number; // 0=诊所, 1=药店
|
||||
star?: any;
|
||||
title: { name: string };
|
||||
contact: string;
|
||||
@@ -82,24 +86,54 @@ const openDoctorQrCode = (doctorId: number, isNotQr = false) => {
|
||||
DoctorQrCodePreviewModalApi.open();
|
||||
};
|
||||
|
||||
// 映射数据
|
||||
const activeTabBar = ref(1);
|
||||
const tabBar: TabBarItem[] = [
|
||||
{ value: 1, label: '诊所资料' },
|
||||
{ value: 2, label: '商品价格' },
|
||||
{ value: 3, label: '诊所二维码' },
|
||||
{ value: 4, label: '我的医生团队' },
|
||||
{ value: 5, label: '轮播图' },
|
||||
{ value: 6, label: '推广员' },
|
||||
];
|
||||
// 诊所/药店数据
|
||||
const data = ref<null | StoreInfo>(null);
|
||||
|
||||
// Tab状态持久化
|
||||
const TAB_STORAGE_KEY = 'xk_store_settings_tab';
|
||||
const savedTab = localStorage.getItem(TAB_STORAGE_KEY);
|
||||
const activeTabBar = ref(savedTab ? Number(savedTab) : 1);
|
||||
|
||||
// 判断是否是药店 (type=1为药店, type=0为诊所)
|
||||
const isPharmacy = computed(() => data.value?.type === 1);
|
||||
|
||||
// 根据类型动态生成tab选项
|
||||
const tabBar = computed<TabBarItem[]>(() => {
|
||||
if (isPharmacy.value) {
|
||||
// 药店只显示:药店资料、药店二维码、轮播图、推广员
|
||||
return [
|
||||
{ value: 1, label: '药店资料' },
|
||||
{ value: 3, label: '药店二维码' },
|
||||
{ value: 5, label: '轮播图' },
|
||||
{ value: 6, label: '推广员' },
|
||||
];
|
||||
}
|
||||
// 诊所显示全部:诊所资料、商品价格、诊所二维码、我的医生团队、轮播图、推广员
|
||||
return [
|
||||
{ value: 1, label: '诊所资料' },
|
||||
{ value: 2, label: '商品价格' },
|
||||
{ value: 3, label: '诊所二维码' },
|
||||
{ value: 4, label: '我的医生团队' },
|
||||
{ value: 5, label: '轮播图' },
|
||||
{ value: 6, label: '推广员' },
|
||||
];
|
||||
});
|
||||
|
||||
// 监听tabBar变化,验证当前tab是否有效(药店和诊所tab选项不同)
|
||||
watch(tabBar, (newTabBar) => {
|
||||
const validValues = newTabBar.map(item => item.value);
|
||||
if (!validValues.includes(activeTabBar.value)) {
|
||||
// 如果当前tab不在可用列表中,切换到第一个tab
|
||||
activeTabBar.value = validValues[0] || 1;
|
||||
localStorage.setItem(TAB_STORAGE_KEY, String(activeTabBar.value));
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString();
|
||||
};
|
||||
|
||||
const data = ref<null | StoreInfo>(null);
|
||||
|
||||
/**
|
||||
* 获取当前登录诊所信息
|
||||
*/
|
||||
@@ -108,12 +142,14 @@ const getMyInfo = () => {
|
||||
data.value = res as StoreInfo;
|
||||
});
|
||||
};
|
||||
getMyInfo();
|
||||
|
||||
/**
|
||||
* 医嘱列表
|
||||
* Tab切换处理
|
||||
*/
|
||||
const tabBarChange = () => {
|
||||
// 保存当前tab到localStorage
|
||||
localStorage.setItem(TAB_STORAGE_KEY, String(activeTabBar.value));
|
||||
|
||||
switch (activeTabBar.value) {
|
||||
case 1: {
|
||||
getMyInfo();
|
||||
@@ -124,6 +160,10 @@ const tabBarChange = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
// 初始化时加载基础数据(所有tab都需要data)
|
||||
onMounted(() => {
|
||||
getMyInfo();
|
||||
});
|
||||
|
||||
const qrCodeUrl = ref<HTMLElement | null>(null);
|
||||
const htmlToImage = ref<HTMLElement | null>(null);
|
||||
@@ -178,6 +218,83 @@ const deleteNavItem = (id: number) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 拖拽排序相关
|
||||
const draggedIndex = ref(-1);
|
||||
const dragOverIndex = ref(-1);
|
||||
|
||||
const onDragStart = (event: DragEvent, index: number) => {
|
||||
draggedIndex.value = index;
|
||||
dragOverIndex.value = -1;
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnter = (index: number) => {
|
||||
if (draggedIndex.value !== -1 && draggedIndex.value !== index) {
|
||||
dragOverIndex.value = index;
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
}
|
||||
};
|
||||
|
||||
const onDrop = (event: DragEvent, targetIndex: number) => {
|
||||
event.preventDefault();
|
||||
if (!data.value || draggedIndex.value === -1 || draggedIndex.value === targetIndex) {
|
||||
draggedIndex.value = -1;
|
||||
dragOverIndex.value = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
const list = [...data.value.nav];
|
||||
const itemToMove = list[draggedIndex.value];
|
||||
list.splice(draggedIndex.value, 1);
|
||||
|
||||
let finalIndex = targetIndex;
|
||||
if (draggedIndex.value < targetIndex) {
|
||||
finalIndex = targetIndex - 1;
|
||||
}
|
||||
list.splice(finalIndex, 0, itemToMove);
|
||||
|
||||
data.value.nav = list;
|
||||
draggedIndex.value = -1;
|
||||
dragOverIndex.value = -1;
|
||||
|
||||
// 保存排序到后端
|
||||
saveNavSort();
|
||||
};
|
||||
|
||||
const onDragEnd = () => {
|
||||
draggedIndex.value = -1;
|
||||
dragOverIndex.value = -1;
|
||||
};
|
||||
|
||||
// 保存轮播图排序
|
||||
const saveNavSort = () => {
|
||||
if (!data.value) return;
|
||||
const ids = data.value.nav.map((item) => item.id);
|
||||
updateNavSortApi(ids).then(() => {
|
||||
notification.success({
|
||||
message: '排序保存成功',
|
||||
duration: 2,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 图片预览相关
|
||||
const previewVisible = ref(false);
|
||||
const previewCurrent = ref(0);
|
||||
|
||||
const openPreview = (index: number) => {
|
||||
previewCurrent.value = index;
|
||||
previewVisible.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -199,7 +316,7 @@ const deleteNavItem = (id: number) => {
|
||||
</RadioButton>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<Card v-if="activeTabBar === 1" title="诊所资料">
|
||||
<Card v-if="activeTabBar === 1" :title="isPharmacy ? '药店资料' : '诊所资料'">
|
||||
<!-- 头部信息 -->
|
||||
<div class="mb-8 flex flex-col gap-6 md:flex-row md:gap-8">
|
||||
<div class="flex-1">
|
||||
@@ -307,7 +424,7 @@ const deleteNavItem = (id: number) => {
|
||||
</InputNumber>
|
||||
<Button type="primary" @click="saveZSalePercent">保存</Button>
|
||||
</Card>
|
||||
<Card v-else-if="activeTabBar === 3" title="诊所二维码">
|
||||
<Card v-else-if="activeTabBar === 3" :title="isPharmacy ? '药店二维码' : '诊所二维码'">
|
||||
<template #extra>
|
||||
<Button
|
||||
style="margin: 20px auto"
|
||||
@@ -335,7 +452,7 @@ const deleteNavItem = (id: number) => {
|
||||
width="30"
|
||||
/>
|
||||
<div class="qrAddress">
|
||||
<span class="addressTitle">诊所地址:</span>{{ data.province.name
|
||||
<span class="addressTitle">{{ isPharmacy ? '药店地址:' : '诊所地址:' }}</span>{{ data.province.name
|
||||
}}{{ data.city.name }}{{ data.position }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -444,39 +561,111 @@ const deleteNavItem = (id: number) => {
|
||||
</Card>
|
||||
<Card v-else-if="activeTabBar === 5" title="轮播图">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<span>最多可添加5张轮播图</span>
|
||||
<div>
|
||||
<span>最多可添加5张轮播图</span>
|
||||
<span class="ml-4 text-gray-400 text-sm">提示:拖拽卡片可调整排序</span>
|
||||
</div>
|
||||
<Button v-if="data.nav.length < 5" type="primary" @click="openUpload">
|
||||
上传新图片
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap gap-4">
|
||||
<Card
|
||||
v-for="(item, index) in data.nav"
|
||||
:key="item.id"
|
||||
class="relative"
|
||||
style="width: 240px"
|
||||
>
|
||||
<div class="group relative">
|
||||
<Image :src="item.pic" class="h-40 w-full rounded object-cover" />
|
||||
<!-- 图片预览组 -->
|
||||
<ImagePreviewGroup
|
||||
:preview="{
|
||||
visible: previewVisible,
|
||||
current: previewCurrent,
|
||||
onVisibleChange: (vis: boolean) => { previewVisible = vis; },
|
||||
}"
|
||||
>
|
||||
<div class="mt-4 flex flex-wrap gap-4">
|
||||
<template v-for="(item, index) in data.nav" :key="item.id">
|
||||
<!-- 拖拽占位符 -->
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
v-if="dragOverIndex === index && draggedIndex !== -1 && draggedIndex !== index"
|
||||
class="relative rounded-lg border-2 border-dashed border-blue-500 bg-blue-50/50 dark:bg-blue-900/30"
|
||||
style="width: 240px; height: 200px"
|
||||
@dragenter="onDragEnter(index)"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop($event, index)"
|
||||
>
|
||||
<Button
|
||||
class="shadow-lg"
|
||||
danger
|
||||
shape="circle"
|
||||
size="large"
|
||||
type="primary"
|
||||
@click.stop="deleteNavItem(item.id)"
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
<div class="flex h-full items-center justify-center text-blue-500">
|
||||
释放以插入此处
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-center text-sm text-gray-500">点击图片删除</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 轮播图卡片 -->
|
||||
<Card
|
||||
:class="[
|
||||
'relative cursor-move transition-all duration-300',
|
||||
{
|
||||
'opacity-30': draggedIndex === index,
|
||||
'translate-y-1': dragOverIndex === index && draggedIndex !== -1 && draggedIndex !== index,
|
||||
}
|
||||
]"
|
||||
style="width: 240px"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart($event, index)"
|
||||
@dragenter="onDragEnter(index)"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop($event, index)"
|
||||
@dragend="onDragEnd"
|
||||
>
|
||||
<!-- 序号标识 -->
|
||||
<div class="absolute left-2 top-2 z-10 flex h-6 w-6 items-center justify-center rounded-full bg-blue-500 text-xs text-white">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<!-- 拖拽手柄 -->
|
||||
<div class="absolute right-2 top-2 z-10 flex h-6 w-6 items-center justify-center rounded bg-gray-800/50 text-white">
|
||||
<HolderOutlined />
|
||||
</div>
|
||||
|
||||
<div class="group relative">
|
||||
<Image
|
||||
:src="item.pic"
|
||||
:preview="false"
|
||||
class="h-40 w-full rounded object-cover pointer-events-none"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center gap-3 bg-black bg-opacity-50 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
>
|
||||
<!-- 预览按钮 -->
|
||||
<Button
|
||||
class="shadow-lg"
|
||||
shape="circle"
|
||||
size="large"
|
||||
type="primary"
|
||||
@click.stop="openPreview(index)"
|
||||
>
|
||||
<EyeOutlined />
|
||||
</Button>
|
||||
<!-- 删除按钮(带确认框) -->
|
||||
<Popconfirm
|
||||
title="确定要删除这张轮播图吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="deleteNavItem(item.id)"
|
||||
>
|
||||
<Button
|
||||
class="shadow-lg"
|
||||
danger
|
||||
shape="circle"
|
||||
size="large"
|
||||
type="primary"
|
||||
@click.stop
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
<!-- 隐藏的Image用于预览组 -->
|
||||
<Image :src="item.pic" class="hidden" />
|
||||
</div>
|
||||
<div class="mt-2 text-center text-sm text-gray-500">拖拽排序 | 悬停操作</div>
|
||||
</Card>
|
||||
</template>
|
||||
</div>
|
||||
</ImagePreviewGroup>
|
||||
</Card>
|
||||
|
||||
<Card v-else-if="activeTabBar === 6" title="推广员">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @author 系统
|
||||
* @date 2024
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EllipsisText, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
@@ -50,7 +50,11 @@ const userInfoStore = useUserStore();
|
||||
|
||||
// 映射数据(示例)
|
||||
const identityMap = { 1: '中医', 2: '西医' };
|
||||
const activeTabBar = ref(1);
|
||||
|
||||
// Tab状态持久化
|
||||
const TAB_STORAGE_KEY = 'xk_doctor_settings_tab';
|
||||
const savedTab = localStorage.getItem(TAB_STORAGE_KEY);
|
||||
const activeTabBar = ref(savedTab ? Number(savedTab) : 1);
|
||||
|
||||
/**
|
||||
* Tab栏配置
|
||||
@@ -91,7 +95,6 @@ const getMyInfo = () => {
|
||||
data.value = res;
|
||||
});
|
||||
};
|
||||
getMyInfo();
|
||||
/**
|
||||
* 保存挂号服务
|
||||
*/
|
||||
@@ -244,6 +247,9 @@ const doctorOrder = ref([]);
|
||||
* @description 根据当前选中的Tab加载对应的数据
|
||||
*/
|
||||
const tabBarChange = () => {
|
||||
// 保存当前tab到localStorage
|
||||
localStorage.setItem(TAB_STORAGE_KEY, String(activeTabBar.value));
|
||||
|
||||
switch (activeTabBar.value) {
|
||||
case 1: {
|
||||
// 个人资料
|
||||
@@ -267,6 +273,14 @@ const tabBarChange = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
// 初始化时加载基础数据和当前tab对应的数据
|
||||
onMounted(() => {
|
||||
getMyInfo(); // 基础数据(个人资料)始终需要
|
||||
// 如果当前tab不是1,还需要加载对应tab的数据
|
||||
if (activeTabBar.value !== 1) {
|
||||
tabBarChange();
|
||||
}
|
||||
});
|
||||
|
||||
const doctorOrderContent = ref('');
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
import { getStoreOption } from '#/views/system/store/api';
|
||||
|
||||
export const modalFormProps: VbenFormProps = {
|
||||
wrapperClass: 'grid-cols-12', // 24栅格,
|
||||
commonConfig: {
|
||||
@@ -23,6 +25,33 @@ export const modalFormProps: VbenFormProps = {
|
||||
triggerFields: ['id'],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择绑定店铺(留空为平台通用)',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
filterOption: (input: string, option: any) => {
|
||||
return option.label?.toLowerCase().includes(input.toLowerCase());
|
||||
},
|
||||
api: async () => {
|
||||
const res = await getStoreOption({});
|
||||
// 添加"平台通用"选项
|
||||
const options = [
|
||||
{ label: '平台通用', value: 0 },
|
||||
...(res || []).map((item: any) => ({
|
||||
label: item.name || item.label,
|
||||
value: item.id || item.value,
|
||||
})),
|
||||
];
|
||||
return options;
|
||||
},
|
||||
},
|
||||
fieldName: 'store_id',
|
||||
label: '绑定店铺',
|
||||
formItemClass: 'col-span-12',
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
component: 'Avatar',
|
||||
fieldName: 'drug_license_image',
|
||||
@@ -92,21 +121,6 @@ export const modalFormProps: VbenFormProps = {
|
||||
formItemClass: 'col-span-6',
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
component: 'VbenInputNumber',
|
||||
componentProps: {
|
||||
placeholder: '门店ID(留空表示平台通用)',
|
||||
min: 0,
|
||||
},
|
||||
dependencies: {
|
||||
show: false,
|
||||
triggerFields: ['id'],
|
||||
},
|
||||
fieldName: 'store_id',
|
||||
label: '门店ID',
|
||||
formItemClass: 'col-span-6',
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
};
|
||||
|
||||
@@ -74,7 +74,13 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
slots: { default: 'internet_drug_license_image' },
|
||||
width: 180,
|
||||
},
|
||||
{ field: 'store_id', align: 'left', title: '门店ID', width: 120 },
|
||||
{
|
||||
field: 'store_name',
|
||||
align: 'left',
|
||||
title: '绑定店铺',
|
||||
width: 150,
|
||||
slots: { default: 'store_name' },
|
||||
},
|
||||
{ field: 'sort_order', align: 'left', title: '排序', width: 100 },
|
||||
{
|
||||
field: 'status_txt',
|
||||
|
||||
@@ -207,6 +207,10 @@ const deleteQualificationsApi = (row: any) => {
|
||||
/>
|
||||
<span v-else>暂无</span>
|
||||
</template>
|
||||
<template #store_name="{ row }">
|
||||
<span v-if="row.store_id === 0 || row.store_id === null">平台通用</span>
|
||||
<span v-else>{{ row.store_name || `店铺ID: ${row.store_id}` }}</span>
|
||||
</template>
|
||||
<template #toolbar-tools></template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
|
||||
Reference in New Issue
Block a user