1. 特色方功能迭代(固定价格),固定价格目前统一使用药品id(-102),后期会考虑新增特色方的时候自动添加一个特色方药品
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
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
Close stale issues / stale (push) Has been cancelled
2. 退款的时候可以退回分成
This commit is contained in:
10
.cursor/rules/Code-Standards.md
Normal file
10
.cursor/rules/Code-Standards.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则
|
||||||
|
2. 注意不要生成太多的空行,上一部分代码和下一部分代码中间的空行不要大于2行
|
||||||
|
3. 有封装好的方法、组件需要复用,不要重复造轮子
|
||||||
|
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面(记得使用v-if而不是v-show)
|
||||||
|
5. 数据库的(created_at、updated_at、deleted_at)统一使用时间戳,不要使用字符串,并且我在查询器中一级格式化成字符串了,无需再次格式化
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Avatar, Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
row: Record<string, any>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
openDoctor: [row: Record<string, any>];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
/** 默认头像路径 */
|
||||||
|
const defaultAvatar = '/img/user-default-avatar.png';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析下单微信用户头像,空值回退默认图
|
||||||
|
*/
|
||||||
|
function getUserAvatarSrc(row: Record<string, any>) {
|
||||||
|
const raw = String(row.user?.avatarurl ?? '').trim();
|
||||||
|
if (!raw) return defaultAvatar;
|
||||||
|
return resolveAvatarUrl(raw) || defaultAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析医生头像,空值回退默认图
|
||||||
|
*/
|
||||||
|
function getDoctorAvatarSrc(row: Record<string, any>) {
|
||||||
|
const raw = String(row.doctor?.avatar ?? '').trim();
|
||||||
|
if (!raw) return defaultAvatar;
|
||||||
|
return resolveAvatarUrl(raw) || defaultAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化就诊人年龄副文本
|
||||||
|
*/
|
||||||
|
function formatPatientAge(row: Record<string, any>) {
|
||||||
|
const age = row.patient_age;
|
||||||
|
if (age == null || age === '') return '';
|
||||||
|
return `${age}岁`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="order-user-info">
|
||||||
|
<!-- 行1:下单微信用户 -->
|
||||||
|
<div class="order-user-info__row">
|
||||||
|
<Avatar
|
||||||
|
:size="24"
|
||||||
|
:src="getUserAvatarSrc(row)"
|
||||||
|
class="shrink-0"
|
||||||
|
/>
|
||||||
|
<div class="order-user-info__text min-w-0">
|
||||||
|
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
||||||
|
下单用户:
|
||||||
|
</span>
|
||||||
|
<span class="truncate text-xs text-gray-700 dark:text-slate-200">
|
||||||
|
{{ row.user?.nickname || '—' }}
|
||||||
|
</span>
|
||||||
|
<div class="text-[11px] text-gray-500 dark:text-slate-400">
|
||||||
|
ID:{{ row.user?.id ?? '—' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 行2:开方医生(可点击查看档案) -->
|
||||||
|
<div class="order-user-info__row">
|
||||||
|
<Avatar
|
||||||
|
:size="24"
|
||||||
|
:src="getDoctorAvatarSrc(row)"
|
||||||
|
class="shrink-0"
|
||||||
|
/>
|
||||||
|
<div class="order-user-info__text min-w-0" >
|
||||||
|
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
||||||
|
医生:
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
v-if="row.doctor?.name"
|
||||||
|
class="order-user-info__doctor-btn !h-auto !px-0 !py-0 dark:!text-blue-400"
|
||||||
|
type="link"
|
||||||
|
@click="emit('openDoctor', row)"
|
||||||
|
>
|
||||||
|
{{ row.doctor.name }}
|
||||||
|
</Button>
|
||||||
|
<!-- <template v-if="row.doctor?.name">-->
|
||||||
|
<!-- <Button-->
|
||||||
|
<!-- v-if="row.doctor?.name"-->
|
||||||
|
<!-- class="order-user-info__doctor-btn !h-auto !px-0 !py-0 dark:!text-blue-400"-->
|
||||||
|
<!-- type="link"-->
|
||||||
|
<!-- @click="emit('openDoctor', row)"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- {{ row.doctor.name }}-->
|
||||||
|
<!-- </Button>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<div v-else class="text-xs text-gray-700 dark:text-slate-200">—</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 行3:就诊人(与上两行保持 Avatar + 文本区结构) -->
|
||||||
|
<div class="order-user-info__row">
|
||||||
|
<Avatar :size="24" :src="defaultAvatar" class="shrink-0" />
|
||||||
|
<div class="order-user-info__text min-w-0">
|
||||||
|
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
||||||
|
就诊人:
|
||||||
|
</span>
|
||||||
|
<span class="truncate text-xs text-gray-700 dark:text-slate-200">
|
||||||
|
{{ row.patient || '—' }}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
v-if="formatPatientAge(row)"
|
||||||
|
class="text-[11px] text-gray-500 dark:text-slate-400"
|
||||||
|
>
|
||||||
|
{{ formatPatientAge(row) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.order-user-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-user-info__row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-user-info__text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-user-info__doctor-btn {
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
//display: block;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,10 +26,10 @@ export const gridOptions: VxeGridProps<RowType> = {
|
|||||||
slots: { default: 'order-store' },
|
slots: { default: 'order-store' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'user.avatarUrl',
|
field: 'order_user_info',
|
||||||
title: '下单用户信息',
|
title: '用户/医生/就诊人',
|
||||||
slots: { default: 'avatar' },
|
slots: { default: 'order-user-info' },
|
||||||
width: 100,
|
width: 210,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'express_info',
|
field: 'express_info',
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ import { getOrderPriceAdjustConfig } from '#/api/order/priceAdjust';
|
|||||||
import type { QuickDiscountOption } from '#/utils/pricePercentAdjust';
|
import type { QuickDiscountOption } from '#/utils/pricePercentAdjust';
|
||||||
import { normalizeQuickOptions } from '#/utils/pricePercentAdjust';
|
import { normalizeQuickOptions } from '#/utils/pricePercentAdjust';
|
||||||
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||||||
|
import DoctorCardModal from '#/views/doctor/doctor/components/DoctorCardModal.vue';
|
||||||
|
|
||||||
import DetailModal from './components/detail.vue';
|
import DetailModal from './components/detail.vue';
|
||||||
|
import OrderUserInfoCell from './components/cells/OrderUserInfoCell.vue';
|
||||||
import FormModalDemo from './components/modal.vue';
|
import FormModalDemo from './components/modal.vue';
|
||||||
import ProductOrderExportModal from './components/ProductOrderExportModal.vue';
|
import ProductOrderExportModal from './components/ProductOrderExportModal.vue';
|
||||||
import Refund from './components/refund.vue';
|
import Refund from './components/refund.vue';
|
||||||
@@ -198,6 +200,25 @@ const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
|||||||
connectedComponent: PrescriptionDetail,
|
connectedComponent: PrescriptionDetail,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [DoctorCardModals, DoctorCardModalApi] = useVbenModal({
|
||||||
|
connectedComponent: DoctorCardModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 从订单列表以只读模式打开医生档案 */
|
||||||
|
function showOrderDoctorCard(row: Record<string, any>) {
|
||||||
|
const doctor = row.doctor;
|
||||||
|
if (!doctor?.id && !row.su_id && !doctor?.su_id) {
|
||||||
|
message.warning('该订单无关联医生');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DoctorCardModalApi.setData({
|
||||||
|
id: doctor?.id,
|
||||||
|
su_id: row.su_id ?? doctor?.su_id,
|
||||||
|
readonly: true,
|
||||||
|
});
|
||||||
|
DoctorCardModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
const [TraceDrawer, traceDrawerApi] = useVbenDrawer({
|
const [TraceDrawer, traceDrawerApi] = useVbenDrawer({
|
||||||
connectedComponent: OrderTraceDrawer,
|
connectedComponent: OrderTraceDrawer,
|
||||||
});
|
});
|
||||||
@@ -501,6 +522,7 @@ const openOrderAmountVerify = () => {
|
|||||||
</Table>
|
</Table>
|
||||||
</AntdModal>
|
</AntdModal>
|
||||||
<RefundModal />
|
<RefundModal />
|
||||||
|
<DoctorCardModals />
|
||||||
<ExportModal />
|
<ExportModal />
|
||||||
<PrescriptionDetailModal />
|
<PrescriptionDetailModal />
|
||||||
<TraceDrawer />
|
<TraceDrawer />
|
||||||
@@ -547,10 +569,8 @@ const openOrderAmountVerify = () => {
|
|||||||
</template>
|
</template>
|
||||||
</TableAction>
|
</TableAction>
|
||||||
</template>
|
</template>
|
||||||
<template #avatar="{ row }">
|
<template #order-user-info="{ row }">
|
||||||
<Image :src="row.user.avatarurl || '/img/user-default-avatar.png'" />
|
<OrderUserInfoCell :row="row" @open-doctor="showOrderDoctorCard" />
|
||||||
{{ row.user.nickname }}
|
|
||||||
<div>ID:{{ row.user.id }}</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #order-store="{ row }">
|
<template #order-store="{ row }">
|
||||||
<div class="leading-snug">
|
<div class="leading-snug">
|
||||||
|
|||||||
@@ -66,6 +66,16 @@ export async function updateDoctorSubjectsBindApi(data: Record<string, any>) {
|
|||||||
return requestClient.post<any>(`${prefix}update-subjects-bind`, data);
|
return requestClient.post<any>(`${prefix}update-subjects-bind`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** doctor_id 为 su_id,更新科室绑定 */
|
||||||
|
export async function updateDoctorDepartBindApi(data: Record<string, any>) {
|
||||||
|
return requestClient.post<any>(`${prefix}update-depart-bind`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** doctor_id 为 su_id,更新职称绑定 */
|
||||||
|
export async function updateDoctorTitleBindApi(data: Record<string, any>) {
|
||||||
|
return requestClient.post<any>(`${prefix}update-title-bind`, data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增医生
|
* 新增医生
|
||||||
* @param data
|
* @param data
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message, Select } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDepartOptionApi,
|
||||||
|
updateDoctorDepartBindApi,
|
||||||
|
} from '#/views/doctor/doctor/api';
|
||||||
|
|
||||||
|
const gridApi = ref();
|
||||||
|
const data = ref<Record<string, any>>();
|
||||||
|
const optionData = ref<{ label: string; value: number }[]>([]);
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
draggable: true,
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onConfirm: async () => {
|
||||||
|
const suId = data.value?.su_id;
|
||||||
|
if (suId == null) {
|
||||||
|
message.error('缺少医生信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const departId =
|
||||||
|
data.value?.depart_id === undefined || data.value?.depart_id === null
|
||||||
|
? 0
|
||||||
|
: Number(data.value.depart_id);
|
||||||
|
await updateDoctorDepartBindApi({
|
||||||
|
doctor_id: suId,
|
||||||
|
depart_id: Number.isNaN(departId) ? 0 : departId,
|
||||||
|
});
|
||||||
|
modalApi.close();
|
||||||
|
message.success('保存成功');
|
||||||
|
gridApi.value?.reload?.();
|
||||||
|
gridApi.value?.query?.();
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
gridApi.value = modalApi.getData()?.gridApi ?? null;
|
||||||
|
const payload = modalApi.getData<Record<string, any>>();
|
||||||
|
const { values } = payload ?? {};
|
||||||
|
if (values) {
|
||||||
|
data.value = JSON.parse(JSON.stringify(values));
|
||||||
|
if (
|
||||||
|
data.value.depart_id === undefined ||
|
||||||
|
data.value.depart_id === null
|
||||||
|
) {
|
||||||
|
data.value.depart_id = undefined;
|
||||||
|
} else {
|
||||||
|
data.value.depart_id = Number(data.value.depart_id) || undefined;
|
||||||
|
}
|
||||||
|
loadDepartOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 加载科室下拉选项 */
|
||||||
|
const loadDepartOptions = () => {
|
||||||
|
getDepartOptionApi().then((res: any) => {
|
||||||
|
const list = Array.isArray(res) ? res : [];
|
||||||
|
optionData.value = list.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal class="w-[400px]" title="编辑科室">
|
||||||
|
<div v-if="data" class="py-4">
|
||||||
|
<Select
|
||||||
|
v-model:value="data.depart_id"
|
||||||
|
:options="optionData"
|
||||||
|
allow-clear
|
||||||
|
placeholder="请选择科室(清空表示未绑定)"
|
||||||
|
show-search
|
||||||
|
:filter-option="
|
||||||
|
(input: string, option: any) =>
|
||||||
|
String(option?.label ?? '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(input.trim().toLowerCase())
|
||||||
|
"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message, Select } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getDoctorTitleOptionApi } from '#/views/doctor/pharmacist/api';
|
||||||
|
import { updateDoctorTitleBindApi } from '#/views/doctor/doctor/api';
|
||||||
|
|
||||||
|
const gridApi = ref();
|
||||||
|
const data = ref<Record<string, any>>();
|
||||||
|
const optionData = ref<{ label: string; value: number }[]>([]);
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
draggable: true,
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onConfirm: async () => {
|
||||||
|
const suId = data.value?.su_id;
|
||||||
|
if (suId == null) {
|
||||||
|
message.error('缺少医生信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const titleId =
|
||||||
|
data.value?.title_id === undefined || data.value?.title_id === null
|
||||||
|
? 0
|
||||||
|
: Number(data.value.title_id);
|
||||||
|
await updateDoctorTitleBindApi({
|
||||||
|
doctor_id: suId,
|
||||||
|
title_id: Number.isNaN(titleId) ? 0 : titleId,
|
||||||
|
});
|
||||||
|
modalApi.close();
|
||||||
|
message.success('保存成功');
|
||||||
|
gridApi.value?.reload?.();
|
||||||
|
gridApi.value?.query?.();
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
gridApi.value = modalApi.getData()?.gridApi ?? null;
|
||||||
|
const payload = modalApi.getData<Record<string, any>>();
|
||||||
|
const { values } = payload ?? {};
|
||||||
|
if (values) {
|
||||||
|
data.value = JSON.parse(JSON.stringify(values));
|
||||||
|
if (
|
||||||
|
data.value.title_id === undefined ||
|
||||||
|
data.value.title_id === null
|
||||||
|
) {
|
||||||
|
data.value.title_id = undefined;
|
||||||
|
} else {
|
||||||
|
data.value.title_id = Number(data.value.title_id) || undefined;
|
||||||
|
}
|
||||||
|
loadTitleOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 加载职称下拉选项(医生 type=1) */
|
||||||
|
const loadTitleOptions = () => {
|
||||||
|
getDoctorTitleOptionApi().then((res: any) => {
|
||||||
|
const list = Array.isArray(res) ? res : [];
|
||||||
|
optionData.value = list.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal class="w-[400px]" title="编辑职称">
|
||||||
|
<div v-if="data" class="py-4">
|
||||||
|
<Select
|
||||||
|
v-model:value="data.title_id"
|
||||||
|
:options="optionData"
|
||||||
|
allow-clear
|
||||||
|
placeholder="请选择职称(清空表示未绑定)"
|
||||||
|
show-search
|
||||||
|
:filter-option="
|
||||||
|
(input: string, option: any) =>
|
||||||
|
String(option?.label ?? '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(input.trim().toLowerCase())
|
||||||
|
"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
@@ -45,6 +45,8 @@ const gridApi = ref<any>();
|
|||||||
const doctorId = ref(0);
|
const doctorId = ref(0);
|
||||||
const suId = ref(0);
|
const suId = ref(0);
|
||||||
const hideStoresTab = ref(false);
|
const hideStoresTab = ref(false);
|
||||||
|
/** 只读模式:从订单页等场景打开时禁止编辑与保存 */
|
||||||
|
const readonly = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const activeTab = ref('overview');
|
const activeTab = ref('overview');
|
||||||
const cardData = ref<any>(null);
|
const cardData = ref<any>(null);
|
||||||
@@ -67,6 +69,8 @@ const userStore = useUserStore();
|
|||||||
const canEditProfile = computed(() =>
|
const canEditProfile = computed(() =>
|
||||||
canEditDoctorServiceUser(userStore.userInfo),
|
canEditDoctorServiceUser(userStore.userInfo),
|
||||||
);
|
);
|
||||||
|
/** 是否允许编辑(权限 + 非只读) */
|
||||||
|
const canEdit = computed(() => canEditProfile.value && !readonly.value);
|
||||||
|
|
||||||
const savingServiceUser = ref(false);
|
const savingServiceUser = ref(false);
|
||||||
|
|
||||||
@@ -177,7 +181,7 @@ async function loadCard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveOverview() {
|
async function saveOverview() {
|
||||||
if (!canEditProfile.value) return;
|
if (!canEdit.value) return;
|
||||||
savingOverview.value = true;
|
savingOverview.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await updateDoctor({
|
const res = await updateDoctor({
|
||||||
@@ -198,7 +202,7 @@ async function saveOverview() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveServiceUser() {
|
async function saveServiceUser() {
|
||||||
if (!canEditProfile.value || !suId.value) return;
|
if (!canEdit.value || !suId.value) return;
|
||||||
savingServiceUser.value = true;
|
savingServiceUser.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await updateDoctorServiceUserApi({
|
const res = await updateDoctorServiceUserApi({
|
||||||
@@ -220,7 +224,7 @@ async function saveServiceUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveCredentials() {
|
async function saveCredentials() {
|
||||||
if (!canEditProfile.value) return;
|
if (!canEdit.value) return;
|
||||||
savingCredentials.value = true;
|
savingCredentials.value = true;
|
||||||
try {
|
try {
|
||||||
await updateDoctorCredentialsApi({
|
await updateDoctorCredentialsApi({
|
||||||
@@ -363,6 +367,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
doctorId.value = data?.id ?? data?.values?.id ?? 0;
|
doctorId.value = data?.id ?? data?.values?.id ?? 0;
|
||||||
suId.value = data?.su_id ?? data?.values?.su_id ?? 0;
|
suId.value = data?.su_id ?? data?.values?.su_id ?? 0;
|
||||||
hideStoresTab.value = !!data?.hideStoresTab;
|
hideStoresTab.value = !!data?.hideStoresTab;
|
||||||
|
readonly.value = !!data?.readonly;
|
||||||
activeTab.value = 'overview';
|
activeTab.value = 'overview';
|
||||||
prescriptionList.value = [];
|
prescriptionList.value = [];
|
||||||
patientList.value = [];
|
patientList.value = [];
|
||||||
@@ -460,7 +465,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
<Col :span="6" class="flex flex-col items-center border-r border-gray-200 dark:border-slate-700">
|
<Col :span="6" class="flex flex-col items-center border-r border-gray-200 dark:border-slate-700">
|
||||||
<div class="text-gray-500 dark:text-slate-400 mb-3 text-sm">医生头像</div>
|
<div class="text-gray-500 dark:text-slate-400 mb-3 text-sm">医生头像</div>
|
||||||
<AvatarUpload
|
<AvatarUpload
|
||||||
v-if="canEditProfile"
|
v-if="canEdit"
|
||||||
v-model:value="overviewForm.avatar"
|
v-model:value="overviewForm.avatar"
|
||||||
class="shadow-sm rounded-full overflow-hidden"
|
class="shadow-sm rounded-full overflow-hidden"
|
||||||
/>
|
/>
|
||||||
@@ -469,18 +474,18 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
<Col :span="18">
|
<Col :span="18">
|
||||||
<Form layout="vertical" class="grid grid-cols-2 gap-x-6">
|
<Form layout="vertical" class="grid grid-cols-2 gap-x-6">
|
||||||
<Form.Item label="医生姓名" class="mb-4">
|
<Form.Item label="医生姓名" class="mb-4">
|
||||||
<Input v-model:value="overviewForm.name" :disabled="!canEditProfile" placeholder="请输入姓名" size="large" />
|
<Input v-model:value="overviewForm.name" :disabled="!canEdit" placeholder="请输入姓名" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="手机号码" class="mb-4">
|
<Form.Item label="手机号码" class="mb-4">
|
||||||
<Input v-model:value="overviewForm.mobile" :disabled="!canEditProfile" placeholder="请输入手机号码" size="large" />
|
<Input v-model:value="overviewForm.mobile" :disabled="!canEdit" placeholder="请输入手机号码" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="专业擅长" class="col-span-2 mb-4">
|
<Form.Item label="专业擅长" class="col-span-2 mb-4">
|
||||||
<Input.TextArea v-model:value="overviewForm.good_at" :disabled="!canEditProfile" :rows="2" placeholder="填写医生擅长的领域..." />
|
<Input.TextArea v-model:value="overviewForm.good_at" :disabled="!canEdit" :rows="2" placeholder="填写医生擅长的领域..." />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="个人简介" class="col-span-2 mb-4">
|
<Form.Item label="个人简介" class="col-span-2 mb-4">
|
||||||
<Input.TextArea v-model:value="overviewForm.intro" :disabled="!canEditProfile" :rows="3" placeholder="填写医生简介..." />
|
<Input.TextArea v-model:value="overviewForm.intro" :disabled="!canEdit" :rows="3" placeholder="填写医生简介..." />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div v-if="canEditProfile" class="col-span-2 text-right mt-2">
|
<div v-if="canEdit" class="col-span-2 text-right mt-2">
|
||||||
<Button type="primary" size="large" :loading="savingOverview" @click="saveOverview">
|
<Button type="primary" size="large" :loading="savingOverview" @click="saveOverview">
|
||||||
保存基本信息
|
保存基本信息
|
||||||
</Button>
|
</Button>
|
||||||
@@ -498,14 +503,14 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
<Col :span="6" class="flex flex-col items-center border-r border-gray-200 dark:border-slate-700">
|
<Col :span="6" class="flex flex-col items-center border-r border-gray-200 dark:border-slate-700">
|
||||||
<div class="text-gray-500 dark:text-slate-400 mb-3 text-sm">账号头像</div>
|
<div class="text-gray-500 dark:text-slate-400 mb-3 text-sm">账号头像</div>
|
||||||
<AvatarUpload
|
<AvatarUpload
|
||||||
v-if="canEditProfile"
|
v-if="canEdit"
|
||||||
v-model:value="serviceUserForm.avatar"
|
v-model:value="serviceUserForm.avatar"
|
||||||
class="shadow-sm rounded-full overflow-hidden"
|
class="shadow-sm rounded-full overflow-hidden"
|
||||||
/>
|
/>
|
||||||
<Avatar v-else :src="resolveAvatarUrl(serviceUserForm.avatar)" :size="80" />
|
<Avatar v-else :src="resolveAvatarUrl(serviceUserForm.avatar)" :size="80" />
|
||||||
</Col>
|
</Col>
|
||||||
<Col :span="18">
|
<Col :span="18">
|
||||||
<Form v-if="canEditProfile" layout="vertical" class="grid grid-cols-2 gap-x-6">
|
<Form v-if="canEdit" layout="vertical" class="grid grid-cols-2 gap-x-6">
|
||||||
<Form.Item label="登录手机号" class="mb-4">
|
<Form.Item label="登录手机号" class="mb-4">
|
||||||
<Input v-model:value="serviceUserForm.mobile" placeholder="service_user.mobile" size="large" />
|
<Input v-model:value="serviceUserForm.mobile" placeholder="service_user.mobile" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -567,48 +572,60 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
<h3 class="text-base font-semibold text-gray-800 dark:text-slate-200">资质证书管理</h3>
|
<h3 class="text-base font-semibold text-gray-800 dark:text-slate-200">资质证书管理</h3>
|
||||||
<p class="text-sm text-gray-500 dark:text-slate-400 mt-1">请上传清晰、无遮挡的证件扫描件或照片</p>
|
<p class="text-sm text-gray-500 dark:text-slate-400 mt-1">请上传清晰、无遮挡的证件扫描件或照片</p>
|
||||||
</div>
|
</div>
|
||||||
<Button v-if="canEditProfile" type="primary" :loading="savingCredentials" @click="saveCredentials">
|
<Button v-if="canEdit" type="primary" :loading="savingCredentials" @click="saveCredentials">
|
||||||
保存全部资质
|
保存全部资质
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form layout="vertical">
|
<Form layout="vertical">
|
||||||
<!-- 通过比例控制让上传区域更像“证件” -->
|
|
||||||
<div class="grid grid-cols-2 lg:grid-cols-3 gap-6 bg-gray-50 dark:bg-[#1f242e] p-5 rounded-lg border border-gray-100 dark:border-slate-700">
|
<div class="grid grid-cols-2 lg:grid-cols-3 gap-6 bg-gray-50 dark:bg-[#1f242e] p-5 rounded-lg border border-gray-100 dark:border-slate-700">
|
||||||
<Form.Item label="身份证 (正面)" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
<Form.Item label="身份证 (正面)" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
||||||
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
||||||
<AvatarUpload v-model:value="credentialsForm.card_up" />
|
<AvatarUpload v-if="canEdit" v-model:value="credentialsForm.card_up" />
|
||||||
|
<Image v-else-if="credentialsForm.card_up" :src="resolveAvatarUrl(credentialsForm.card_up)" class="max-h-full object-contain" />
|
||||||
|
<span v-else class="text-gray-400">未上传</span>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="身份证 (反面)" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
<Form.Item label="身份证 (反面)" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
||||||
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
||||||
<AvatarUpload v-model:value="credentialsForm.card_down" />
|
<AvatarUpload v-if="canEdit" v-model:value="credentialsForm.card_down" />
|
||||||
|
<Image v-else-if="credentialsForm.card_down" :src="resolveAvatarUrl(credentialsForm.card_down)" class="max-h-full object-contain" />
|
||||||
|
<span v-else class="text-gray-400">未上传</span>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="执业资格证书" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
<Form.Item label="执业资格证书" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
||||||
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
||||||
<AvatarUpload v-model:value="credentialsForm.qualification" />
|
<AvatarUpload v-if="canEdit" v-model:value="credentialsForm.qualification" />
|
||||||
|
<Image v-else-if="credentialsForm.qualification" :src="resolveAvatarUrl(credentialsForm.qualification)" class="max-h-full object-contain" />
|
||||||
|
<span v-else class="text-gray-400">未上传</span>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="执业证书" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
<Form.Item label="执业证书" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
||||||
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
||||||
<AvatarUpload v-model:value="credentialsForm.practicing" />
|
<AvatarUpload v-if="canEdit" v-model:value="credentialsForm.practicing" />
|
||||||
|
<Image v-else-if="credentialsForm.practicing" :src="resolveAvatarUrl(credentialsForm.practicing)" class="max-h-full object-contain" />
|
||||||
|
<span v-else class="text-gray-400">未上传</span>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="职称证书" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
<Form.Item label="职称证书" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 text-center">
|
||||||
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
<div class="aspect-[1.6/1] w-full flex items-center justify-center">
|
||||||
<AvatarUpload v-model:value="credentialsForm.title" />
|
<AvatarUpload v-if="canEdit" v-model:value="credentialsForm.title" />
|
||||||
|
<Image v-else-if="credentialsForm.title" :src="resolveAvatarUrl(credentialsForm.title)" class="max-h-full object-contain" />
|
||||||
|
<span v-else class="text-gray-400">未上传</span>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<Form.Item label="医生签名图 (透明底)" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 flex-1 text-center">
|
<Form.Item label="医生签名图 (透明底)" class="mb-0 bg-white dark:bg-[#18181c] p-4 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60 flex-1 text-center">
|
||||||
<AvatarUpload v-model:value="credentialsForm.sign_image" />
|
<AvatarUpload v-if="canEdit" v-model:value="credentialsForm.sign_image" />
|
||||||
|
<Image v-else-if="credentialsForm.sign_image" :src="resolveAvatarUrl(credentialsForm.sign_image)" class="max-h-full object-contain" />
|
||||||
|
<span v-else class="text-gray-400">未上传</span>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="签名验证类型" class="mb-0 bg-white dark:bg-[#18181c] p-3 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60">
|
<Form.Item label="签名验证类型" class="mb-0 bg-white dark:bg-[#18181c] p-3 rounded-md shadow-sm border border-gray-100 dark:border-slate-700/60">
|
||||||
<Select v-model:value="credentialsForm.sign_type" size="large" :options="[
|
<Select v-if="canEdit" v-model:value="credentialsForm.sign_type" size="large" :options="[
|
||||||
{ label: '电子认证签名', value: 1 },
|
{ label: '电子认证签名', value: 1 },
|
||||||
{ label: '手写扫描签名', value: 2 },
|
{ label: '手写扫描签名', value: 2 },
|
||||||
]" />
|
]" />
|
||||||
|
<span v-else>{{ credentialsForm.sign_type === 2 ? '手写扫描签名' : '电子认证签名' }}</span>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -670,7 +687,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
<div class="text-orange-400 text-5xl mb-4">🖥️</div>
|
<div class="text-orange-400 text-5xl mb-4">🖥️</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-slate-200 mb-2">未开通 PC 端管理权限</h3>
|
<h3 class="text-lg font-semibold text-gray-800 dark:text-slate-200 mb-2">未开通 PC 端管理权限</h3>
|
||||||
<p class="text-gray-500 dark:text-slate-400 mb-6 text-sm">开通后,医生可使用电脑端登录系统进行高级管理操作,如查看完整报表等。</p>
|
<p class="text-gray-500 dark:text-slate-400 mb-6 text-sm">开通后,医生可使用电脑端登录系统进行高级管理操作,如查看完整报表等。</p>
|
||||||
<Button type="primary" size="large" @click="openPcWindows">立即开通后台账号</Button>
|
<Button v-if="canEdit" type="primary" size="large" @click="openPcWindows">立即开通后台账号</Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Avatar, Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||||
|
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
row: Record<string, any>;
|
||||||
|
onOpenCard: (row: Record<string, any>) => void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="doctor-basic-info">
|
||||||
|
<div class="doctor-basic-info__header">
|
||||||
|
<Avatar
|
||||||
|
:size="32"
|
||||||
|
:src="resolveAvatarUrl(row.avatar) || '/img/user-default-avatar.png'"
|
||||||
|
class="shrink-0"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
class="doctor-basic-info__name !h-auto !px-0 !py-0"
|
||||||
|
type="link"
|
||||||
|
@click="onOpenCard(row)"
|
||||||
|
>
|
||||||
|
{{ row.name || '-' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="doctor-basic-info__grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-item__label">手机</span>
|
||||||
|
<SensitiveText
|
||||||
|
:record="row"
|
||||||
|
field="mobile"
|
||||||
|
empty-text="-"
|
||||||
|
class="info-item__value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-item__label">wxID</span>
|
||||||
|
<span class="info-item__value">{{ row.su_id ?? '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.doctor-basic-info {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doctor-basic-info__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doctor-basic-info__name {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
max-width: 160px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doctor-basic-info__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgb(107 114 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item__label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 36px;
|
||||||
|
color: rgb(156 163 175);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item__value {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
row: Record<string, any>;
|
||||||
|
onEditDepart: (row: Record<string, any>) => void;
|
||||||
|
onEditTitle: (row: Record<string, any>) => void;
|
||||||
|
onEditSubjects: (row: Record<string, any>) => void;
|
||||||
|
onEditStores: (row: Record<string, any>) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化所属诊所列表为单行展示
|
||||||
|
*/
|
||||||
|
function formatStores(stores: any[] | undefined) {
|
||||||
|
if (!stores?.length) return '';
|
||||||
|
return stores
|
||||||
|
.map((item) => item?.store?.name)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('、');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="doctor-professional-info">
|
||||||
|
<div class="doctor-professional-info__grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-item__label">科室</span>
|
||||||
|
<Button
|
||||||
|
class="info-item__action"
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
@click="onEditDepart(row)"
|
||||||
|
>
|
||||||
|
{{ row.depart?.name || '绑定' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-item__label">职称</span>
|
||||||
|
<Button
|
||||||
|
class="info-item__action"
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
@click="onEditTitle(row)"
|
||||||
|
>
|
||||||
|
{{ row.title?.name || '绑定' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-item__label">诊疗科目</span>
|
||||||
|
<Button
|
||||||
|
class="info-item__action"
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
@click="onEditSubjects(row)"
|
||||||
|
>
|
||||||
|
{{ row.subjects?.name || '绑定' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="info-item info-item--stores">
|
||||||
|
<span class="info-item__label">所属诊所</span>
|
||||||
|
<Button
|
||||||
|
class="info-item__action"
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
@click="onEditStores(row)"
|
||||||
|
>
|
||||||
|
{{ formatStores(row.stores) || '绑定' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.doctor-professional-info {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doctor-professional-info__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgb(107 114 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item--stores {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item__label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 52px;
|
||||||
|
color: rgb(156 163 175);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item__action {
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -29,27 +29,24 @@ export const gridOptions: VxeGridProps<RowType> = {
|
|||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
{ type: 'checkbox', width: 60 },
|
{ type: 'checkbox', width: 60 },
|
||||||
{ field: 'id', align: 'left', title: 'ID', width: 100 },
|
{ field: 'id', align: 'left', title: 'ID', width: 80 },
|
||||||
{ field: 'name', align: 'left', title: '名称', slots: { default: 'name' } },
|
|
||||||
{ field: 'mobile', align: 'left', title: '手机号', slots: { default: 'mobile' } },
|
|
||||||
{
|
{
|
||||||
field: 'avatar',
|
field: 'doctor_basic_info',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
title: '头像',
|
title: '基础信息',
|
||||||
slots: { default: 'avatar' },
|
width: 220,
|
||||||
width: 130,
|
slots: { default: 'doctor_basic_info' },
|
||||||
},
|
},
|
||||||
{ field: 'su_id', title: 'wxID' },
|
|
||||||
{ field: 'stores', title: '所属诊所', slots: { default: 'stores' } },
|
|
||||||
{
|
{
|
||||||
field: 'subjects',
|
field: 'doctor_professional_info',
|
||||||
title: '诊疗科目',
|
align: 'left',
|
||||||
slots: { default: 'subjects' },
|
title: '执业信息',
|
||||||
minWidth: 120,
|
width: 280,
|
||||||
|
slots: { default: 'doctor_professional_info' },
|
||||||
},
|
},
|
||||||
{ field: 'service_user', title: '审核状态', slots: { default: 'service-user' } },
|
{ field: 'service_user', title: '审核状态', slots: { default: 'service-user' } },
|
||||||
{ field: 'created_at', title: '注册时间' },
|
{ field: 'created_at', title: '注册时间', width: 160 },
|
||||||
{ type: 'html', title: '操作', width: 340, slots: { default: 'action' } },
|
{ type: 'html', title: '操作', width: 260, slots: { default: 'action' } },
|
||||||
],
|
],
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
pagerConfig: {},
|
pagerConfig: {},
|
||||||
|
|||||||
@@ -5,15 +5,18 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Button, Image, message, Tag } from 'ant-design-vue';
|
import { Button, message, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { TableAction } from '#/components/table-action';
|
import { TableAction } from '#/components/table-action';
|
||||||
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
|
||||||
|
|
||||||
import { auditApi, openPcWindowsApi, refuseApi } from './api';
|
import { auditApi, openPcWindowsApi, refuseApi } from './api';
|
||||||
|
import BindDepart from './components/BindDepartModal.vue';
|
||||||
import BindStore from './components/BindStoreModal.vue';
|
import BindStore from './components/BindStoreModal.vue';
|
||||||
import BindSubjects from './components/BindSubjectsModal.vue';
|
import BindSubjects from './components/BindSubjectsModal.vue';
|
||||||
|
import BindTitle from './components/BindTitleModal.vue';
|
||||||
|
import DoctorBasicInfoCell from './components/cells/DoctorBasicInfoCell.vue';
|
||||||
|
import DoctorProfessionalInfoCell from './components/cells/DoctorProfessionalInfoCell.vue';
|
||||||
import FormModalDemo from './components/modal.vue';
|
import FormModalDemo from './components/modal.vue';
|
||||||
import DoctorCardModal from './components/DoctorCardModal.vue';
|
import DoctorCardModal from './components/DoctorCardModal.vue';
|
||||||
import { formOptions } from './config/search';
|
import { formOptions } from './config/search';
|
||||||
@@ -52,13 +55,20 @@ const [BindSubjectsModal, BindSubjectsModalApi] = useVbenModal({
|
|||||||
connectedComponent: BindSubjects,
|
connectedComponent: BindSubjects,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [BindDepartModal, BindDepartModalApi] = useVbenModal({
|
||||||
|
connectedComponent: BindDepart,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [BindTitleModal, BindTitleModalApi] = useVbenModal({
|
||||||
|
connectedComponent: BindTitle,
|
||||||
|
});
|
||||||
|
|
||||||
const [DoctorCardModals, DoctorCardModalApi] = useVbenModal({
|
const [DoctorCardModals, DoctorCardModalApi] = useVbenModal({
|
||||||
connectedComponent: DoctorCardModal,
|
connectedComponent: DoctorCardModal,
|
||||||
});
|
});
|
||||||
|
|
||||||
const showModal = (data = {}, isUpdate = false) => {
|
const showModal = (data = {}, isUpdate = false) => {
|
||||||
formModalApi.setData({
|
formModalApi.setData({
|
||||||
// 表单值
|
|
||||||
values: data,
|
values: data,
|
||||||
update: isUpdate,
|
update: isUpdate,
|
||||||
gridApi,
|
gridApi,
|
||||||
@@ -66,15 +76,16 @@ const showModal = (data = {}, isUpdate = false) => {
|
|||||||
formModalApi.open();
|
formModalApi.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
const showBindStoreModal = (data: number) => {
|
/** 打开绑定诊所弹窗 */
|
||||||
|
const showBindStoreModal = (row: Record<string, any>) => {
|
||||||
BindStoreModalApi.setData({
|
BindStoreModalApi.setData({
|
||||||
// 表单值
|
values: row,
|
||||||
values: data,
|
|
||||||
gridApi,
|
gridApi,
|
||||||
});
|
});
|
||||||
BindStoreModalApi.open();
|
BindStoreModalApi.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 打开绑定诊疗科目弹窗 */
|
||||||
const showBindSubjectsModal = (row: Record<string, any>) => {
|
const showBindSubjectsModal = (row: Record<string, any>) => {
|
||||||
BindSubjectsModalApi.setData({
|
BindSubjectsModalApi.setData({
|
||||||
values: row,
|
values: row,
|
||||||
@@ -83,6 +94,24 @@ const showBindSubjectsModal = (row: Record<string, any>) => {
|
|||||||
BindSubjectsModalApi.open();
|
BindSubjectsModalApi.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 打开编辑科室弹窗 */
|
||||||
|
const showBindDepartModal = (row: Record<string, any>) => {
|
||||||
|
BindDepartModalApi.setData({
|
||||||
|
values: row,
|
||||||
|
gridApi,
|
||||||
|
});
|
||||||
|
BindDepartModalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 打开编辑职称弹窗 */
|
||||||
|
const showBindTitleModal = (row: Record<string, any>) => {
|
||||||
|
BindTitleModalApi.setData({
|
||||||
|
values: row,
|
||||||
|
gridApi,
|
||||||
|
});
|
||||||
|
BindTitleModalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
const showDoctorCard = (row: Record<string, any>) => {
|
const showDoctorCard = (row: Record<string, any>) => {
|
||||||
DoctorCardModalApi.setData({
|
DoctorCardModalApi.setData({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
@@ -119,6 +148,8 @@ const refuse = (id: number) => {
|
|||||||
<FormModal />
|
<FormModal />
|
||||||
<BindStoreModal />
|
<BindStoreModal />
|
||||||
<BindSubjectsModal />
|
<BindSubjectsModal />
|
||||||
|
<BindDepartModal />
|
||||||
|
<BindTitleModal />
|
||||||
<DoctorCardModals />
|
<DoctorCardModals />
|
||||||
<Grid>
|
<Grid>
|
||||||
<template #toolbar-buttons>
|
<template #toolbar-buttons>
|
||||||
@@ -128,22 +159,10 @@ const refuse = (id: number) => {
|
|||||||
label: '新增',
|
label: '新增',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
icon: 'ant-design:plus-outlined',
|
icon: 'ant-design:plus-outlined',
|
||||||
// auth: ['超级医生', 'sys:user:save'],
|
|
||||||
onClick: showModal.bind(null),
|
onClick: showModal.bind(null),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
:drop-down-actions="[
|
:drop-down-actions="[]"
|
||||||
// {
|
|
||||||
// label: '删除',
|
|
||||||
// icon: 'ant-design:delete-outlined',
|
|
||||||
// ifShow: hasTopTableDropDownActions,
|
|
||||||
// // auth: ['超级医生', 'sys:user:save'],
|
|
||||||
// popConfirm: {
|
|
||||||
// title: '确定删除吗?',
|
|
||||||
// confirm: deleteApi.bind(null, false),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<template #more>
|
<template #more>
|
||||||
<Button style="margin-left: 16px">
|
<Button style="margin-left: 16px">
|
||||||
@@ -153,24 +172,17 @@ const refuse = (id: number) => {
|
|||||||
</template>
|
</template>
|
||||||
</TableAction>
|
</TableAction>
|
||||||
</template>
|
</template>
|
||||||
<template #avatar="{ row }">
|
<template #doctor_basic_info="{ row }">
|
||||||
<Image :src="row.avatar" height="30" width="30" />
|
<DoctorBasicInfoCell :row="row" :on-open-card="showDoctorCard" />
|
||||||
</template>
|
</template>
|
||||||
<template #name="{ row }">
|
<template #doctor_professional_info="{ row }">
|
||||||
<Button type="link" size="small" @click="showDoctorCard(row)">
|
<DoctorProfessionalInfoCell
|
||||||
{{ row.name }}
|
:row="row"
|
||||||
</Button>
|
:on-edit-depart="showBindDepartModal"
|
||||||
</template>
|
:on-edit-title="showBindTitleModal"
|
||||||
<template #mobile="{ row }">
|
:on-edit-subjects="showBindSubjectsModal"
|
||||||
<SensitiveText :record="row" field="mobile" />
|
:on-edit-stores="showBindStoreModal"
|
||||||
</template>
|
/>
|
||||||
<template #stores="{ row }">
|
|
||||||
<p v-for="item in row.stores" :key="item.store_id">
|
|
||||||
{{ item?.store?.name }}
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
<template #subjects="{ row }">
|
|
||||||
{{ row.subjects?.name ?? '-' }}
|
|
||||||
</template>
|
</template>
|
||||||
<template #service-user="{ row }">
|
<template #service-user="{ row }">
|
||||||
<Tag v-if="row.service_user?.status === 2" color="success">已通过</Tag>
|
<Tag v-if="row.service_user?.status === 2" color="success">已通过</Tag>
|
||||||
@@ -203,30 +215,13 @@ const refuse = (id: number) => {
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'uil:edit',
|
icon: 'uil:edit',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
// auth: ['doctor', 'sys:role:detail'],
|
onClick: showModal.bind(null, row, true),
|
||||||
onClick: showModal.bind(null, row),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '绑定诊所',
|
|
||||||
type: 'link',
|
|
||||||
icon: 'uil:edit',
|
|
||||||
size: 'small',
|
|
||||||
// auth: ['doctor', 'sys:role:detail'],
|
|
||||||
onClick: showBindStoreModal.bind(null, row),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '绑定诊疗科目',
|
|
||||||
type: 'link',
|
|
||||||
icon: 'uil:edit',
|
|
||||||
size: 'small',
|
|
||||||
onClick: showBindSubjectsModal.bind(null, row),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '开通PC端',
|
label: '开通PC端',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'uil:edit',
|
icon: 'uil:edit',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
// auth: ['doctor', 'sys:role:detail'],
|
|
||||||
onClick: openPcWindows.bind(null, row.id),
|
onClick: openPcWindows.bind(null, row.id),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@@ -237,7 +232,6 @@ const refuse = (id: number) => {
|
|||||||
icon: 'uil:edit',
|
icon: 'uil:edit',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
ifShow: row.service_user?.status === 1,
|
ifShow: row.service_user?.status === 1,
|
||||||
// auth: ['doctor', 'sys:role:detail'],
|
|
||||||
onClick: audit.bind(null, row.id),
|
onClick: audit.bind(null, row.id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -246,7 +240,6 @@ const refuse = (id: number) => {
|
|||||||
icon: 'uil:edit',
|
icon: 'uil:edit',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
ifShow: row.service_user?.status === 1,
|
ifShow: row.service_user?.status === 1,
|
||||||
// auth: ['doctor', 'sys:role:detail'],
|
|
||||||
onClick: refuse.bind(null, row.id),
|
onClick: refuse.bind(null, row.id),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
|||||||
Reference in New Issue
Block a user