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-11-08 15:05:52 +08:00
parent e637a46889
commit 09a423b8dc
29 changed files with 885 additions and 240 deletions

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import { Select } from 'ant-design-vue';
defineOptions({
inheritAttrs: false,
});
const props = defineProps({
value: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '请输入',
},
});
const emits = defineEmits(['update:value']);
const mValue = useVModel(props, 'value', emits, {
defaultValue: props.value,
passive: true,
});
</script>
<template>
<Select
v-model:value="mValue"
:placeholder="placeholder"
list-type="picture-card"
mode="tags"
style="width: 100%"
/>
</template>
<style lang="less" scoped></style>

View File

@@ -16,6 +16,7 @@ import {
getProcessRuleList, getProcessRuleList,
getProductListDoctorReception, getProductListDoctorReception,
} from '#/views/doctor/doctor-reception/api'; } from '#/views/doctor/doctor-reception/api';
import traditionalJson from '#/views/business/chat/config/traditional.json';
export const usePrescriptionStore = defineStore('prescription', () => { export const usePrescriptionStore = defineStore('prescription', () => {
// 基础数据 // 基础数据
@@ -73,6 +74,11 @@ export const usePrescriptionStore = defineStore('prescription', () => {
const isInitialized = ref(false); const isInitialized = ref(false);
const isPatientInfoLoaded = ref(false); const isPatientInfoLoaded = ref(false);
const traditionalChineseMedicine = ref(traditionalJson);
const diseasesSelected = ref();
const methodSelected = ref();
const syndromeSelected = ref();
// 获取localStorage key // 获取localStorage key
const getStorageKey = () => const getStorageKey = () =>
`prescriptionData-chat-${currentRegisterId.value}`; `prescriptionData-chat-${currentRegisterId.value}`;
@@ -590,6 +596,7 @@ export const usePrescriptionStore = defineStore('prescription', () => {
// 发送处方 // 发送处方
const sendPrescription = async (doctorSecondSignValue = 0) => { const sendPrescription = async (doctorSecondSignValue = 0) => {
if (currentDrugs.value.length === 0) { if (currentDrugs.value.length === 0) {
message.error('请选择药品'); message.error('请选择药品');
return false; return false;
@@ -625,7 +632,6 @@ export const usePrescriptionStore = defineStore('prescription', () => {
message.error('医嘱不能为空'); message.error('医嘱不能为空');
return false; return false;
} }
try { try {
await addWestPrescription({ await addWestPrescription({
patient: activePatient.value, patient: activePatient.value,
@@ -647,6 +653,10 @@ export const usePrescriptionStore = defineStore('prescription', () => {
dosage: dosage.value, dosage: dosage.value,
day_dosage: dayDosage.value, day_dosage: dayDosage.value,
doctor_second_sign: doctorSecondSignValue, doctor_second_sign: doctorSecondSignValue,
// 中医在线问诊所需的东西
diseases_id: diseasesSelected.value,
method_id: methodSelected.value,
syndrome_id: syndromeSelected.value,
}).then((res) => { }).then((res) => {
message.success('处方已发送'); message.success('处方已发送');
sendMessage({ sendMessage({
@@ -798,5 +808,9 @@ export const usePrescriptionStore = defineStore('prescription', () => {
syncToLocalStorage, syncToLocalStorage,
loadFromLocalStorage, loadFromLocalStorage,
updateCurrentDrugs, updateCurrentDrugs,
traditionalChineseMedicine,
diseasesSelected,
methodSelected,
syndromeSelected,
}; };
}); });

View File

@@ -1,25 +1,39 @@
<script setup> <script setup>
import {computed, ref} from 'vue'; import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores'; import { useUserStore } from '@vben/stores';
import {Avatar, Button, Image} from 'ant-design-vue'; import { Avatar, Button, Image } from 'ant-design-vue';
import {getChatMessageRegisterInfoApi} from "#/views/business/chat/api"; import { getChatMessageRegisterInfoApi } from '#/views/business/chat/api';
import {getPrescriptionCheckStatusApi} from "#/views/business/order/prescription/api"; import { useChatStore } from '#/views/business/chat/stores/chat';
import { sendMessage } from '#/views/business/chat/utils/request';
import { getPrescriptionCheckStatusApi } from '#/views/business/order/prescription/api';
import { receptionApi } from '#/views/doctor/doctor-reception/api';
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue'; import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
import RefusalOfTreatmentModal from '#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue';
import previewMedia from '../composables/useMediaPreview.ts'; import previewMedia from '../composables/useMediaPreview.ts';
import {useThemeStore} from '../stores/theme.ts'; import { useThemeStore } from '../stores/theme.ts';
import AudioMessage from './AudioMessage.vue';
import {receptionApi} from "#/views/doctor/doctor-reception/api";
import {useChatStore} from "#/views/business/chat/stores/chat";
import { useUserStore as chatUseUserStore } from '../stores/user'; import { useUserStore as chatUseUserStore } from '../stores/user';
import {sendMessage} from "#/views/business/chat/utils/request"; import AudioMessage from './AudioMessage.vue';
import RefusalOfTreatmentModal
from "#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue";
const props = defineProps({
message: {
type: Object,
required: true,
},
isSent: {
type: Boolean,
default: false,
},
senderInfo: {
type: Object,
required: true,
},
});
const emit = defineEmits(['show-user-profile']);
const chatUserStore = chatUseUserStore(); const chatUserStore = chatUseUserStore();
const userStore = useUserStore(); const userStore = useUserStore();
const chatStore = useChatStore(); const chatStore = useChatStore();
@@ -29,23 +43,6 @@ const [RefusalOfTreatmentModals, RefusalOfTreatmentModalApi] = useVbenModal({
connectedComponent: RefusalOfTreatmentModal, connectedComponent: RefusalOfTreatmentModal,
}); });
const props = defineProps({
message: {
type: Object,
required: true
},
isSent: {
type: Boolean,
default: false
},
senderInfo: {
type: Object,
required: true
}
});
const emit = defineEmits(['show-user-profile']);
const avatarStyle = computed(() => ({ const avatarStyle = computed(() => ({
background: props.senderInfo.color, background: props.senderInfo.color,
})); }));
@@ -85,18 +82,20 @@ const payStatusMap = {
const checkPrescriptionStatus = (id) => { const checkPrescriptionStatus = (id) => {
if (!id) return; if (!id) return;
getPrescriptionCheckStatusApi({id}).then((res) => { getPrescriptionCheckStatusApi({ id }).then((res) => {
statusInfo.value.status = res.status; statusInfo.value.status = res.status;
statusInfo.value.text = prescriptionStatusMap[res.status]; statusInfo.value.text = prescriptionStatusMap[res.status];
return prescriptionStatusMap[res.status] return prescriptionStatusMap[res.status];
}) });
}; };
const getRegisterCardInfo = () => { const getRegisterCardInfo = () => {
getChatMessageRegisterInfoApi({ id: props.message.content.id }).then((res) => { getChatMessageRegisterInfoApi({ id: props.message.content.id }).then(
// eslint-disable-next-line vue/no-mutating-props (res) => {
props.message.content = res; // eslint-disable-next-line vue/no-mutating-props
}); props.message.content = res;
},
);
}; };
// 接诊操作 // 接诊操作
@@ -105,7 +104,6 @@ const acceptRegister = async (registerId, orderNo) => {
registerOperating.value = true; registerOperating.value = true;
try { try {
// 模拟API调用 // 模拟API调用
@@ -124,7 +122,8 @@ const acceptRegister = async (registerId, orderNo) => {
read: true, read: true,
duration: 0, duration: 0,
}; };
localStorage.setItem('currentRegisterId', reregisterId);
// chatStore.currentFriend?.register_id = reregisterId;
chatStore.addMessage(messageObj, chatUserStore.currentUser.id); chatStore.addMessage(messageObj, chatUserStore.currentUser.id);
await sendMessage({ await sendMessage({
senderId: chatUserStore.currentUser.id, senderId: chatUserStore.currentUser.id,
@@ -134,7 +133,6 @@ const acceptRegister = async (registerId, orderNo) => {
content: '我已接诊,请简单说明您的情况~', content: '我已接诊,请简单说明您的情况~',
}); });
}); });
} catch (error) { } catch (error) {
console.error('接诊失败:', error); console.error('接诊失败:', error);
} finally { } finally {
@@ -192,7 +190,7 @@ const formatDuration = (seconds) => {
}; };
const formatTextWithLinks = (text) => { const formatTextWithLinks = (text) => {
console.log(text, props.message) console.log(text, props.message);
if (!text) return text; // 或 return text (但可能返回 undefined/null,根据需求调整) if (!text) return text; // 或 return text (但可能返回 undefined/null,根据需求调整)
const urlRegex = /(https?:\/\/\S+)/g; const urlRegex = /(https?:\/\/\S+)/g;
return text.replaceAll( return text.replaceAll(
@@ -231,9 +229,9 @@ const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
const viewPrescription = (item) => { const viewPrescription = (item) => {
PrescriptionDetailModalApi.setData({ PrescriptionDetailModalApi.setData({
values: item.id, values: item.id,
}) });
PrescriptionDetailModalApi.open(); PrescriptionDetailModalApi.open();
} };
// 格式化日期时间 // 格式化日期时间
const formatDateTime = (timestamp) => { const formatDateTime = (timestamp) => {
@@ -244,7 +242,7 @@ const formatDateTime = (timestamp) => {
month: '2-digit', month: '2-digit',
day: '2-digit', day: '2-digit',
hour: '2-digit', hour: '2-digit',
minute: '2-digit' minute: '2-digit',
}); });
}; };
@@ -253,7 +251,6 @@ const getSexText = (sex) => {
return sex === 1 ? '男' : '女'; return sex === 1 ? '男' : '女';
}; };
if (props.message.type === 'register') { if (props.message.type === 'register') {
getRegisterCardInfo(); getRegisterCardInfo();
} }
@@ -270,16 +267,22 @@ if (props.message.type === 'register') {
<div <div
v-if="isSent" v-if="isSent"
:style="avatarStyle" :style="avatarStyle"
class="rounded-full cursor-pointer" class="cursor-pointer rounded-full"
@click="showUserProfile" @click="showUserProfile"
> >
<Avatar v-if="userStore.userInfo?.avatar" :size="48" :src="userStore.userInfo?.avatar" /> <Avatar
<Avatar v-else :size="48">{{ userStore.userInfo?.nick_name.charAt(0) }}</Avatar> v-if="userStore.userInfo?.avatar"
:size="48"
:src="userStore.userInfo?.avatar"
/>
<Avatar v-else :size="48">
{{ userStore.userInfo?.nick_name.charAt(0) }}
</Avatar>
</div> </div>
<div <div
v-else v-else
:style="avatarStyle" :style="avatarStyle"
class="rounded-full cursor-pointer" class="cursor-pointer rounded-full"
@click="showUserProfile" @click="showUserProfile"
> >
<Avatar v-if="senderInfo.avatar" :size="48" :src="senderInfo.avatar" /> <Avatar v-if="senderInfo.avatar" :size="48" :src="senderInfo.avatar" />
@@ -289,10 +292,13 @@ if (props.message.type === 'register') {
<!-- 消息内容区域 --> <!-- 消息内容区域 -->
<div <div
:class="isSent ? 'items-end' : 'items-start'" :class="isSent ? 'items-end' : 'items-start'"
class="flex flex-col max-w-xs md:max-w-md lg:max-w-lg xl:max-w-xl" class="flex max-w-xs flex-col md:max-w-md lg:max-w-lg xl:max-w-xl"
> >
<!-- 发送者名称 --> <!-- 发送者名称 -->
<div v-if="isSent" class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400"> <div
v-if="isSent"
class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400"
>
{{ userStore.userInfo?.nick_name }} {{ userStore.userInfo?.nick_name }}
</div> </div>
<div v-else class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400"> <div v-else class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400">
@@ -310,7 +316,7 @@ if (props.message.type === 'register') {
<Image <Image
:src="message.content" :src="message.content"
alt="图片消息" alt="图片消息"
class="rounded-lg max-w-full" class="max-w-full rounded-lg"
@error="handleImageError" @error="handleImageError"
/> />
</div> </div>
@@ -323,7 +329,7 @@ if (props.message.type === 'register') {
> >
<video <video
:src="message.content" :src="message.content"
class="rounded-lg max-w-full video-thumbnail" class="video-thumbnail max-w-full rounded-lg"
preload="metadata" preload="metadata"
@error="handleVideoError" @error="handleVideoError"
></video> ></video>
@@ -372,39 +378,49 @@ if (props.message.type === 'register') {
/> />
<!-- 挂号卡片消息 --> <!-- 挂号卡片消息 -->
<div <div v-else-if="message.type === 'register'" class="register-card">
v-else-if="message.type === 'register'"
class="register-card"
>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center flex-shrink-0"> <div
<i class="fas fa-calendar-check text-green-500 dark:text-green-300 text-xl"></i> class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-green-100 dark:bg-green-900"
>
<i
class="fas fa-calendar-check text-xl text-green-500 dark:text-green-300"
></i>
</div> </div>
<div class="flex-1 min-w-0"> <div class="min-w-0 flex-1">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="font-semibold text-gray-800 dark:text-gray-100">挂号信息</div> <div class="font-semibold text-gray-800 dark:text-gray-100">
挂号信息
</div>
<div <div
:class="{ :class="{
'bg-yellow-100 text-yellow-800': message.content.status === 0, 'bg-yellow-100 text-yellow-800':
message.content.status === 0,
'bg-blue-100 text-blue-800': message.content.status === 1, 'bg-blue-100 text-blue-800': message.content.status === 1,
'bg-green-100 text-green-800': message.content.status === 2, 'bg-green-100 text-green-800': message.content.status === 2,
'bg-red-100 text-red-800': message.content.status === 3, 'bg-red-100 text-red-800': message.content.status === 3,
'bg-gray-100 text-gray-800': message.content.status === 4, 'bg-gray-100 text-gray-800': message.content.status === 4,
'bg-purple-100 text-purple-800': message.content.status === 5, 'bg-purple-100 text-purple-800':
'bg-indigo-100 text-indigo-800': message.content.status === 6, message.content.status === 5,
'bg-indigo-100 text-indigo-800':
message.content.status === 6,
'bg-pink-100 text-pink-800': message.content.status === 7, 'bg-pink-100 text-pink-800': message.content.status === 7,
}" }"
class="text-xs px-2 py-1 rounded-full font-medium" class="rounded-full px-2 py-1 text-xs font-medium"
> >
{{ registerStatusMap[message.content.status] || '未知状态' }} {{ registerStatusMap[message.content.status] || '未知状态' }}
</div> </div>
</div> </div>
<div class="mt-1 flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300"> <div
class="mt-1 flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300"
>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<i class="fas fa-hashtag text-xs"></i> <i class="fas fa-hashtag text-xs"></i>
<span class="truncate max-w-[120px]">{{ message.content.order_no }}</span> <span class="max-w-[120px] truncate">{{
message.content.order_no
}}</span>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@@ -416,21 +432,25 @@ if (props.message.type === 'register') {
</div> </div>
<!-- 患者信息 --> <!-- 患者信息 -->
<div class="mt-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"> <div class="mt-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
<div class="flex items-center justify-between mb-2"> <div class="mb-2 flex items-center justify-between">
<div class="font-medium text-gray-700 dark:text-gray-200">患者信息</div> <div class="font-medium text-gray-700 dark:text-gray-200">
患者信息
</div>
<div <div
:class="{ :class="{
'bg-red-100 text-red-800': message.content.is_pay === 0, 'bg-red-100 text-red-800': message.content.is_pay === 0,
'bg-green-100 text-green-800': message.content.is_pay === 1 'bg-green-100 text-green-800': message.content.is_pay === 1,
}" }"
class="text-xs px-2 py-1 rounded-full font-medium" class="rounded-full px-2 py-1 text-xs font-medium"
> >
{{ payStatusMap[message.content.is_pay] || '未知' }} {{ payStatusMap[message.content.is_pay] || '未知' }}
</div> </div>
</div> </div>
<div class="grid grid-cols-2 gap-2 text-sm text-gray-600 dark:text-gray-300"> <div
class="grid grid-cols-2 gap-2 text-sm text-gray-600 dark:text-gray-300"
>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<i class="fas fa-user text-xs"></i> <i class="fas fa-user text-xs"></i>
<span>{{ message.content.user_patient?.name || '未知' }}</span> <span>{{ message.content.user_patient?.name || '未知' }}</span>
@@ -445,37 +465,44 @@ if (props.message.type === 'register') {
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<i class="fas fa-phone text-xs"></i> <i class="fas fa-phone text-xs"></i>
<span>{{ message.content.user_patient?.mobile ? '已绑定' : '未绑定' }}</span> <span>{{
message.content.user_patient?.mobile ? '已绑定' : '未绑定'
}}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 操作按钮区域 --> <!-- 操作按钮区域 -->
<div class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-600"> <div class="mt-3 border-t border-gray-200 pt-3 dark:border-gray-600">
<div class="flex items-center justify-between mb-2"> <div class="mb-2 flex items-center justify-between">
<div class="text-sm font-medium text-gray-700 dark:text-gray-200"> <div class="text-sm font-medium text-gray-700 dark:text-gray-200">
挂号费用: <span class="text-green-500 dark:text-green-400">¥{{ message.content.price }}</span> 挂号费用:
<span class="text-green-500 dark:text-green-400">¥{{ message.content.price }}</span>
</div> </div>
</div> </div>
<!-- 接诊/拒诊按钮 - 只在待就诊状态显示 --> <!-- 接诊/拒诊按钮 - 只在待就诊状态显示 -->
<div v-if="message.content.status === 1" class="flex gap-2 mt-2"> <div v-if="message.content.status === 1" class="mt-2 flex gap-2">
<Button <Button
type="primary"
size="small"
:loading="registerOperating" :loading="registerOperating"
@click="acceptRegister(message.content.id, message.content.order_no)"
class="flex-1" class="flex-1"
size="small"
type="primary"
@click="
acceptRegister(message.content.id, message.content.order_no)
"
> >
<i class="fas fa-check mr-1"></i> <i class="fas fa-check mr-1"></i>
接诊 接诊
</Button> </Button>
<Button <Button
:loading="registerOperating"
class="flex-1"
danger danger
size="small" size="small"
:loading="registerOperating" @click="
@click="rejectRegister(message.content.id, message.content.order_no)" rejectRegister(message.content.id, message.content.order_no)
class="flex-1" "
> >
<i class="fas fa-times mr-1"></i> <i class="fas fa-times mr-1"></i>
拒诊 拒诊
@@ -483,12 +510,12 @@ if (props.message.type === 'register') {
</div> </div>
<!-- 查看详情按钮 --> <!-- 查看详情按钮 -->
<div class="flex items-center justify-center mt-2"> <!-- <div class="flex items-center justify-center mt-2">-->
<div class="flex items-center text-blue-500 dark:text-blue-300 hover:text-blue-600 dark:hover:text-blue-200 transition-colors cursor-pointer"> <!-- <div class="flex items-center text-blue-500 dark:text-blue-300 hover:text-blue-600 dark:hover:text-blue-200 transition-colors cursor-pointer">-->
<span class="text-sm font-medium">查看详情</span> <!-- <span class="text-sm font-medium">查看详情</span>-->
<i class="fas fa-chevron-right ml-1 text-xs"></i> <!-- <i class="fas fa-chevron-right ml-1 text-xs"></i>-->
</div> <!-- </div>-->
</div> <!-- </div>-->
</div> </div>
<!-- 时间信息 --> <!-- 时间信息 -->
@@ -507,31 +534,45 @@ if (props.message.type === 'register') {
@click="viewPrescription(message.content)" @click="viewPrescription(message.content)"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center flex-shrink-0"> <div
<i class="fas fa-file-prescription text-blue-500 dark:text-blue-300 text-xl"></i> class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900"
>
<i
class="fas fa-file-prescription text-xl text-blue-500 dark:text-blue-300"
></i>
</div> </div>
<div class="flex-1 min-w-0"> <div class="min-w-0 flex-1">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="font-semibold text-gray-800 dark:text-gray-100">电子处方</div> <div class="font-semibold text-gray-800 dark:text-gray-100">
电子处方
</div>
<div <div
:class="{ :class="{
'bg-yellow-100 text-yellow-800': statusInfo.status === 0, 'bg-yellow-100 text-yellow-800': statusInfo.status === 0,
'bg-green-100 text-green-800': statusInfo.status === 1, 'bg-green-100 text-green-800': statusInfo.status === 1,
'bg-red-100 text-red-800': statusInfo.status === 2, 'bg-red-100 text-red-800': statusInfo.status === 2,
'bg-blue-100 text-blue-800': statusInfo.status === 3, 'bg-blue-100 text-blue-800': statusInfo.status === 3,
'bg-gray-100 text-gray-800': statusInfo.status === 4 'bg-gray-100 text-gray-800': statusInfo.status === 4,
}" }"
class="text-xs px-2 py-1 rounded-full font-medium" class="rounded-full px-2 py-1 text-xs font-medium"
> >
{{ checkPrescriptionStatus(message.content.id) || statusInfo.text || '加载中' }} {{
checkPrescriptionStatus(message.content.id) ||
statusInfo.text ||
'加载中'
}}
</div> </div>
</div> </div>
<div class="mt-1 flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300"> <div
class="mt-1 flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300"
>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<i class="fas fa-hashtag text-xs"></i> <i class="fas fa-hashtag text-xs"></i>
<span class="truncate max-w-[100px]">{{ message.content.order_no }}</span> <span class="max-w-[100px] truncate">{{
message.content.order_no
}}</span>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@@ -542,21 +583,107 @@ if (props.message.type === 'register') {
</div> </div>
</div> </div>
<div class="mt-3 pt-2 border-t border-gray-200 dark:border-gray-600 flex items-center justify-between"> <div
class="mt-3 flex items-center justify-between border-t border-gray-200 pt-2 dark:border-gray-600"
>
<div class="text-sm font-medium text-gray-700 dark:text-gray-200"> <div class="text-sm font-medium text-gray-700 dark:text-gray-200">
支付金额: <span class="text-red-500 dark:text-red-400">¥{{ message.content.total_pay_price }}</span> 支付金额:
<span class="text-red-500 dark:text-red-400">
¥{{ message.content.total_pay_price }}
</span>
</div> </div>
<div class="flex items-center text-blue-500 dark:text-blue-300 hover:text-blue-600 dark:hover:text-blue-200 transition-colors"> <div
class="flex items-center text-blue-500 transition-colors hover:text-blue-600 dark:text-blue-300 dark:hover:text-blue-200"
>
<span class="text-sm font-medium">查看处方</span> <span class="text-sm font-medium">查看处方</span>
<i class="fas fa-chevron-right ml-1 text-xs"></i> <i class="fas fa-chevron-right ml-1 text-xs"></i>
</div> </div>
</div> </div>
</div> </div>
<!-- 在线复诊|购药信息 -->
<div
v-else-if="message.type === 'follow-up-visit'"
class="group transition-all duration-200"
>
<!-- 卡片容器 -->
<div
:class="{
'border-green-200': message.content.hasVisited === '1', // 已到访状态
'border-yellow-200': message.content.hasVisited === '0', // 未到访状态
}"
class="rounded-lg border p-4 shadow-sm"
>
<!-- 标题区域 -->
<div class="mb-3 flex items-center justify-between">
<h3 class="flex items-center text-lg font-semibold">
<svg
class="mr-2 h-4 w-4 text-green-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>
{{ message.content.drug_name }} 复诊信息
</h3>
<span
:class="{
'text-green-800': message.content.hasVisited === '1',
'bg-yellow-100 text-yellow-800':
message.content.hasVisited === '0',
}"
class="rounded-full px-2 py-1 text-xs"
>
{{ message.content.hasVisited === '1' ? '就诊过' : '未就诊过' }}
</span>
</div>
<!-- 症状信息 -->
<div class="space-y-2">
<p class="flex items-start text-sm">
<span class="w-20">症状描述:</span>
<span class="flex-1">{{ message.content.illnessInfo }}</span>
</p>
<!-- 用药状态 -->
<p
:class="{
'font-medium text-blue-600':
message.content.hasUsedDrug === '1',
'text-gray-500': message.content.hasUsedDrug === '0',
}"
class="text-sm"
>
<span class="w-20 text-gray-500">用药情况:</span>
{{
message.content.hasUsedDrug === '1' ? '使用过' : '未使用过'
}}
</p>
<!-- 医生信息 -->
<p
class="mt-3 border-t border-gray-100 pt-3 text-sm text-gray-500"
>
<span class="w-20 text-gray-400">主诊医生:</span>
ID: {{ message.content.doctor_id }}
</p>
</div>
</div>
</div>
<!-- 通话消息 --> <!-- 通话消息 -->
<div <div
v-else-if="message.type === 'video-call' || message.type === 'voice-call'" v-else-if="
message.type === 'video-call' || message.type === 'voice-call'
"
class="call-message" class="call-message"
> >
<div class="call-info"> <div class="call-info">
@@ -865,29 +992,29 @@ if (props.message.type === 'register') {
/* 挂号卡片样式 */ /* 挂号卡片样式 */
.register-card { .register-card {
@apply w-full max-w-xs md:max-w-sm cursor-pointer rounded-xl bg-white dark:bg-gray-700 p-4 shadow-sm transition-all duration-300; @apply w-full max-w-xs cursor-pointer rounded-xl bg-white p-4 shadow-sm transition-all duration-300 md:max-w-sm dark:bg-gray-700;
@apply border border-gray-200 dark:border-gray-600 hover:shadow-md hover:border-green-300 dark:hover:border-green-500; @apply border border-gray-200 hover:border-green-300 hover:shadow-md dark:border-gray-600 dark:hover:border-green-500;
} }
.message-bubble.sent .register-card { .message-bubble.sent .register-card {
@apply bg-green-50/30 dark:bg-green-900/30 border-green-200/50 dark:border-green-700/70; @apply border-green-200/50 bg-green-50/30 dark:border-green-700/70 dark:bg-green-900/30;
} }
.register-card:hover { .register-card:hover {
@apply transform -translate-y-0.5; @apply -translate-y-0.5 transform;
} }
/* 处方卡片样式 - 使用TailwindCSS类替代 */ /* 处方卡片样式 - 使用TailwindCSS类替代 */
.prescription-card { .prescription-card {
@apply w-full max-w-xs md:max-w-sm cursor-pointer rounded-xl bg-white dark:bg-gray-700 p-4 shadow-sm transition-all duration-300; @apply w-full max-w-xs cursor-pointer rounded-xl bg-white p-4 shadow-sm transition-all duration-300 md:max-w-sm dark:bg-gray-700;
@apply border border-gray-200 dark:border-gray-600 hover:shadow-md hover:border-blue-300 dark:hover:border-blue-500; @apply border border-gray-200 hover:border-blue-300 hover:shadow-md dark:border-gray-600 dark:hover:border-blue-500;
} }
.message-bubble.sent .prescription-card { .message-bubble.sent .prescription-card {
@apply bg-blue-50/30 dark:bg-blue-900/30 border-blue-200/50 dark:border-blue-700/70; @apply border-blue-200/50 bg-blue-50/30 dark:border-blue-700/70 dark:bg-blue-900/30;
} }
.prescription-card:hover { .prescription-card:hover {
@apply transform -translate-y-0.5; @apply -translate-y-0.5 transform;
} }
</style> </style>

View File

@@ -32,7 +32,7 @@ import {
} from 'ant-design-vue'; } from 'ant-design-vue';
import { usePrescriptionStore } from '#/store/prescription'; import { usePrescriptionStore } from '#/store/prescription';
// import { getTraditionalChineseMedicineAllApi } from '#/views/doctor/doctor-reception/api';
// 导入子组件 // 导入子组件
import DiagnosisModal from '#/views/doctor/doctor-reception/components/DiagnosisModal.vue'; import DiagnosisModal from '#/views/doctor/doctor-reception/components/DiagnosisModal.vue';
import DoctorOrderModal from '#/views/doctor/doctor-reception/components/DoctorOrderModal.vue'; import DoctorOrderModal from '#/views/doctor/doctor-reception/components/DoctorOrderModal.vue';
@@ -82,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({
await prescriptionStore.initializePrescription( await prescriptionStore.initializePrescription(
modalData.value.registerId, modalData.value.registerId,
); );
prescriptionStore.loadFromLocalStorage() prescriptionStore.loadFromLocalStorage();
} else { } else {
console.warn('未提供registerId'); console.warn('未提供registerId');
} }
@@ -228,6 +228,20 @@ const openPrescriptionDetail = (prescription: any) => {
}); });
PrescriptionDetailModalApi.open(); PrescriptionDetailModalApi.open();
}; };
/**
* 拆选
* @param input
* @param option
*/
const filterOption = (input: string, option: any) => {
return (
option.initials_of_pinyin.includes(input) ||
option.name.includes(input) ||
option.alias.includes(input) ||
option.code.includes(input)
);
};
</script> </script>
<template> <template>
@@ -710,7 +724,16 @@ const openPrescriptionDetail = (prescription: any) => {
</div> </div>
</Card> </Card>
</Col> </Col>
<Col v-if="prescriptionStore.currentDrugs.length < 3" style="opacity: 0" :lg="12" :md="24" :sm="24" :xl="12" :xs="24" :xxl="8"> <Col
v-if="prescriptionStore.currentDrugs.length < 3"
:lg="12"
:md="24"
:sm="24"
:xl="12"
:xs="24"
:xxl="8"
style="opacity: 0"
>
<Card class="prescription-card" title=""> <Card class="prescription-card" title="">
<div> <div>
<span class="card-index">{{ prescriptionStore.currentDrugs.length + 1 }}、</span> <span class="card-index">{{ prescriptionStore.currentDrugs.length + 1 }}、</span>
@@ -778,7 +801,16 @@ const openPrescriptionDetail = (prescription: any) => {
</div> </div>
</Card> </Card>
</Col> </Col>
<Col v-if="prescriptionStore.currentDrugs.length < 3" style="opacity: 0" :lg="12" :md="24" :sm="24" :xl="12" :xs="24" :xxl="8"> <Col
v-if="prescriptionStore.currentDrugs.length < 3"
:lg="12"
:md="24"
:sm="24"
:xl="12"
:xs="24"
:xxl="8"
style="opacity: 0"
>
<Card class="prescription-card" title=""> <Card class="prescription-card" title="">
<div> <div>
<span class="card-index">{{ prescriptionStore.currentDrugs.length + 1 }}、</span> <span class="card-index">{{ prescriptionStore.currentDrugs.length + 1 }}、</span>
@@ -918,15 +950,110 @@ const openPrescriptionDetail = (prescription: any) => {
<!-- 诊断和医嘱区域 --> <!-- 诊断和医嘱区域 -->
<div class="diagnosis-area"> <div class="diagnosis-area">
<!-- 中医证候 -->
<div v-if="prescriptionStore.activeCategory === 1" class="mb-3">
<div class="mb-2 flex items-center justify-between">
<label class="font-medium">中医证候</label>
<!-- <Button type="primary" @click="openDiagnosisModal">中医证候</Button>-->
</div>
<!-- <Select-->
<!-- v-if="traditionalChineseMedicine"-->
<!-- v-model:value="diseasesSelected"-->
<!-- :filter-option="false"-->
<!-- class="w-full mt-3"-->
<!-- placeholder="中医证候"-->
<!-- show-search-->
<!-- @search="searchTraditionalDiseases"-->
<!-- >-->
<Select
v-if="prescriptionStore.traditionalChineseMedicine"
v-model:value="prescriptionStore.diseasesSelected"
:field-names="{
label: 'name',
value: 'id',
}"
:filter-option="filterOption"
:options="prescriptionStore.traditionalChineseMedicine.diseases"
class="mt-3 w-full"
placeholder="中医证候"
show-search
>
<!-- <SelectOption-->
<!-- v-for="(value, index) in traditionalChineseMedicine.diseases"-->
<!-- :key="index"-->
<!-- :value="value.id"-->
<!-- >-->
<!-- {{ value.name }}({{ value.code }})-->
<!-- </SelectOption>-->
</Select>
<!-- <Textarea-->
<!-- v-model:value="prescriptionStore.diagnosis"-->
<!-- placeholder="输入中医证候..."-->
<!-- disabled-->
<!-- />-->
</div>
<!-- 中医治法 -->
<div v-if="prescriptionStore.activeCategory === 1" class="mb-3">
<div class="mb-2 flex items-center justify-between">
<label class="font-medium">中医治法</label>
<!-- <Button type="primary" @click="openDiagnosisModal">中医治法</Button>-->
</div>
<Select
v-if="prescriptionStore.traditionalChineseMedicine"
v-model:value="prescriptionStore.methodSelected"
:field-names="{
label: 'name',
value: 'id',
}"
:filter-option="filterOption"
:options="prescriptionStore.traditionalChineseMedicine.method"
class="mt-3 w-full"
placeholder="中医证候"
show-search
/>
<!-- <Textarea-->
<!-- v-model:value="prescriptionStore.diagnosis"-->
<!-- placeholder="输入中医治法..."-->
<!-- disabled-->
<!-- />-->
</div>
<!-- 中医疾病 -->
<div v-if="prescriptionStore.activeCategory === 1" class="mb-3">
<div class="mb-2 flex items-center justify-between">
<label class="font-medium">中医疾病</label>
<!-- <Button type="primary" @click="openDiagnosisModal">中医疾病</Button>-->
</div>
<Select
v-if="prescriptionStore.traditionalChineseMedicine"
v-model:value="prescriptionStore.syndromeSelected"
:field-names="{
label: 'name',
value: 'id',
}"
:filter-option="filterOption"
:options="prescriptionStore.traditionalChineseMedicine.syndrome"
class="mt-3 w-full"
placeholder="中医证候"
show-search
/>
<!-- <Textarea-->
<!-- v-model:value="prescriptionStore.diagnosis"-->
<!-- placeholder="输入中医疾病..."-->
<!-- disabled-->
<!-- />-->
</div>
<!-- 诊断输入 --> <!-- 诊断输入 -->
<div class="mb-3"> <div class="mb-3">
<div class="mb-2 flex items-center justify-between"> <div class="mb-2 flex items-center justify-between">
<label class="font-medium">诊断结果</label> <label class="font-medium">常用诊断</label>
<Button type="primary" @click="openDiagnosisModal">常用诊断</Button> <Button type="primary" @click="openDiagnosisModal">常用诊断</Button>
</div> </div>
<Textarea <Textarea
v-model:value="prescriptionStore.diagnosis" v-model:value="prescriptionStore.diagnosis"
placeholder="输入诊断结果..." disabled
placeholder="输入常用诊断..."
/> />
</div> </div>

View File

@@ -237,7 +237,7 @@ export const useChatStore = defineStore('chat', () => {
id: message.id, id: message.id,
type: getMessageType(message.message_type), type: getMessageType(message.message_type),
content: content:
message.message_type === 4 || message.message_type === 10 message.message_type === 4 || message.message_type === 10 || message.message_type === 11
? JSON.parse(message.message_content) ? JSON.parse(message.message_content)
: message.message_content, : message.message_content,
time: message.created_at_text, time: message.created_at_text,
@@ -383,6 +383,7 @@ export const useChatStore = defineStore('chat', () => {
'file', 'file',
'', '',
'register', 'register',
'follow-up-visit',
]; ];
return types[type] || 'text'; return types[type] || 'text';
}; };

View File

@@ -77,6 +77,12 @@ function getColor(status: any) {
} }
} }
} }
const orderTypeMap = {
1: '处方订单',
2: '预约购药订单',
3: '商城处方订单',
}
</script> </script>
<template> <template>
<Modal class="w-[80%]" title="订单详情"> <Modal class="w-[80%]" title="订单详情">
@@ -91,7 +97,7 @@ function getColor(status: any) {
{{ data.order_no }} {{ data.order_no }}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="订单类型"> <Descriptions.Item label="订单类型">
{{ data.order_type === 1 ? '普通订单' : '其他' }} {{ orderTypeMap[data.order_type] || '其他' }}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="总支付价格"> <Descriptions.Item label="总支付价格">
{{ data.total_pay_price }} 元 {{ data.total_pay_price }} 元
@@ -99,7 +105,7 @@ function getColor(status: any) {
<Descriptions.Item label="物品总价"> <Descriptions.Item label="物品总价">
{{ data.items_price }} 元 {{ data.items_price }} 元
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="市场价"> <Descriptions.Item label="供货价">
{{ data.market_price }} 元 {{ data.market_price }} 元
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="运费"> <Descriptions.Item label="运费">
@@ -216,7 +222,7 @@ function getColor(status: any) {
</div> </div>
</Card> </Card>
</div> </div>
<div class="mt-4"> <div class="mt-4" v-if="data.order_type !== 2 && data.order_type !== 3 && data.prescription.content">
<h3>处方信息</h3> <h3>处方信息</h3>
<Descriptions <Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }" :column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"

View File

@@ -2,6 +2,7 @@ import type { VbenFormProps } from '#/adapter/form';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import {getOrderStatusOption} from "#/views/business/order/product-order/api"; import {getOrderStatusOption} from "#/views/business/order/product-order/api";
import {getStoreOption} from "#/views/system/store/api";
export const formOptions: VbenFormProps = { export const formOptions: VbenFormProps = {
// 默认展开 // 默认展开
@@ -16,6 +17,25 @@ export const formOptions: VbenFormProps = {
fieldName: 'order_no', fieldName: 'order_no',
label: '订单号', label: '订单号',
}, },
{
component: 'ApiSelect',
componentProps: {
allowClear: true,
filterOption: true,
// showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: `${item.name}【${item.id}】`,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '请选择',
},
fieldName: 'store_id',
label: '门店',
},
{ {
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {

View File

@@ -174,7 +174,7 @@ const openRefundModal = (id) => {
<Modal /> <Modal />
<RefundModal /> <RefundModal />
<PrescriptionDetailModal /> <PrescriptionDetailModal />
<AnalysisOverview :items="overviewItems" :my-card="false" class="mt-5" /> <AnalysisOverview :items="overviewItems" :my-card="false" />
<Grid> <Grid>
<template #toolbar-buttons> <template #toolbar-buttons>
<TableAction <TableAction
@@ -233,16 +233,28 @@ const openRefundModal = (id) => {
<Tag v-else color="red">未同步</Tag> <Tag v-else color="red">未同步</Tag>
</template> </template>
<template #delivery-method="{ row }"> <template #delivery-method="{ row }">
<div v-if="row.order_type === 1"> <!-- 判断是否为非处方订单-->
<Tag v-if="row.prescription_type === 1" color="orange">中药订单</Tag> <div v-if="row.order_type === 2">
<Tag v-if="row.order_type === 1" color="yellow">处方订单</Tag>
<Tag v-else-if="row.order_type === 2" color="yellow">
预约购药订单
</Tag>
<Tag v-else-if="row.order_type === 3" color="yellow">
商城处方订单
</Tag>
</div>
<div v-else>
<Tag v-if="row.prescription_type === 1" color="orange">
处方订单【中药】
</Tag>
<Tag v-else-if="row.prescription_type === 2" color="blue"> <Tag v-else-if="row.prescription_type === 2" color="blue">
商品订单【西药】 处方订单【西药】
</Tag> </Tag>
<Tag v-else-if="row.prescription_type === 3" color="purple"> <Tag v-else-if="row.prescription_type === 3" color="purple">
商品订单【非处方药】 处方订单【非处方药】
</Tag> </Tag>
<Tag v-else-if="row.prescription_type === 4" color="green"> <Tag v-else-if="row.prescription_type === 4" color="green">
已退款 处方订单【西药】
</Tag> </Tag>
<Tag v-else-if="row.prescription_type === 5" color="pink"> <Tag v-else-if="row.prescription_type === 5" color="pink">
产品服务包 产品服务包
@@ -283,6 +295,7 @@ const openRefundModal = (id) => {
label: '处方', label: '处方',
type: 'link', type: 'link',
icon: 'marketeq:eye', icon: 'marketeq:eye',
ifShow: row.order_type !== 2 && row.order_type !== 3,
// auth: ['超级订单', 'sys:user:save'], // auth: ['超级订单', 'sys:user:save'],
onClick: openPrescriptionDetail.bind(null, row.p_id), onClick: openPrescriptionDetail.bind(null, row.p_id),
}, },

View File

@@ -175,6 +175,27 @@ export const modalFormProps: VbenFormProps = {
label: '商品状态', label: '商品状态',
rules: 'required', rules: 'required',
}, },
{
component: 'RadioGroup',
componentProps: {
placeholder: '请选择',
options: [
{
label: '否',
value: 0,
},
{
label: '是',
value: 1,
},
],
},
defaultValue: 1,
formItemClass: 'col-span-12',
fieldName: 'is_otc',
label: '是否处方药',
rules: 'required',
},
{ {
component: 'Textarea', component: 'Textarea',
componentProps: { componentProps: {
@@ -184,6 +205,24 @@ export const modalFormProps: VbenFormProps = {
label: '药品主治功能', label: '药品主治功能',
rules: 'required', rules: 'required',
}, },
// {
// component: 'Textarea',
// componentProps: {
// placeholder: '请输入对应症状',
// },
// fieldName: 'indications',
// label: '对应症状',
// rules: 'required',
// },
{
component: 'InputSelect',
componentProps: {
placeholder: '请输入对应症状',
},
fieldName: 'indications',
label: '对应症状',
rules: 'required',
},
{ {
component: 'Avatar', component: 'Avatar',
fieldName: 'instruction', fieldName: 'instruction',

View File

@@ -45,7 +45,7 @@ export const gridOptions: VxeGridProps<RowType> = {
{ field: 'specification', title: '规格' }, { field: 'specification', title: '规格' },
{ field: 'status', title: '状态', slots: { default: 'status' } }, { field: 'status', title: '状态', slots: { default: 'status' } },
{ field: 'created_at', title: '发布时间' }, { field: 'created_at', title: '发布时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } }, { type: 'html', title: '操作', width: 230, slots: { default: 'action' } },
], ],
keepSource: true, keepSource: true,
pagerConfig: {}, pagerConfig: {},

View File

@@ -95,6 +95,7 @@ const openExcelUploadModal = () => {
<Grid> <Grid>
<template #toolbar-buttons> <template #toolbar-buttons>
<TableAction <TableAction
:flex="false"
:actions="[ :actions="[
{ {
label: '新增', label: '新增',

View File

@@ -67,3 +67,10 @@ export async function salespersonListApi() {
export async function salespersonCreateApi(data) { export async function salespersonCreateApi(data) {
return requestClient.post<any>(`salesperson/create`, data); return requestClient.post<any>(`salesperson/create`, data);
} }
/**
* 编辑推广员
* @param data
*/
export async function salespersonUpdateApi(data) {
return requestClient.post<any>(`salesperson/update`, data);
}

View File

@@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVbenModal } from '@vben/common-ui'; import {useVbenModal} from '@vben/common-ui';
import { Button, Card, message } from 'ant-design-vue'; import {Button, Card, message} from 'ant-design-vue';
import { PlusOutlined } from '@ant-design/icons-vue'; import {PlusOutlined} from '@ant-design/icons-vue';
import QrCodePreview from '#/views/business/store/settings/components/SalespersonQrCodePreview.vue'; import QrCodePreview from '#/views/business/store/settings/components/SalespersonQrCodePreview.vue';
import salespersonCreateModal from "#/views/business/store/settings/components/SalespersonCreateModal.vue"; import salespersonCreateModal
from "#/views/business/store/settings/components/SalespersonCreateModal.vue";
import {ref} from "vue"; import {ref} from "vue";
import {salespersonListApi} from "#/views/business/store/settings/api"; import {salespersonListApi} from "#/views/business/store/settings/api";
@@ -40,84 +41,87 @@ const openSalespersonModal = () => {
SalespersonCreateModalApi.open(); SalespersonCreateModalApi.open();
}; };
const openUpdateSalespersonModal = (values) => {
SalespersonCreateModalApi.setData({
values,
getList,
update: true
})
SalespersonCreateModalApi.open();
};
</script> </script>
<template> <template>
<div <div>
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" <SalespersonCreateModal/>
> <QrCodePreviewModal/>
<SalespersonCreateModal /> <Card :title="`您拥有${data.total}个推广员`" class="w-full">
<QrCodePreviewModal /> <template #extra>
<Card <Button type="primary" @click="openSalespersonModal">新增推广员</Button>
class="rounded-xl shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer border border-gray-200 p-6 items-center justify-center" </template>
@click="openSalespersonModal" <div
> class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
<!-- 内容容器:居中布局 + 垂直排列 --> >
<div class="flex flex-col items-center justify-center gap-3"> <Card
<!-- 图标增强视觉引导 --> v-for="(item, index) in data.items"
<PlusOutlined class="text-blue-500 text-2xl transition-transform duration-200 hover:scale-110" /> :key="index"
class="rounded-lg shadow-sm"
<!-- 按钮文字居中 + 优化字体样式 -->
<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 class="mb-4 flex items-start justify-between">
</div> <h3 class="text-lg font-semibold ">
</Card> {{ item.nick_name || '未知' }}
<Card </h3>
v-for="(item, index) in data.items" </div>
: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="mb-4 space-y-2 text-sm">
<div class="flex justify-between"> <div class="flex justify-between">
<span>手机号:</span> <span>手机号:</span>
<span class="text-gray-800">{{ item.phone }}</span> <span class="">{{ item.phone }}</span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span>加入时间:</span> <span>加入时间:</span>
<span>{{ <span>{{
item.created_at item.created_at
}}</span> }}</span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span>累计获客:</span> <span>累计获客:</span>
<span class="font-medium text-blue-600">100</span> <span class="font-medium text-blue-600">{{ item.count.user_number }}</span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span>本月获客:</span> <span>总佣金:</span>
<span class="font-medium text-blue-600">10</span> <span class="font-medium text-blue-600">{{ item.count.money }}</span>
</div> </div>
</div> </div>
<!-- 二维码区域 --> <!-- 二维码区域 -->
<div class="border-t border-gray-100 pt-4"> <div class="border-t border-gray-100 pt-4">
<p class="mb-2 text-sm text-gray-600">联系二维码:</p> <p class="mb-2 text-sm">联系二维码:</p>
<div <div
v-if="item.qr_code.qr_code" v-if="item.qr_code.qr_code"
class="cursor-pointer" class="cursor-pointer"
@click="openDoctorQrCode(item)" >
> <Button
<Button class="w-full" size="small" type="primary">查看二维码</Button> class="w-full"
</div> size="small"
<div v-else class="py-2 text-center text-sm text-gray-400"> type="primary"
暂无二维码 @click="openDoctorQrCode(item)"
</div> >
查看二维码
</Button>
</div>
<div v-else class="py-2 text-center text-sm text-gray-400">
暂无二维码
</div>
<Button @click="openUpdateSalespersonModal(item)" class="w-full mt-3" size="small"
type="primary">编辑
</Button>
</div>
</Card>
</div> </div>
</Card> </Card>
</div> </div>

View File

@@ -6,13 +6,16 @@ import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import {salespersonCreateApi, saveNavUrlApi} from '#/views/business/store/settings/api'; import {
salespersonCreateApi,
salespersonUpdateApi,
saveNavUrlApi
} from '#/views/business/store/settings/api';
import { salespersonModalFormProps } from '../config/form'; import { salespersonModalFormProps } from '../config/form';
const isUpdate = ref(false); const isUpdate = ref(false);
const gridApi = ref(); const gridApi = ref();
const getMyInfoApi = ref();
const [Form, formApi] = useVbenForm(salespersonModalFormProps); const [Form, formApi] = useVbenForm(salespersonModalFormProps);
@@ -27,12 +30,13 @@ const [Modal, modalApi] = useVbenModal({
if (e.valid) { if (e.valid) {
const values = await formApi.getValues(); const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true }); modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = salespersonCreateApi; const submitApi = isUpdate.value
? salespersonUpdateApi
: salespersonCreateApi;
submitApi(values) submitApi(values)
.then(() => { .then(() => {
message.success('保存成功'); message.success('保存成功');
gridApi.value?.reload(); gridApi.value?.reload();
getMyInfoApi.value();
modalApi.close(); modalApi.close();
}) })
.finally(() => { .finally(() => {
@@ -43,9 +47,8 @@ const [Modal, modalApi] = useVbenModal({
await formApi.validateAndSubmitForm(); await formApi.validateAndSubmitForm();
}, },
onOpenChange(isOpen: boolean) { onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null; gridApi.value = isOpen ? modalApi.getData()?.getList : null;
if (isOpen) { if (isOpen) {
getMyInfoApi.value = isOpen ? modalApi.getData()?.getMyInfo : null;
const { values, update } = modalApi.getData<Record<string, any>>(); const { values, update } = modalApi.getData<Record<string, any>>();
if (values) { if (values) {
isUpdate.value = update; isUpdate.value = update;

View File

@@ -36,6 +36,16 @@ export const salespersonModalFormProps: VbenFormProps = {
// handleSubmit: onSubmit, // handleSubmit: onSubmit,
layout: 'horizontal', layout: 'horizontal',
schema: [ schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{ {
component: 'Input', component: 'Input',
fieldName: 'phone', fieldName: 'phone',
@@ -50,9 +60,32 @@ export const salespersonModalFormProps: VbenFormProps = {
fieldName: 'nick_name', fieldName: 'nick_name',
label: '名称', label: '名称',
rules: 'required', rules: 'required',
},
{
component: 'RadioGroup',
fieldName: 'split_type',
label: '分成类型',
rules: 'required',
componentProps: { componentProps: {
minLength: 11, placeholder: '请选择',
options: [
{
label: '元',
value: 0,
},
{
label: '百分比(%)',
value: 1,
},
],
}, },
defaultValue: 0,
},
{
component: 'Input',
fieldName: 'split',
label: '分成',
rules: 'required',
}, },
// { // {
// component: 'UploadImage', // component: 'UploadImage',

View File

@@ -93,12 +93,12 @@ const openExcelUploadModal = (type = 1) => {
ExcelUploadModalApi.open(); ExcelUploadModalApi.open();
}; };
const updateStatus = (id) => { const updateStatus = (id: number) => {
updateWarehouseDrugManagementStatusApi(id).then(() => { updateWarehouseDrugManagementStatusApi(id).then(() => {
message.success('修改成功!'); message.success('修改成功!');
gridApi.reload(); gridApi.reload();
}); });
} };
</script> </script>
<template> <template>
@@ -160,7 +160,14 @@ const updateStatus = (id) => {
</TableAction> </TableAction>
</template> </template>
<template #image="{ row }"> <template #image="{ row }">
<Image :src="row.drug?.image || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250510/cd470ebf97e31c4048ba502ba71b66a3.jpg'" height="30" width="30" /> <Image
:src="
row.drug?.image ||
'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250510/cd470ebf97e31c4048ba502ba71b66a3.jpg'
"
height="30"
width="30"
/>
</template> </template>
<template #instruction="{ row }"> <template #instruction="{ row }">
<div class="div" style="width: 120px; height: 120px; overflow: scroll"> <div class="div" style="width: 120px; height: 120px; overflow: scroll">
@@ -168,9 +175,9 @@ const updateStatus = (id) => {
</div> </div>
</template> </template>
<template #status="{ row }"> <template #status="{ row }">
<Tag :color="row.status === 2 ? 'green' : 'red'">{{ <Tag :color="row.status === 2 ? 'green' : 'red'">
row.status === 2 ? '上架' : '下架' {{ row.status === 2 ? '上架' : '下架' }}
}}</Tag> </Tag>
</template> </template>
<template #toolbar-tools></template> <template #toolbar-tools></template>
<template #action="{ row }"> <template #action="{ row }">

View File

@@ -102,3 +102,17 @@ export async function syncDrugApi(data: Record<string, any>) {
export async function syncDrugPriceApi(data: Record<string, any>) { export async function syncDrugPriceApi(data: Record<string, any>) {
return requestClient.get(`${prefix}sync-drug-price`, data); return requestClient.get(`${prefix}sync-drug-price`, data);
} }
/**
* 检查订阅总仓库价格api
*/
export async function checkSubscribeApi() {
return requestClient.get(`${prefix}check-subscribe`);
}
/**
* 订阅总仓库价格api
*/
export async function subscribeApi() {
return requestClient.get(`${prefix}subscribe`);
}

View File

@@ -12,8 +12,9 @@ import { TableAction } from '#/components/table-action';
import { downloadByData } from '#/util/tool'; import { downloadByData } from '#/util/tool';
import { import {
checkSubscribeApi,
deleteWarehouseDrugManagementStore, deleteWarehouseDrugManagementStore,
exportWarehouseDrugManagementStoreApi, syncDrugApi, syncDrugPriceApi, exportWarehouseDrugManagementStoreApi, subscribeApi, syncDrugApi, syncDrugPriceApi,
updateWarehouseDrugManagementStoreStatusApi updateWarehouseDrugManagementStoreStatusApi
} from './api'; } from './api';
import FormModalDemo from './components/modal.vue'; import FormModalDemo from './components/modal.vue';
@@ -116,6 +117,22 @@ const syncDrugPrice = () => {
gridApi.reload(); gridApi.reload();
}); });
} }
const isSubscribe = ref(true);
const checkSubscribe = () =>{
checkSubscribeApi().then((res) => {
isSubscribe.value = res;
})
};
const subscribe = () =>{
subscribeApi().then(() => {
message.success('操作成功!');
})
};
checkSubscribe();
</script> </script>
<template> <template>
@@ -148,6 +165,14 @@ const syncDrugPrice = () => {
// auth: ['超级西(中成)药', 'sys:user:save'], // auth: ['超级西(中成)药', 'sys:user:save'],
onClick: syncDrugPrice, onClick: syncDrugPrice,
}, },
{
label: '订阅仓库价格',
type: 'primary',
ifShow: !isSubscribe,
icon: 'gridicons:reader-following',
// auth: ['超级西(中成)药', 'sys:user:save'],
onClick: subscribe,
},
]" ]"
:drop-down-actions="[ :drop-down-actions="[
{ {

View File

@@ -172,16 +172,23 @@ const todayDate = getTodayDate();
<div class="mt-5 flex flex-col lg:flex-row"> <div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5"> <div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" /> <!-- <WorkbenchProject :items="projectItems" title="项目" @click="navTo" />-->
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" /> <!-- <WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />-->
</div>
<div class="w-full lg:w-2/5">
<WorkbenchQuickNav <WorkbenchQuickNav
:items="quickNavItems" :items="quickNavItems"
class="mt-5 lg:mt-0" class="mt-5 lg:mt-0"
title="快捷导航" title="快捷导航"
@click="navTo" @click="navTo"
/> />
</div>
<div class="w-full lg:w-2/5">
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
<!-- <WorkbenchQuickNav-->
<!-- :items="quickNavItems"-->
<!-- class="mt-5 lg:mt-0"-->
<!-- title="快捷导航"-->
<!-- @click="navTo"-->
<!-- />-->
<!-- <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />--> <!-- <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />-->
<!-- <AnalysisChartCard class="mt-5" title="访问来源">--> <!-- <AnalysisChartCard class="mt-5" title="访问来源">-->
<!-- <AnalyticsVisitsSource />--> <!-- <AnalyticsVisitsSource />-->

View File

@@ -642,8 +642,8 @@ function updateProductNumber(id, number) {
<PlusOutlined key="add" @click="addProducts(item)" /> <PlusOutlined key="add" @click="addProducts(item)" />
</template> </template>
<CardMeta <CardMeta
:description="item.drug?.pinyin_simple" :description="item.drug?.function"
:title="item.drug?.drug_name" :title="`${item.drug?.drug_name}(${item.drug?.pinyin_simple})`"
> >
<template #avatar> <template #avatar>
<a-avatar src="https://joeschmoe.io/api/v1/random" /> <a-avatar src="https://joeschmoe.io/api/v1/random" />
@@ -651,9 +651,9 @@ function updateProductNumber(id, number) {
</CardMeta> </CardMeta>
<div class="card-box mt-5" style="padding: 10px 5px"> <div class="card-box mt-5" style="padding: 10px 5px">
<Popover> <Popover>
<p class="drug-function text-xs"> <!-- <p class="drug-function text-xs">-->
功效:{{ item.drug?.function }} <!-- 功效:{{ item.drug?.function }}-->
</p> <!-- </p>-->
<template #content> <template #content>
<p>功效:{{ item.drug?.function }}</p> <p>功效:{{ item.drug?.function }}</p>
<p>用法:{{ item.drug?.usage }}</p> <p>用法:{{ item.drug?.usage }}</p>

View File

@@ -7,6 +7,14 @@ const prefix = 'settlement/';
export async function getSettlementList(data: any) { export async function getSettlementList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data }); return requestClient.get<any>(`${prefix}list`, { params: data });
} }
/**
* 分页查询用户列表
* @param data
*/
export async function getSettlementItemsList(data: any) {
data.type = 'items';
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/** /**
* 获取管理员详情 * 获取管理员详情

View File

@@ -31,6 +31,7 @@ const withdrawnFrozenAmount = ref(0);
const frozenAmount = ref(0); const frozenAmount = ref(0);
// const arrivedAmount = ref(0); // const arrivedAmount = ref(0);
const fee = ref(0); const fee = ref(0);
const isShow = ref(false);
const myCard = ref(); const myCard = ref();
const overviewItems = ref<AnalysisOverviewItem[]>([]); const overviewItems = ref<AnalysisOverviewItem[]>([]);
@@ -136,6 +137,10 @@ function refresh() {
showCard(); showCard();
} }
function updateShowStatus() {
isShow.value = !isShow.value;
}
const showModal = (data = {}) => { const showModal = (data = {}) => {
formModalApi.setData({ formModalApi.setData({
// 表单值 // 表单值
@@ -164,6 +169,9 @@ const showCardModal = () => {
:title="`${myCard?.user_name || myCard?.store?.name || myCard?.supplier?.name}--账户信息`" :title="`${myCard?.user_name || myCard?.store?.name || myCard?.supplier?.name}--账户信息`"
class="p-5" class="p-5"
> >
<!-- <template #extra>-->
<!-- <Button type="link" @click="updateShowStatus">{{ isShow === false ? '展开' : '收起' }}</Button>-->
<!-- </template>-->
<FormModal /> <FormModal />
<saveMyCardModal /> <saveMyCardModal />
<Button class="ml-5" type="primary" @click="showModal"> 申请提现 </Button> <Button class="ml-5" type="primary" @click="showModal"> 申请提现 </Button>

View File

@@ -4,6 +4,15 @@ export const formOptions2: VbenFormProps = {
// 默认展开 // 默认展开
collapsed: false, collapsed: false,
schema: [ schema: [
// {
// component: 'VbenInput',
// componentProps: {
// placeholder: '选择费用类型',
// },
// defaultValue: '',
// fieldName: 'order_no',
// label: '订单号',
// },
{ {
component: 'VbenSelect', component: 'VbenSelect',
componentProps: { componentProps: {

View File

@@ -1,6 +1,6 @@
import type { VxeGridProps } from '#/adapter/vxe-table'; import type { VxeGridProps } from '#/adapter/vxe-table';
import { getSettlementList } from '#/views/finance/withdrawal/api/settlement'; import {getSettlementItemsList, getSettlementList} from '#/views/finance/withdrawal/api/settlement';
interface RowType { interface RowType {
id: string; id: string;
@@ -40,6 +40,64 @@ export const gridOptions2: VxeGridProps<RowType> = {
], ],
keepSource: true, keepSource: true,
pagerConfig: {}, pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getSettlementItemsList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
// height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};
export const gridOptions3: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
// { type: 'checkbox', width: 60 },
// { field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'order_no', title: '订单号' },
{ field: 'user_id', title: '名称', slots: { default: 'user_id' } },
{ field: 'money', title: '分账金额' },
// { field: 'fee_type_txt', title: '费用类型' },
// { field: 'status_txt', title: '结算状态' },
{ field: 'created_at', title: '创建时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: { proxyConfig: {
ajax: { ajax: {
// 请求后端接口方法 // 请求后端接口方法

View File

@@ -1,14 +1,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import { AnalysisChartsTabs, Page } from '@vben/common-ui'; import { AnalysisChartsTabs, Page } from '@vben/common-ui';
import { FloatButton, FloatButtonGroup, Popover, 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 { Tag } from 'ant-design-vue';
import Statistics from './components/statistics.vue'; import Statistics from './components/statistics.vue';
import { formOptions } from './config/search'; import { formOptions } from './config/search';
import { formOptions2 } from './config/settlement-search'; import { formOptions2 } from './config/settlement-search';
import { gridOptions2 } from './config/settlement-table'; import { gridOptions2, gridOptions3 } from './config/settlement-table';
import { gridOptions } from './config/table'; import { gridOptions } from './config/table';
const [Grid, GridApi] = useVbenVxeGrid({ const [Grid, GridApi] = useVbenVxeGrid({
@@ -21,6 +24,11 @@ const [Grid2] = useVbenVxeGrid({
gridOptions: gridOptions2, gridOptions: gridOptions2,
}); });
const [Grid3] = useVbenVxeGrid({
formOptions: formOptions2,
gridOptions: gridOptions3,
});
const chartTabs = [ const chartTabs = [
{ {
label: '提现记录', label: '提现记录',
@@ -30,7 +38,17 @@ const chartTabs = [
label: '结算记录', label: '结算记录',
value: 'visits', value: 'visits',
}, },
{
label: '结算明细',
value: 'visitsItems',
},
]; ];
const isShow = ref(true);
function updateShowStatus() {
isShow.value = !isShow.value;
}
</script> </script>
<template> <template>
@@ -39,7 +57,7 @@ const chartTabs = [
description="提现前请确认好您的打款账户" description="提现前请确认好您的打款账户"
title="提现管理" title="提现管理"
> >
<Statistics :grid-api="GridApi" /> <Statistics v-show="isShow" :grid-api="GridApi" />
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5"> <AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
<template #trends> <template #trends>
<Grid> <Grid>
@@ -69,7 +87,9 @@ const chartTabs = [
<span v-else-if="row.dakuan_status === 1">打款成功</span> <span v-else-if="row.dakuan_status === 1">打款成功</span>
</template> </template>
<template #check_id="{ row }"> <template #check_id="{ row }">
<span>{{ row.check_admin?.username || row.check_admin?.nick_name || '暂无' }}</span> <span>{{
row.check_admin?.username || row.check_admin?.nick_name || '暂无'
}}</span>
</template> </template>
<template #action="{ row }"> <template #action="{ row }">
<TableAction <TableAction
@@ -88,6 +108,34 @@ const chartTabs = [
</Grid> </Grid>
</template> </template>
<template #visits> <template #visits>
<!-- 提现记录 --->
<Grid3>
<template #toolbar-buttons></template>
<template #toolbar-tools></template>
<template #user_id="{ row }">
<span v-if="row.user_type === 1">{{ row.store.name }}</span>
<span v-else-if="row.user_type === 2">萧康平台</span>
<span v-else-if="row.user_type === 3">{{ row.supplier.name }}</span>
</template>
<template #action="{ row }">
<TableAction
:actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'uil:edit',
// size: 'small',
// // auth: ['admin', 'sys:role:detail'],
// onClick: showModal.bind(null, row, true),
// },
]"
:drop-down-actions="[]"
/>
</template>
</Grid3>
</template>
<template #visitsItems>
<!-- 结算明细 --->
<Grid2> <Grid2>
<template #toolbar-buttons></template> <template #toolbar-buttons></template>
<template #toolbar-tools></template> <template #toolbar-tools></template>
@@ -114,5 +162,24 @@ const chartTabs = [
</Grid2> </Grid2>
</template> </template>
</AnalysisChartsTabs> </AnalysisChartsTabs>
<!-- 浮动按钮组 -->
<FloatButtonGroup :style="{ right: '24px', bottom: '84px' }" shape="circle">
<!-- 打开显示余额卡片的按钮 -->
<Popover placement="left" title="显示余额卡片">
<template #content>
<p>点击展开卡片信息,再次点击收起</p>
</template>
<FloatButton
:badge="{ dot: true }"
:type="isShow === true ? 'primary' : 'dashed'"
shape="circle"
@click="updateShowStatus"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="M21.5 16.052V7.948a4.14 4.14 0 0 0-1.236-2.945a4.25 4.25 0 0 0-2.985-1.22H6.72a4.25 4.25 0 0 0-2.985 1.22A4.14 4.14 0 0 0 2.5 7.948v8.104c0 1.105.445 2.164 1.236 2.945a4.25 4.25 0 0 0 2.985 1.22H17.28c1.12 0 2.193-.44 2.985-1.22a4.14 4.14 0 0 0 1.236-2.945"/><path d="M8.552 12.14a2.054 2.054 0 1 0 0-4.108a2.054 2.054 0 0 0 0 4.108m3.081 3.828c0-.812-.324-1.59-.902-2.165a3.09 3.09 0 0 0-4.358 0a3.05 3.05 0 0 0-.902 2.165m9.097-7.049h3.594M14.568 12h1.54m-1.54 3.081h3.594"/></g></svg>
</template>
</FloatButton>
</Popover>
</FloatButtonGroup>
</Page> </Page>
</template> </template>

View File

@@ -50,7 +50,8 @@ const [Modal, modalApi] = useVbenModal({
{{ data.controller }} {{ data.controller }}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="IP"> <Descriptions.Item label="IP">
{{ data.ip === '127.0.0.1' ? '本地' : data.ip }} IP地址:{{ data.ip }}
<p>归属地:{{data.ip_address}}</p>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="类型"> <Descriptions.Item label="类型">
{{ data.type === 0 ? '访问成功' : '访问失败' }} {{ data.type === 0 ? '访问成功' : '访问失败' }}

View File

@@ -6,6 +6,18 @@ export const formOptions: VbenFormProps = {
// 默认展开 // 默认展开
collapsed: false, collapsed: false,
schema: [ schema: [
{
component: 'VbenInput',
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'user_id',
label: '用户ID',
},
{
component: 'VbenInput',
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'router',
label: 'api接口',
},
{ {
component: 'RangePicker', component: 'RangePicker',
componentProps: { componentProps: {
@@ -22,7 +34,7 @@ export const formOptions: VbenFormProps = {
content: '查询', content: '查询',
}, },
// 是否在字段值改变时提交表单 // 是否在字段值改变时提交表单
submitOnChange: true, submitOnChange: false,
// 按下回车时是否提交表单 // 按下回车时是否提交表单
submitOnEnter: false, submitOnEnter: true,
}; };

View File

@@ -33,6 +33,7 @@ export const gridOptions: VxeGridProps<RowType> = {
}, },
{ field: 'url', title: '访问路由' }, { field: 'url', title: '访问路由' },
{ field: 'ip', title: '用户IP地址' }, { field: 'ip', title: '用户IP地址' },
{ field: 'ip_address', title: 'IP归属地' },
{ field: 'controller', title: '访问控制器' }, { field: 'controller', title: '访问控制器' },
// { field: 'param', title: '携带参数' }, // { field: 'param', title: '携带参数' },
{ field: 'equipment', title: '操作系统', slots: { default: 'equipment' }, width: 80 }, { field: 'equipment', title: '操作系统', slots: { default: 'equipment' }, width: 80 },

View File

@@ -68,7 +68,7 @@ export const modalFormProps: VbenFormProps = {
// 自定义过滤逻辑,确保可以根据 name 进行搜索 // 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0; return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
}, },
showSearch: true, // showSearch: true,
// 菜单接口转options格式 // 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => { afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({ return data.map((item: any) => ({