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

View File

@@ -1,25 +1,39 @@
<script setup>
import {computed, ref} from 'vue';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
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 {getPrescriptionCheckStatusApi} from "#/views/business/order/prescription/api";
import { getChatMessageRegisterInfoApi } from '#/views/business/chat/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 RefusalOfTreatmentModal from '#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue';
import previewMedia from '../composables/useMediaPreview.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 { useThemeStore } from '../stores/theme.ts';
import { useUserStore as chatUseUserStore } from '../stores/user';
import {sendMessage} from "#/views/business/chat/utils/request";
import RefusalOfTreatmentModal
from "#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue";
import AudioMessage from './AudioMessage.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 userStore = useUserStore();
const chatStore = useChatStore();
@@ -29,23 +43,6 @@ const [RefusalOfTreatmentModals, RefusalOfTreatmentModalApi] = useVbenModal({
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(() => ({
background: props.senderInfo.color,
}));
@@ -85,18 +82,20 @@ const payStatusMap = {
const checkPrescriptionStatus = (id) => {
if (!id) return;
getPrescriptionCheckStatusApi({id}).then((res) => {
getPrescriptionCheckStatusApi({ id }).then((res) => {
statusInfo.value.status = res.status;
statusInfo.value.text = prescriptionStatusMap[res.status];
return prescriptionStatusMap[res.status]
})
return prescriptionStatusMap[res.status];
});
};
const getRegisterCardInfo = () => {
getChatMessageRegisterInfoApi({ id: props.message.content.id }).then((res) => {
// eslint-disable-next-line vue/no-mutating-props
props.message.content = res;
});
getChatMessageRegisterInfoApi({ id: props.message.content.id }).then(
(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;
try {
// 模拟API调用
@@ -124,7 +122,8 @@ const acceptRegister = async (registerId, orderNo) => {
read: true,
duration: 0,
};
localStorage.setItem('currentRegisterId', reregisterId);
// chatStore.currentFriend?.register_id = reregisterId;
chatStore.addMessage(messageObj, chatUserStore.currentUser.id);
await sendMessage({
senderId: chatUserStore.currentUser.id,
@@ -134,7 +133,6 @@ const acceptRegister = async (registerId, orderNo) => {
content: '我已接诊,请简单说明您的情况~',
});
});
} catch (error) {
console.error('接诊失败:', error);
} finally {
@@ -192,7 +190,7 @@ const formatDuration = (seconds) => {
};
const formatTextWithLinks = (text) => {
console.log(text, props.message)
console.log(text, props.message);
if (!text) return text; // 或 return text (但可能返回 undefined/null根据需求调整)
const urlRegex = /(https?:\/\/\S+)/g;
return text.replaceAll(
@@ -231,9 +229,9 @@ const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
const viewPrescription = (item) => {
PrescriptionDetailModalApi.setData({
values: item.id,
})
});
PrescriptionDetailModalApi.open();
}
};
// 格式化日期时间
const formatDateTime = (timestamp) => {
@@ -244,7 +242,7 @@ const formatDateTime = (timestamp) => {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
minute: '2-digit',
});
};
@@ -253,7 +251,6 @@ const getSexText = (sex) => {
return sex === 1 ? '男' : '女';
};
if (props.message.type === 'register') {
getRegisterCardInfo();
}
@@ -270,16 +267,22 @@ if (props.message.type === 'register') {
<div
v-if="isSent"
:style="avatarStyle"
class="rounded-full cursor-pointer"
class="cursor-pointer rounded-full"
@click="showUserProfile"
>
<Avatar v-if="userStore.userInfo?.avatar" :size="48" :src="userStore.userInfo?.avatar" />
<Avatar v-else :size="48">{{ userStore.userInfo?.nick_name.charAt(0) }}</Avatar>
<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
v-else
:style="avatarStyle"
class="rounded-full cursor-pointer"
class="cursor-pointer rounded-full"
@click="showUserProfile"
>
<Avatar v-if="senderInfo.avatar" :size="48" :src="senderInfo.avatar" />
@@ -289,10 +292,13 @@ if (props.message.type === 'register') {
<!-- 消息内容区域 -->
<div
: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 }}
</div>
<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
:src="message.content"
alt="图片消息"
class="rounded-lg max-w-full"
class="max-w-full rounded-lg"
@error="handleImageError"
/>
</div>
@@ -323,7 +329,7 @@ if (props.message.type === 'register') {
>
<video
:src="message.content"
class="rounded-lg max-w-full video-thumbnail"
class="video-thumbnail max-w-full rounded-lg"
preload="metadata"
@error="handleVideoError"
></video>
@@ -372,39 +378,49 @@ if (props.message.type === 'register') {
/>
<!-- 挂号卡片消息 -->
<div
v-else-if="message.type === 'register'"
class="register-card"
>
<div v-else-if="message.type === 'register'" class="register-card">
<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">
<i class="fas fa-calendar-check text-green-500 dark:text-green-300 text-xl"></i>
<div
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 class="flex-1 min-w-0">
<div class="min-w-0 flex-1">
<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
: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-green-100 text-green-800': message.content.status === 2,
'bg-red-100 text-red-800': message.content.status === 3,
'bg-gray-100 text-gray-800': message.content.status === 4,
'bg-purple-100 text-purple-800': message.content.status === 5,
'bg-indigo-100 text-indigo-800': message.content.status === 6,
'bg-purple-100 text-purple-800':
message.content.status === 5,
'bg-indigo-100 text-indigo-800':
message.content.status === 6,
'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] || '未知状态' }}
</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">
<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 class="flex items-center gap-1">
@@ -416,21 +432,25 @@ if (props.message.type === 'register') {
</div>
<!-- 患者信息 -->
<div class="mt-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div class="flex items-center justify-between mb-2">
<div class="font-medium text-gray-700 dark:text-gray-200">患者信息</div>
<div class="mt-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
<div class="mb-2 flex items-center justify-between">
<div class="font-medium text-gray-700 dark:text-gray-200">
患者信息
</div>
<div
:class="{
'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] || '未知' }}
</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">
<i class="fas fa-user text-xs"></i>
<span>{{ message.content.user_patient?.name || '未知' }}</span>
@@ -445,37 +465,44 @@ if (props.message.type === 'register') {
</div>
<div class="flex items-center gap-1">
<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 class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-600">
<div class="flex items-center justify-between mb-2">
<div class="mt-3 border-t border-gray-200 pt-3 dark:border-gray-600">
<div class="mb-2 flex items-center justify-between">
<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 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
type="primary"
size="small"
:loading="registerOperating"
@click="acceptRegister(message.content.id, message.content.order_no)"
class="flex-1"
size="small"
type="primary"
@click="
acceptRegister(message.content.id, message.content.order_no)
"
>
<i class="fas fa-check mr-1"></i>
接诊
</Button>
<Button
:loading="registerOperating"
class="flex-1"
danger
size="small"
:loading="registerOperating"
@click="rejectRegister(message.content.id, message.content.order_no)"
class="flex-1"
@click="
rejectRegister(message.content.id, message.content.order_no)
"
>
<i class="fas fa-times mr-1"></i>
拒诊
@@ -483,12 +510,12 @@ if (props.message.type === 'register') {
</div>
<!-- 查看详情按钮 -->
<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">
<span class="text-sm font-medium">查看详情</span>
<i class="fas fa-chevron-right ml-1 text-xs"></i>
</div>
</div>
<!-- <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">-->
<!-- <span class="text-sm font-medium">查看详情</span>-->
<!-- <i class="fas fa-chevron-right ml-1 text-xs"></i>-->
<!-- </div>-->
<!-- </div>-->
</div>
<!-- 时间信息 -->
@@ -507,31 +534,45 @@ if (props.message.type === 'register') {
@click="viewPrescription(message.content)"
>
<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">
<i class="fas fa-file-prescription text-blue-500 dark:text-blue-300 text-xl"></i>
<div
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 class="flex-1 min-w-0">
<div class="min-w-0 flex-1">
<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
:class="{
'bg-yellow-100 text-yellow-800': statusInfo.status === 0,
'bg-green-100 text-green-800': statusInfo.status === 1,
'bg-red-100 text-red-800': statusInfo.status === 2,
'bg-blue-100 text-blue-800': statusInfo.status === 3,
'bg-gray-100 text-gray-800': statusInfo.status === 4
}"
class="text-xs px-2 py-1 rounded-full font-medium"
'bg-yellow-100 text-yellow-800': statusInfo.status === 0,
'bg-green-100 text-green-800': statusInfo.status === 1,
'bg-red-100 text-red-800': statusInfo.status === 2,
'bg-blue-100 text-blue-800': statusInfo.status === 3,
'bg-gray-100 text-gray-800': statusInfo.status === 4,
}"
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 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">
<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 class="flex items-center gap-1">
@@ -542,21 +583,107 @@ if (props.message.type === 'register') {
</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">
支付金额: <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 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>
<i class="fas fa-chevron-right ml-1 text-xs"></i>
</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
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"
>
<div class="call-info">
@@ -865,29 +992,29 @@ if (props.message.type === 'register') {
/* 挂号卡片样式 */
.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 border border-gray-200 dark:border-gray-600 hover:shadow-md hover:border-green-300 dark:hover:border-green-500;
@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 hover:border-green-300 hover:shadow-md dark:border-gray-600 dark:hover:border-green-500;
}
.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 {
@apply transform -translate-y-0.5;
@apply -translate-y-0.5 transform;
}
/* 处方卡片样式 - 使用TailwindCSS类替代 */
.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 border border-gray-200 dark:border-gray-600 hover:shadow-md hover:border-blue-300 dark:hover:border-blue-500;
@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 hover:border-blue-300 hover:shadow-md dark:border-gray-600 dark:hover:border-blue-500;
}
.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 {
@apply transform -translate-y-0.5;
@apply -translate-y-0.5 transform;
}
</style>

View File

@@ -32,7 +32,7 @@ import {
} from 'ant-design-vue';
import { usePrescriptionStore } from '#/store/prescription';
// import { getTraditionalChineseMedicineAllApi } from '#/views/doctor/doctor-reception/api';
// 导入子组件
import DiagnosisModal from '#/views/doctor/doctor-reception/components/DiagnosisModal.vue';
import DoctorOrderModal from '#/views/doctor/doctor-reception/components/DoctorOrderModal.vue';
@@ -82,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({
await prescriptionStore.initializePrescription(
modalData.value.registerId,
);
prescriptionStore.loadFromLocalStorage()
prescriptionStore.loadFromLocalStorage();
} else {
console.warn('未提供registerId');
}
@@ -228,6 +228,20 @@ const openPrescriptionDetail = (prescription: any) => {
});
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>
<template>
@@ -710,7 +724,16 @@ const openPrescriptionDetail = (prescription: any) => {
</div>
</Card>
</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="">
<div>
<span class="card-index">{{ prescriptionStore.currentDrugs.length + 1 }}、</span>
@@ -778,7 +801,16 @@ const openPrescriptionDetail = (prescription: any) => {
</div>
</Card>
</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="">
<div>
<span class="card-index">{{ prescriptionStore.currentDrugs.length + 1 }}、</span>
@@ -918,15 +950,110 @@ const openPrescriptionDetail = (prescription: any) => {
<!-- 诊断和医嘱区域 -->
<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-2 flex items-center justify-between">
<label class="font-medium">诊断结果</label>
<label class="font-medium">常用诊断</label>
<Button type="primary" @click="openDiagnosisModal">常用诊断</Button>
</div>
<Textarea
v-model:value="prescriptionStore.diagnosis"
placeholder="输入诊断结果..."
disabled
placeholder="输入常用诊断..."
/>
</div>

View File

@@ -237,7 +237,7 @@ export const useChatStore = defineStore('chat', () => {
id: message.id,
type: getMessageType(message.message_type),
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)
: message.message_content,
time: message.created_at_text,
@@ -383,6 +383,7 @@ export const useChatStore = defineStore('chat', () => {
'file',
'',
'register',
'follow-up-visit',
];
return types[type] || 'text';
};

View File

@@ -77,6 +77,12 @@ function getColor(status: any) {
}
}
}
const orderTypeMap = {
1: '处方订单',
2: '预约购药订单',
3: '商城处方订单',
}
</script>
<template>
<Modal class="w-[80%]" title="订单详情">
@@ -91,7 +97,7 @@ function getColor(status: any) {
{{ data.order_no }}
</Descriptions.Item>
<Descriptions.Item label="订单类型">
{{ data.order_type === 1 ? '普通订单' : '其他' }}
{{ orderTypeMap[data.order_type] || '其他' }}
</Descriptions.Item>
<Descriptions.Item label="总支付价格">
{{ data.total_pay_price }}
@@ -99,7 +105,7 @@ function getColor(status: any) {
<Descriptions.Item label="物品总价">
{{ data.items_price }}
</Descriptions.Item>
<Descriptions.Item label="市场价">
<Descriptions.Item label="供货价">
{{ data.market_price }}
</Descriptions.Item>
<Descriptions.Item label="运费">
@@ -216,7 +222,7 @@ function getColor(status: any) {
</div>
</Card>
</div>
<div class="mt-4">
<div class="mt-4" v-if="data.order_type !== 2 && data.order_type !== 3 && data.prescription.content">
<h3>处方信息</h3>
<Descriptions
: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 {getOrderStatusOption} from "#/views/business/order/product-order/api";
import {getStoreOption} from "#/views/system/store/api";
export const formOptions: VbenFormProps = {
// 默认展开
@@ -16,6 +17,25 @@ export const formOptions: VbenFormProps = {
fieldName: 'order_no',
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',
componentProps: {

View File

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

View File

@@ -175,6 +175,27 @@ export const modalFormProps: VbenFormProps = {
label: '商品状态',
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',
componentProps: {
@@ -184,6 +205,24 @@ export const modalFormProps: VbenFormProps = {
label: '药品主治功能',
rules: 'required',
},
// {
// component: 'Textarea',
// componentProps: {
// placeholder: '请输入对应症状',
// },
// fieldName: 'indications',
// label: '对应症状',
// rules: 'required',
// },
{
component: 'InputSelect',
componentProps: {
placeholder: '请输入对应症状',
},
fieldName: 'indications',
label: '对应症状',
rules: 'required',
},
{
component: 'Avatar',
fieldName: 'instruction',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -93,12 +93,12 @@ const openExcelUploadModal = (type = 1) => {
ExcelUploadModalApi.open();
};
const updateStatus = (id) => {
const updateStatus = (id: number) => {
updateWarehouseDrugManagementStatusApi(id).then(() => {
message.success('修改成功!');
gridApi.reload();
});
}
};
</script>
<template>
@@ -160,7 +160,14 @@ const updateStatus = (id) => {
</TableAction>
</template>
<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 #instruction="{ row }">
<div class="div" style="width: 120px; height: 120px; overflow: scroll">
@@ -168,9 +175,9 @@ const updateStatus = (id) => {
</div>
</template>
<template #status="{ row }">
<Tag :color="row.status === 2 ? 'green' : 'red'">{{
row.status === 2 ? '上架' : '下架'
}}</Tag>
<Tag :color="row.status === 2 ? 'green' : 'red'">
{{ row.status === 2 ? '上架' : '下架' }}
</Tag>
</template>
<template #toolbar-tools></template>
<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>) {
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 {
checkSubscribeApi,
deleteWarehouseDrugManagementStore,
exportWarehouseDrugManagementStoreApi, syncDrugApi, syncDrugPriceApi,
exportWarehouseDrugManagementStoreApi, subscribeApi, syncDrugApi, syncDrugPriceApi,
updateWarehouseDrugManagementStoreStatusApi
} from './api';
import FormModalDemo from './components/modal.vue';
@@ -116,6 +117,22 @@ const syncDrugPrice = () => {
gridApi.reload();
});
}
const isSubscribe = ref(true);
const checkSubscribe = () =>{
checkSubscribeApi().then((res) => {
isSubscribe.value = res;
})
};
const subscribe = () =>{
subscribeApi().then(() => {
message.success('操作成功!');
})
};
checkSubscribe();
</script>
<template>
@@ -148,6 +165,14 @@ const syncDrugPrice = () => {
// auth: ['超级西(中成)药', 'sys:user:save'],
onClick: syncDrugPrice,
},
{
label: '订阅仓库价格',
type: 'primary',
ifShow: !isSubscribe,
icon: 'gridicons:reader-following',
// auth: ['超级西(中成)药', 'sys:user:save'],
onClick: subscribe,
},
]"
:drop-down-actions="[
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
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 {
id: string;
@@ -40,6 +40,64 @@ export const gridOptions2: VxeGridProps<RowType> = {
],
keepSource: true,
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: {
ajax: {
// 请求后端接口方法

View File

@@ -1,14 +1,17 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { AnalysisChartsTabs, Page } from '@vben/common-ui';
import { FloatButton, FloatButtonGroup, Popover, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { Tag } from 'ant-design-vue';
import Statistics from './components/statistics.vue';
import { formOptions } from './config/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';
const [Grid, GridApi] = useVbenVxeGrid({
@@ -21,6 +24,11 @@ const [Grid2] = useVbenVxeGrid({
gridOptions: gridOptions2,
});
const [Grid3] = useVbenVxeGrid({
formOptions: formOptions2,
gridOptions: gridOptions3,
});
const chartTabs = [
{
label: '提现记录',
@@ -30,7 +38,17 @@ const chartTabs = [
label: '结算记录',
value: 'visits',
},
{
label: '结算明细',
value: 'visitsItems',
},
];
const isShow = ref(true);
function updateShowStatus() {
isShow.value = !isShow.value;
}
</script>
<template>
@@ -39,7 +57,7 @@ const chartTabs = [
description="提现前请确认好您的打款账户"
title="提现管理"
>
<Statistics :grid-api="GridApi" />
<Statistics v-show="isShow" :grid-api="GridApi" />
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
<template #trends>
<Grid>
@@ -69,7 +87,9 @@ const chartTabs = [
<span v-else-if="row.dakuan_status === 1">打款成功</span>
</template>
<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 #action="{ row }">
<TableAction
@@ -88,6 +108,34 @@ const chartTabs = [
</Grid>
</template>
<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>
<template #toolbar-buttons></template>
<template #toolbar-tools></template>
@@ -114,5 +162,24 @@ const chartTabs = [
</Grid2>
</template>
</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>
</template>

View File

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

View File

@@ -6,6 +6,18 @@ export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
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',
componentProps: {
@@ -22,7 +34,7 @@ export const formOptions: VbenFormProps = {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
submitOnChange: false,
// 按下回车时是否提交表单
submitOnEnter: false,
submitOnEnter: true,
};

View File

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

View File

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