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-08-18 16:08:37 +08:00
parent c0180d699f
commit 6f8f87fb09
12 changed files with 795 additions and 457 deletions

View File

@@ -0,0 +1,175 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useVModel } from '@vueuse/core';
import { Modal, Upload } from 'ant-design-vue';
import { uploadFile } from '#/api/core/upload';
import { Icon } from '#/components/icon';
defineOptions({
name: 'UploadImage',
inheritAttrs: false,
});
const props = defineProps({
modelValue: {
type: Array as () => string[],
default: () => [],
},
multiple: {
type: Boolean,
default: true,
},
maxCount: {
type: Number,
default: 9,
},
});
const emits = defineEmits(['update:modelValue']);
const mValue = useVModel(props, 'modelValue', emits, {
passive: true,
});
const fileList = ref(
props.modelValue.map((url) => ({
uid: url,
name: url.split('/').pop() || 'file',
status: 'done',
url,
})),
);
// 监听 modelValue 变化,同步到 fileList
watch(props.modelValue, (newVal) => {
fileList.value = newVal.map((url) => ({
uid: url,
name: url.split('/').pop() || 'file',
status: 'done',
url,
}));
});
// 监听 fileList 变化,同步到 modelValue
watch(fileList, (newVal) => {
mValue.value = newVal
.filter((file) => file.status === 'done' && file.url)
.map((file) => file.url);
});
const customRequest = async (e: any) => {
try {
const res = await uploadFile({
file: e.file,
});
// 更新 fileList
fileList.value = [
...fileList.value.filter((file) => file.status !== 'uploading'),
{
uid: res.url,
name: e.file.name,
status: 'done',
url: res.url,
},
];
// 更新 modelValue
mValue.value = fileList.value.map((file) => file.url);
// 触发上传完成回调
e.onSuccess?.(res);
} catch (error) {
console.error('上传失败', error);
e.onError?.(error);
}
};
const handleRemove = (file: any) => {
// 从 fileList 中移除
fileList.value = fileList.value.filter((item) => item.uid !== file.uid);
// 更新 modelValue
mValue.value = fileList.value.map((file) => file.url);
};
const previewVisible = ref(false);
const previewImage = ref('');
const previewTitle = ref('');
const handlePreview = async (file) => {
previewImage.value = file.response.url || file.preview;
previewVisible.value = true;
previewTitle.value =
file.name || file.url.slice(Math.max(0, file.url.lastIndexOf('/') + 1));
};
// 计算是否还能上传更多图片
const showUploadButton = computed(() =>
props.multiple
? fileList.value.length < props.maxCount
: fileList.value.length === 0,
);
</script>
<template>
<Upload
v-model:value="fileList"
:custom-request="customRequest"
:multiple="multiple"
:limit="maxCount"
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
list-type="picture-card"
@remove="handleRemove"
@preview="handlePreview"
>
<div v-if="showUploadButton" class="upload-button">
<Icon icon="ant-design:plus-outlined" />
<div class="ant-upload-text">上传图片</div>
</div>
</Upload>
<Modal
v-model:visible="previewVisible"
:title="previewTitle"
footer=""
width="60%"
>
<img alt="example" style="width: 100%" :src="previewImage" />
</Modal>
</template>
<style lang="less" scoped>
.m-avatar-wrap {
position: relative;
height: 102px;
width: 102px;
.m-avatar-icon-delete {
position: absolute;
top: 0;
right: 0;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border-radius: 0 0 0 4px;
padding: 2px 4px;
}
&:hover .m-avatar-icon-delete {
opacity: 1;
}
}
.upload-button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
cursor: pointer;
}
</style>

View File

@@ -13,6 +13,9 @@ import previewMedia from '../composables/useMediaPreview.ts';
import {useThemeStore} from '../stores/theme.ts';
import AudioMessage from './AudioMessage.vue';
const userStore = useUserStore();
const themeStore = useThemeStore();
const props = defineProps({
message: {
type: Object,
@@ -30,10 +33,6 @@ const props = defineProps({
const emit = defineEmits(['show-user-profile']);
const userStore = useUserStore();
const themeStore = useThemeStore();
const avatarStyle = computed(() => ({
background: props.senderInfo.color,
}));
@@ -43,6 +42,8 @@ const statusInfo = ref({
text: '',
});
// 挂号操作状态
const registerOperating = ref(false);
// 添加处方状态映射
const prescriptionStatusMap = {
@@ -51,6 +52,21 @@ const prescriptionStatusMap = {
2: '未通过',
};
// 添加挂号状态映射
const registerStatusMap = {
0: '待就诊',
1: '已缴费',
2: '已就诊',
3: '已取消',
4: '已退费'
};
// 添加支付状态映射
const payStatusMap = {
0: '未支付',
1: '已支付'
};
const checkPrescriptionStatus = (id) => {
if (!id) return;
getPrescriptionCheckStatusApi({id}).then((res) => {
@@ -60,6 +76,62 @@ const checkPrescriptionStatus = (id) => {
})
};
// 接诊操作
const acceptRegister = async (registerId, orderNo) => {
if (registerOperating.value) return;
registerOperating.value = true;
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1500));
// 更新消息状态
if (props.message.content && props.message.content.id === registerId) {
props.message.content.status = 2; // 已就诊
}
// 显示成功提示
console.log(`接诊成功 - 挂号ID: ${registerId}, 订单号: ${orderNo}`);
// 这里可以调用实际的API
// await acceptRegisterApi({ id: registerId, order_no: orderNo });
} catch (error) {
console.error('接诊失败:', error);
} finally {
registerOperating.value = false;
}
};
// 拒诊操作
const rejectRegister = async (registerId, orderNo) => {
if (registerOperating.value) return;
registerOperating.value = true;
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1500));
// 更新消息状态
if (props.message.content && props.message.content.id === registerId) {
props.message.content.status = 3; // 已取消
}
// 显示成功提示
console.log(`拒诊成功 - 挂号ID: ${registerId}, 订单号: ${orderNo}`);
// 这里可以调用实际的API
// await rejectRegisterApi({ id: registerId, order_no: orderNo, reason: '医生拒诊' });
} catch (error) {
console.error('拒诊失败:', error);
} finally {
registerOperating.value = false;
}
};
const bubbleClasses = computed(() => {
const baseClasses = ['message-bubble'];
@@ -125,8 +197,6 @@ const handleVideoError = (event) => {
console.error('视频加载失败:', event);
};
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
connectedComponent: PrescriptionDetail,
});
@@ -138,6 +208,24 @@ const viewPrescription = (item) => {
PrescriptionDetailModalApi.open();
}
// 格式化日期时间
const formatDateTime = (timestamp) => {
if (!timestamp) return '';
const date = new Date(timestamp * 1000);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
// 获取性别文本
const getSexText = (sex) => {
return sex === 1 ? '男' : '女';
};
</script>
<template>
@@ -147,11 +235,6 @@ const viewPrescription = (item) => {
>
<PrescriptionDetailModal />
<!-- 头像 -->
<!-- <div-->
<!-- :style="avatarStyle"-->
<!-- class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm flex-shrink-0 cursor-pointer"-->
<!-- @click="showUserProfile"-->
<!-- >-->
<div
v-if="isSent"
:style="avatarStyle"
@@ -256,7 +339,132 @@ const viewPrescription = (item) => {
:message="message"
/>
<!-- 处方卡片消息 -->
<!-- 挂号卡片消息 -->
<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>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<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-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
}"
class="text-xs px-2 py-1 rounded-full 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="flex items-center gap-1">
<i class="fas fa-hashtag text-xs"></i>
<span class="truncate max-w-[120px]">{{ message.content.order_no }}</span>
</div>
<div class="flex items-center gap-1">
<i class="fas fa-list-ol text-xs"></i>
<span>{{ message.content.order_number }}</span>
</div>
</div>
</div>
</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="{
'bg-red-100 text-red-800': message.content.is_pay === 0,
'bg-green-100 text-green-800': message.content.is_pay === 1
}"
class="text-xs px-2 py-1 rounded-full 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="flex items-center gap-1">
<i class="fas fa-user text-xs"></i>
<span>{{ message.content.user_patient?.name || '未知' }}</span>
</div>
<div class="flex items-center gap-1">
<i class="fas fa-birthday-cake text-xs"></i>
<span>{{ message.content.user_patient?.age || 0 }}</span>
</div>
<div class="flex items-center gap-1">
<i class="fas fa-venus-mars text-xs"></i>
<span>{{ getSexText(message.content.user_patient?.sex) }}</span>
</div>
<div class="flex items-center gap-1">
<i class="fas fa-phone text-xs"></i>
<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="text-sm font-medium text-gray-700 dark:text-gray-200">
挂号费用: <span class="text-green-500 dark:text-green-400">¥{{ message.content.price }}</span>
</div>
</div>
<!-- 接诊/拒诊按钮 - 只在待就诊状态显示 -->
<div v-if="message.content.status === 0" class="flex gap-2 mt-2">
<Button
type="primary"
size="small"
:loading="registerOperating"
@click="acceptRegister(message.content.id, message.content.order_no)"
class="flex-1"
>
<i class="fas fa-check mr-1"></i>
接诊
</Button>
<Button
danger
size="small"
:loading="registerOperating"
@click="rejectRegister(message.content.id, message.content.order_no)"
class="flex-1"
>
<i class="fas fa-times mr-1"></i>
拒诊
</Button>
</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>
<!-- 时间信息 -->
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
创建时间: {{ message.content.created_at }}
<span v-if="message.content.pay_time" class="ml-2">
支付时间: {{ formatDateTime(message.content.pay_time) }}
</span>
</div>
</div>
<!-- 处方卡片消息 -->
<div
v-else-if="message.type === 'prescription'"
@@ -282,7 +490,6 @@ const viewPrescription = (item) => {
class="text-xs px-2 py-1 rounded-full font-medium"
>
{{ checkPrescriptionStatus(message.content.id) || statusInfo.text || '加载中' }}
<!-- {{ statusInfo.text || '加载中' }}-->
</div>
</div>
@@ -314,8 +521,7 @@ const viewPrescription = (item) => {
<!-- 通话消息 -->
<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">
@@ -622,6 +828,19 @@ v-else-if="message.type === 'video-call' || message.type === 'voice-call'"
color: white;
}
/* 挂号卡片样式 */
.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;
}
.message-bubble.sent .register-card {
@apply bg-green-50/30 dark:bg-green-900/30 border-green-200/50 dark:border-green-700/70;
}
.register-card:hover {
@apply transform -translate-y-0.5;
}
/* 处方卡片样式 - 使用TailwindCSS类替代 */
.prescription-card {

View File

@@ -262,8 +262,9 @@ const emit = defineEmits([
// 开方功能触发函数
const handleOpenPrescription = () => {
// 获取当前会话的register_id这里假设可以通过某种方式获取
console.log(chatStore.currentFriend.register_id, 'sssssssssssssss')
// 例如从聊天存储或props中获取
const registerId = props.currentFriend?.register_id || 66;
const registerId = chatStore.currentFriend?.register_id || 66;
if (registerId) {
emit('openPrescription', registerId);

View File

@@ -238,7 +238,7 @@ export const useChatStore = defineStore('chat', () => {
id: message.id,
type: getMessageType(message.message_type),
content:
message.message_type === 4
message.message_type === 4 || message.message_type === 10
? JSON.parse(message.message_content)
: message.message_content,
time: message.created_at_text,
@@ -373,6 +373,8 @@ export const useChatStore = defineStore('chat', () => {
'video-call',
'audio-call',
'file',
'',
'register',
];
return types[type] || 'text';
};

View File

@@ -36,3 +36,17 @@ export async function saveZSalePercentApi(z_sale_percent: number | string) {
z_sale_percent,
});
}
/**
* 保存轮播图
* @param data
*/
export async function saveNavUrlApi(data) {
return requestClient.post<any>(`${prefix}upload-nav`, data);
}
/**
* 删除轮播图
* @param data
*/
export async function deleteNavApi(data) {
return requestClient.post<any>(`${prefix}delete-nav`, data);
}

View File

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

View File

@@ -0,0 +1,26 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'UploadImage',
fieldName: 'url',
label: '图片',
rules: 'required',
componentProps: {
maxCount: 5,
},
},
],
showDefaultActions: false,
};

View File

@@ -3,12 +3,14 @@ import { ref } from 'vue';
import { EllipsisText, Page, useVbenModal } from '@vben/common-ui';
import { DeleteOutlined } from '@ant-design/icons-vue';
import {
Avatar,
Button,
Card,
Image,
InputNumber, message,
InputNumber,
message,
notification,
RadioButton,
RadioGroup,
@@ -19,14 +21,56 @@ import html2canvas from 'html2canvas';
import DoctorQrCodePreview from '#/components/modal/DoctorQrCodePreview.vue';
import {
deleteNavApi,
getStoreInfoApi,
saveZSalePercentApi,
} from '#/views/business/store/settings/api';
import UploadModal from './components/uploadModal.vue';
// 定义类型
interface TabBarItem {
value: number;
label: string;
}
interface StoreInfo {
name: string;
star?: any;
title: { name: string };
contact: string;
mobile: string;
created_at: string;
start_time: string;
end_time: string;
province: { name: string };
city: { name: string };
position: string;
inquiry_count: number;
doctor_count: number;
inquiry_rate: string;
z_sale_percent: number;
qr_code: string;
doctor_list: any[];
nav: { id: number; pic: string }[];
}
const [DoctorQrCodePreviewModal, DoctorQrCodePreviewModalApi] = useVbenModal({
connectedComponent: DoctorQrCodePreview,
});
const [UploadModals, UploadModalApi] = useVbenModal({
connectedComponent: UploadModal,
});
const openUpload = () => {
UploadModalApi.setData({
// doctorId,
getMyInfo,
});
UploadModalApi.open();
};
const openDoctorQrCode = (doctorId: number, isNotQr = false) => {
if (isNotQr) return message.error('该医生暂时没有二维码!');
DoctorQrCodePreviewModalApi.setData({
@@ -37,41 +81,33 @@ const openDoctorQrCode = (doctorId: number, isNotQr = false) => {
DoctorQrCodePreviewModalApi.open();
};
// 映射数据(示例)
// 映射数据
const activeTabBar = ref(1);
const tabBar = [
{
value: 1,
label: '诊所资料',
},
{
value: 2,
label: '商品价格',
},
{
value: 3,
label: '诊所二维码',
},
{
value: 4,
label: '我的医生团队',
},
const tabBar: TabBarItem[] = [
{ value: 1, label: '诊所资料' },
{ value: 2, label: '商品价格' },
{ value: 3, label: '诊所二维码' },
{ value: 4, label: '我的医生团队' },
{ value: 5, label: '轮播图' },
];
// 格式化日期
const formatDate = (dateString) => {
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString();
};
const data = ref();
const data = ref<null | StoreInfo>(null);
/**
* 获取当前登录诊所信息
*/
const getMyInfo = () => {
getStoreInfoApi({}).then((res) => {
data.value = res;
getStoreInfoApi({}).then((res: any) => {
data.value = res as StoreInfo;
});
};
getMyInfo();
/**
* 医嘱列表
*/
@@ -87,8 +123,8 @@ const tabBarChange = () => {
}
};
const qrCodeUrl = ref('');
const htmlToImage = ref('');
const qrCodeUrl = ref<HTMLElement | null>(null);
const htmlToImage = ref<HTMLElement | null>(null);
/**
* 下载二维码
@@ -96,23 +132,28 @@ const htmlToImage = ref('');
* @param isQr
*/
const downloadQRCode = async (name: string, isQr = false) => {
if (!data.value) return;
const element = isQr ? qrCodeUrl.value : htmlToImage.value;
if (!element) return;
html2canvas(element, {
useCORS: true,
allowTaint: true,
logging: false,
}).then((canvas) => {
// 创建a标签下载
const link = document.createElement('a'); // 创建a标签
link.href = canvas.toDataURL(); // 是canvas对象的一种方法用于将canvas对象转换为base64位编码
link.setAttribute('download', `${data.value.name}${name}.png`); // 利用了a标签的download 来下载 canvas图片
link.style.display = 'none'; // 将图片隐藏起来
document.body.append(link); // 插入到其中
const link = document.createElement('a');
link.href = canvas.toDataURL();
link.setAttribute('download', `${data.value!.name}${name}.png`);
link.style.display = 'none';
document.body.append(link);
link.click();
});
};
const saveZSalePercent = () => {
if (!data.value) return;
saveZSalePercentApi(data.value.z_sale_percent).then(() => {
notification.success({
message: '任务开始执行',
@@ -120,11 +161,27 @@ const saveZSalePercent = () => {
});
});
};
// 删除轮播图项
const deleteNavItem = (id: number) => {
if (data.value) {
deleteNavApi({
id,
}).then(() => {
notification.success({
message: '删除成功',
duration: 2,
});
getMyInfo();
});
}
};
</script>
<template>
<Page v-if="data" :title="`${data.name}的配置中心`" auto-content-height>
<DoctorQrCodePreviewModal />
<UploadModals />
<div class="pb-2">
<RadioGroup
v-model:value="activeTabBar"
@@ -287,10 +344,7 @@ const saveZSalePercent = () => {
<div
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<Card
v-for="(doctor, index) in data.doctor_list"
:key="index"
>
<Card v-for="(doctor, index) in data.doctor_list" :key="index">
<!-- 医生头像和信息 -->
<div class="mb-4 flex items-center">
<Avatar
@@ -340,8 +394,9 @@ const saveZSalePercent = () => {
</div>
<!-- 二维码 -->
<div class="border-t pt-4"
@click="openDoctorQrCode(doctor.doctor.su_id, !doctor.qr_code)"
<div
class="border-t pt-4"
@click="openDoctorQrCode(doctor.doctor.su_id, !doctor.qr_code)"
>
<p class="mb-2 text-sm text-gray-600">联系二维码:</p>
<template v-if="doctor.qr_code">
@@ -385,6 +440,42 @@ const saveZSalePercent = () => {
</Card>
</div>
</Card>
<Card v-else-if="activeTabBar === 5" title="轮播图">
<div class="mb-4 flex items-center justify-between">
<span>最多可添加5张轮播图</span>
<Button v-if="data.nav.length < 5" type="primary" @click="openUpload">
上传新图片
</Button>
</div>
<div class="mt-4 flex flex-wrap gap-4">
<Card
v-for="(item, index) in data.nav"
:key="item.id"
class="relative"
style="width: 240px"
>
<div class="group relative">
<Image :src="item.pic" class="h-40 w-full rounded object-cover" />
<div
class="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50 opacity-0 transition-opacity group-hover:opacity-100"
>
<Button
class="shadow-lg"
danger
shape="circle"
size="large"
type="primary"
@click.stop="deleteNavItem(item.id)"
>
<DeleteOutlined />
</Button>
</div>
</div>
<div class="mt-2 text-center text-sm text-gray-500">点击图片删除</div>
</Card>
</div>
</Card>
</Page>
</template>
<style lang="scss" scoped>
@@ -438,4 +529,13 @@ const saveZSalePercent = () => {
border-radius: 20px;
}
}
/* 轮播图卡片样式 */
.card-wrapper {
transition: all 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
}
</style>

View File

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

View File

@@ -13,7 +13,7 @@ import { downloadByData } from '#/util/tool';
import {
deleteWarehouseDrugManagementStore,
exportWarehouseDrugManagementStoreApi,
exportWarehouseDrugManagementStoreApi, syncDrugApi,
updateWarehouseDrugManagementStoreStatusApi
} from './api';
import FormModalDemo from './components/modal.vue';
@@ -93,6 +93,13 @@ const openExcelUploadModal = (type = 1) => {
ExcelUploadModalApi.open();
};
const syncDrugs = () => {
syncDrugApi().then((res) => {
message.success('同步成功!');
gridApi.reload();
})
};
const updateStatus = (id) => {
updateWarehouseDrugManagementStoreStatusApi(id).then(() => {
message.success('修改成功!');
@@ -116,6 +123,13 @@ const updateStatus = (id) => {
// auth: ['超级西(中成)药', 'sys:user:save'],
onClick: openExcelUploadModal.bind(null, 2),
},
{
label: '更新总仓库商品(只更新未同步的商品)',
type: 'primary',
icon: 'ix:export-check',
// auth: ['超级西(中成)药', 'sys:user:save'],
onClick: syncDrugs,
},
]"
:drop-down-actions="[
{

View File

@@ -1,4 +1,5 @@
import type {VbenFormProps} from '#/adapter/form';
import type { VbenFormProps } from '#/adapter/form';
import { addressOption } from '#/util/address.ts';
export const modalFormProps: VbenFormProps = {
@@ -214,7 +215,7 @@ export const modalFormProps: VbenFormProps = {
hideLabel: true,
dependencies: {
show: (values) => {
return values.id === null;
return values.id == null;
},
triggerFields: ['id'],
},
@@ -234,7 +235,7 @@ export const modalFormProps: VbenFormProps = {
},
dependencies: {
show: (values) => {
return values.id === null;
return values.id == null;
},
triggerFields: ['id'],
},
@@ -250,7 +251,7 @@ export const modalFormProps: VbenFormProps = {
},
dependencies: {
show: (values) => {
return values.id === null;
return values.id == null;
},
triggerFields: ['id'],
},
@@ -266,7 +267,7 @@ export const modalFormProps: VbenFormProps = {
},
dependencies: {
show: (values) => {
return values.id === null;
return values.id == null;
},
triggerFields: ['id'],
},
@@ -282,7 +283,7 @@ export const modalFormProps: VbenFormProps = {
},
dependencies: {
show: (values) => {
return values.id === null;
return values.id == null;
},
triggerFields: ['id'],
},
@@ -301,7 +302,7 @@ export const modalFormProps: VbenFormProps = {
},
dependencies: {
show: (values) => {
return values.id === null;
return values.id == null;
},
triggerFields: ['id'],
},