fix: 订单管理新增查看处方功能,接诊页面优化

This commit is contained in:
2025-04-03 16:17:07 +08:00
parent 342f3cbd57
commit 0994dd94d5
16 changed files with 516 additions and 102 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -175,7 +175,7 @@ function getRes(obj: any, isDecode = true) {
if (arr.has(key)) {
let aseFile = '';
aseFile = isDecode
? customBase64Decode(obj[key])
? customBase64Decode(obj[key], key)
: customBase64Encode(obj[key]);
const isGarbled =
// eslint-disable-next-line no-control-regex
@@ -232,8 +232,9 @@ function getRes(obj: any, isDecode = true) {
/**
* 解密
* @param data
* @param key
*/
function customBase64Decode(data: string) {
function customBase64Decode(data: string, key = '') {
// return data
try {
// 第一次Base64解码
@@ -244,7 +245,10 @@ function customBase64Decode(data: string) {
const base64 = Base64.decode(subStr);
return base64.length > 0 ? base64 : data;
} catch (error) {
console.error(`解码Base64字符串【${data}】时发生错误`, error);
console.error(
`解码Base64字符串【${data}】时发生错误KEY${key}`,
error,
);
return data;
}
}

View File

@@ -0,0 +1,34 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Image } from 'ant-design-vue';
const url = ref('');
const previewTitle = ref('二维码');
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
const { values, title } = modalApi.getData<Record<string, any>>();
if (values) {
url.value = values;
previewTitle.value = title;
}
}
},
});
</script>
<template>
<Modal :title="previewTitle" class="w-[50%]">
<Image :src="url" height="30" width="30" />
</Modal>
</template>

View File

@@ -0,0 +1,135 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button, Image } from 'ant-design-vue';
import html2canvas from 'html2canvas';
const qrCodeUrl = ref('');
const htmlToImage = ref('');
const url = ref('');
const previewTitle = ref('二维码');
const previewAddress = ref('');
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
footer: false,
header: false,
showCancelButton: true,
showConfirmButton: true,
confirmText: '保存诊所卡片图片',
cancelText: '保存诊所二维码图片',
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
const { values, title, address } =
modalApi.getData<Record<string, any>>();
if (values) {
url.value = values;
qrCodeUrl.value = values;
previewTitle.value = title;
previewAddress.value = address;
}
}
},
});
/**
* 下载二维码
* @param name
* @param isQr
*/
const downloadQRCode = async (name: string, isQr = false) => {
const element = isQr ? qrCodeUrl.value : htmlToImage.value;
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', `${previewTitle.value}${name}.png`); // 利用了a标签的download 来下载 canvas图片
link.style.display = 'none'; // 将图片隐藏起来
document.body.append(link); // 插入到其中
link.click();
});
};
</script>
<template>
<Modal :title="`${previewTitle}二维码`" class="w-[50%]">
<div class="qrBox" style="text-align: center">
<div ref="htmlToImage" class="qrModal">
<div id="qrcode" ref="qrCodeUrl" class="qrcode">
<div class="qrTitle">{{ previewTitle }}</div>
<Image :preview="false" :src="url" height="30" width="30" />
<div class="qrAddress">
<span class="addressTitle">诊所地址</span>{{ previewAddress }}
</div>
</div>
</div>
<Button
style="margin: 20px auto"
type="primary"
@click="downloadQRCode('二维码卡片下载')"
>
二维码卡片下载
</Button>
<Button
style="margin: 20px auto 20px 20px"
type="primary"
@click="downloadQRCode('二维码下载', true)"
>
二维码下载
</Button>
</div>
</Modal>
</template>
<style lang="scss" scoped>
.qrModal {
// 宽度适应文本长度
margin: 0 auto;
text-align: center;
background: radial-gradient(
circle at center,
rgba(173, 216, 230, 0.8),
rgba(135, 206, 235, 0.8),
rgba(100, 149, 237, 0.8)
);
padding: 20px 30px;
.qrTitle {
font-size: 20px;
margin-bottom: 30px;
color: #333;
font-weight: bold;
}
.qrAddress {
font-size: 12px;
margin-top: 30px;
color: #333;
font-weight: bold;
}
.addressTitle {
color: #3a8ee6;
}
.qrcode {
width: fit-content;
//width: 50%;
margin: 0 auto;
text-align: center;
padding: 20px 30px;
background-color: #fff;
border-radius: 20px;
}
}
</style>

View File

@@ -160,11 +160,11 @@ function getColor(status: any) {
<div
class="mr-6 h-32 w-32 overflow-hidden rounded-lg bg-gray-200 dark:bg-gray-800"
>
<Image :src="item.drug_image" alt="" height="100%" width="100%" />
<Image :src="item.drug_image || '/img/empty.png'" alt="/img/empty.png" height="100%" width="100%" />
</div>
<div>
<h4>{{ item.drug_name }}</h4>
<p>规格: {{ item.drug.specification }}</p>
<p>规格: {{ item.drug.specification || 'g' }}</p>
<p>数量: {{ item.number }}</p>
<p>单价: {{ item.price }} </p>
<p>

View File

@@ -43,6 +43,7 @@ export const gridOptions: VxeGridProps<RowType> = {
slots: { default: 'delivery-method' },
},
{ field: 'items_price', title: '总价' },
{ field: 'is_sync_erp', title: 'Erp状态', slots: { default: 'is-sync-erp' } },
{ field: 'pay_time', title: '支付时间', slots: { default: 'pay-time' } },
{ field: 'created_at', title: '下单时间' },
{

View File

@@ -14,6 +14,7 @@ import FormModalDemo from './components/modal.vue';
import DetailModal from './components/detail.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
import PrescrtionDetail from "#/views/doctor/doctor-reception/components/PrescrtionDetail.vue";
const hasTopTableDropDownActions = ref(false);
@@ -80,12 +81,25 @@ const expandAll = () => {
const collapseAll = () => {
gridApi.grid?.setAllRowExpand(false);
};
const [PrescrtionDetailModal, PrescrtionDetailModalApi] = useVbenModal({
connectedComponent: PrescrtionDetail,
});
const openPrescriptionDetail = (values) => {
// 打开西药处方模态框逻辑
PrescrtionDetailModalApi.setData({
values,
});
PrescrtionDetailModalApi.open();
};
</script>
<template>
<Page auto-content-height title="订单管理">
<FormModal />
<Modal />
<PrescrtionDetailModal />
<Grid>
<template #toolbar-buttons>
<TableAction
@@ -131,6 +145,10 @@ const collapseAll = () => {
<template #pay-time="{ row }">
{{ row?.pay_time || '未支付' }}
</template>
<template #is-sync-erp="{ row }">
<Tag v-if="row.is_sync_erp === 1" color="green">已同步</Tag>
<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>
@@ -191,16 +209,13 @@ const collapseAll = () => {
},
]"
:drop-down-actions="[
// {
// label: '发货',
// type: 'link',
// icon: 'ri:send-plane-fill',
// // auth: ['超级订单', 'sys:user:save'],
// popConfirm: {
// title: '确定发货吗',
// confirm: wareSend.bind(null, false),
// },
// },
{
label: '查看处方',
type: 'link',
icon: 'ri:send-plane-fill',
// auth: ['超级订单', 'sys:user:save'],
onClick: openPrescriptionDetail.bind(null, row.prescription),
},
]"
/>
</template>
@@ -266,7 +281,8 @@ const collapseAll = () => {
:key="index"
class="custom-list-item"
>
{{ item.drug.drug_number }} {{ item.drug_name }} * {{item.number}}
{{ item.drug.drug_number }} {{ item.drug_name }} *
{{ item.number * (item.dosage > 0 ? item.dosage : 1) }}
</li>
</ul>
</div>

View File

@@ -17,7 +17,6 @@ const [Modal, modalApi] = useVbenModal({
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) item.value = values;
console.log(item.value, 'sssssssssssssssss');
}
},
});
@@ -125,14 +124,14 @@ const decrypt = (str: string) => str; // 简单base64解码示例
class="recipe-item"
>
<!-- <div class="medicine-item" v-for="drug in JSON.parse(recipe.content)" :key="drug?.id">-->
<div v-if="item.prescription_type === 1">
<div v-for="drug in JSON.parse(recipe.content)" :key="drug?.id">
<div v-if="item.prescription_type === 1" class="w-full">
<div v-for="drug in JSON.parse(recipe.content)" :key="drug?.id" class="w-1/3" style="display: inline-block">
<div class="medicine-item">
<!-- {{ drug }}-->
<span class="drug-name">{{
drug?.name || drug?.drug_name
}}</span>
<span class="drug-quantity ml-5">{{ drug?.number }} /g</span>
<span class="drug-quantity mr-5">{{ drug?.number }} /g</span>
</div>
<div class="">煎服方法: {{ drug.use_way?.name || '煎服' }}</div>
</div>
@@ -278,6 +277,9 @@ const decrypt = (str: string) => str; // 简单base64解码示例
margin: 8px 0;
padding: 4px 0;
}
.chinese-item {
width: 28%;
}
.drug-name {
font-weight: 500;

View File

@@ -24,6 +24,7 @@ import {
Row,
Select,
SelectOption,
Modal,
Tag,
Textarea,
Timeline,
@@ -702,6 +703,22 @@ function updateChineseNumber() {
updateLocalStorage();
}
const showNewDrugModal = ref(false);
/**
* 在新的药品数量框失去焦点后弹出新药品弹窗
*/
function newDrugBlur() {
if (newDrugInfo.value.id > 0 && newDrugInfo.value.number > 0) {
showNewDrugModal.value = true;
}
}
function newDrugModalOk() {
addDrugByChinese();
showNewDrugModal.value = false;
}
function updateChineseNumberGoNewDrug(event: KeyboardEvent) {
console.log(event.key);
if (event.key === 'Enter') {
@@ -771,6 +788,7 @@ function selectNewDrugInfo() {
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
newDrugInfo.value.price = data.price;
newDrugInfo.value.name = data.drug.drug_name;
setTimeout(() => {
// 获取新药品卡片中的克数输入框并聚焦
const newDrugNumberInput = document.querySelector(
@@ -875,6 +893,31 @@ function searchOption(inputValue) {
getDrugListByWesternModal(inputValue);
}
/**
* 添加中药
* @param inputValue
*/
function addDrugByChinese() {
// 如果是新药品输入框,调用添加新药品方法
const check = currentDrugs.value.find(
(v) => v.id === newDrugInfo.value.id,
);
if (check) {
message.error('该药品已经在处方中了!');
return;
}
const data = drugList.value.find(
(v) => v.drug_id === newDrugInfo.value.id,
);
if (data === null || data === undefined) {
message.error('请选择药品');
return;
}
data.drug.number = newDrugInfo.value.number;
data.drug.way_id = newDrugInfo.value.way_id;
addProducts(data);
newDrugInfo.value = {};
}
/**
* 添加操作
*/
@@ -886,24 +929,7 @@ function selectDrugByNewDrugInfo(event: KeyboardEvent, isNewDrug: boolean) {
if (isNewDrug) {
// 如果是新药品输入框,调用添加新药品方法
const check = currentDrugs.value.find(
(v) => v.id === newDrugInfo.value.id,
);
if (check) {
message.error('该药品已经在处方中了!');
return;
}
const data = drugList.value.find(
(v) => v.drug_id === newDrugInfo.value.id,
);
if (data === null || data === undefined) {
message.error('请选择药品');
return;
}
data.drug.number = newDrugInfo.value.number;
data.drug.way_id = newDrugInfo.value.way_id;
addProducts(data);
newDrugInfo.value = {};
addDrugByChinese();
} else {
// 如果是已有药品输入框,调用更新数量方法
updateChineseNumber();
@@ -1011,40 +1037,61 @@ function updateLocalStorageStoreInfo() {
tabType.value = 0;
getPatientListByReception();
}
const leftShow = ref(true)
// 获取doctorReceptionLeftShow
function getDoctorReceptionLeftShow() {
const localStorageLeftShow = localStorage.getItem(`doctorReceptionLeftShow`);
leftShow.value = localStorageLeftShow !== 'false';
}
getDoctorReceptionLeftShow();
// 监听leftShow存入缓存
watch(
() => leftShow.value,
async (enable) => {
localStorage.setItem(`doctorReceptionLeftShow`, enable);
},
{
immediate: true,
},
);
</script>
<template>
<div class="container-box">
<!-- 左侧患者列表 -->
<div class="patient-panel">
<Select v-model:value="myStoreId" class="w-full" @change="switchStore">
<SelectOption v-for="item in myStoreList" :value="item.id">
{{ item.name }}
</SelectOption>
</Select>
<RadioGroup
v-model:value="listType"
class="w-full"
style="text-align: center"
@change="updatePatientList"
>
<RadioButton :value="1" class="w-1/2">当前</RadioButton>
<RadioButton :value="2" class="w-1/2">历史</RadioButton>
</RadioGroup>
<div
v-for="patient in patients"
:key="patient.user_patient.id"
:class="{ active: selectPatientId === patient.id }"
class="patient-card"
@click="selectPatient(patient)"
>
<div class="patient-info">
<h3>{{ patient.user_patient.name }}</h3>
<p class="phone">{{ patient.user_patient.mobile }}</p>
<span :class="getRegisterStatus(patient.status)" class="status">{{
getRegisterStatus(patient.status)
}}</span>
{{ patient.updated_at }}
<div :class="leftShow === true? 'container-box-grid':'container-box'">
<!-- 如果leftShow === true展示收起反之则显示展开-->
<div>
<Button :class="`${leftShow === true? 'w-full': 'sticky top-5 left-5'}`" type="primary" @click="leftShow = !leftShow">{{ leftShow === true ? '收起患者列表' : '展开患者列表' }}</Button>
<!-- 左侧患者列表 -->
<div v-if="leftShow" class="patient-panel">
<Select v-model:value="myStoreId" class="w-full" @change="switchStore">
<SelectOption v-for="item in myStoreList" :value="item.id">
{{ item.name }}
</SelectOption>
</Select>
<RadioGroup
v-model:value="listType"
class="w-full"
style="text-align: center"
@change="updatePatientList"
>
<RadioButton :value="1" class="w-1/2">当前</RadioButton>
<RadioButton :value="2" class="w-1/2">历史</RadioButton>
</RadioGroup>
<div
v-for="patient in patients"
:key="patient.user_patient.id"
:class="{ active: selectPatientId === patient.id }"
class="patient-card"
@click="selectPatient(patient)"
>
<div class="patient-info">
<h3>{{ patient.user_patient.name }}</h3>
<p class="phone">{{ patient.user_patient.mobile }}</p>
<span :class="getRegisterStatus(patient.status)" class="status">{{
getRegisterStatus(patient.status)
}}</span>
{{ patient.updated_at }}
</div>
</div>
</div>
</div>
@@ -1229,9 +1276,6 @@ function updateLocalStorageStoreInfo() {
<div class="prescription-header">
<h2>{{ activePatient.name }} 的处方</h2>
<Button class="send-btn" type="primary" @click="sendPrescription">
发送处方
</Button>
</div>
<!-- 药品分类导航 -->
@@ -1288,9 +1332,9 @@ function updateLocalStorageStoreInfo() {
:lg="12"
:md="24"
:sm="24"
:xl="12"
:xs="24"
:xxl="8"
:xl="leftShow ? 12 : 8"
:xs="leftShow ? 12 : 8"
:xxl="leftShow ? 8 : 6"
>
<Card class="mt-0" title="">
<div>
@@ -1368,11 +1412,11 @@ function updateLocalStorageStoreInfo() {
</Col>
<Col
:lg="12"
:md="24"
:sm="24"
:xl="12"
:xs="24"
:xxl="8"
:md="12"
:sm="12"
:xl="leftShow ? 12 : 8"
:xs="leftShow ? 12 : 8"
:xxl="leftShow ? 8 : 6"
>
<Card title="">
<div>
@@ -1401,7 +1445,7 @@ function updateLocalStorageStoreInfo() {
v-model:value="newDrugInfo.number"
class="new-number-input w-1/5"
style="min-width: 100px"
@blur="updateChineseNumber"
@blur="newDrugBlur"
@keydown="selectDrugByNewDrugInfo($event, true)"
>
<template #addonAfter> g</template>
@@ -1559,6 +1603,7 @@ function updateLocalStorageStoreInfo() {
<Select
v-model:value="newDrugInfo.way_id"
placeholder="用法"
@blur="updateChineseNumber"
@keydown="selectDrugByNewDrugInfo($event, true)"
disabled
>
@@ -1777,6 +1822,10 @@ function updateLocalStorageStoreInfo() {
商品价格:¥{{ totalProductCost.toFixed(2) }}
</div>
<div class="total-cost">总计:¥{{ totalCost.toFixed(2) }}</div>
<Button class="mt-5 w-full" style="display: block" type="primary" @click="sendPrescription">
发送处方
</Button>
</div>
</Page>
<div v-else-if="tabType === 0" class="prescription-panel">
@@ -1786,6 +1835,9 @@ function updateLocalStorageStoreInfo() {
<DoctorOrderModals/>
<WesternDrugModal/>
<PrescrtionDetailModal/>
<Modal v-model:open="showNewDrugModal" @ok="newDrugModalOk">
要把{{ newDrugInfo.name }}添加到清单吗
</Modal>
</div>
</template>
@@ -1812,9 +1864,12 @@ function updateLocalStorageStoreInfo() {
}
.container-box {
min-height: 90vh;
}
.container-box-grid {
min-height: 90vh;
display: grid;
grid-template-columns: 300px 1fr;
min-height: 90vh;
}
.patient-panel {

View File

@@ -57,3 +57,13 @@ export async function openPcWindowsApiByStore(id: number) {
id,
});
}
/**
* 开启后台登录
* @param id
*/
export async function openQrCodeApi(id: number) {
return requestClient.post<any>(`${prefix}open-qr-code`, {
id,
});
}

View File

@@ -135,10 +135,6 @@ export const modalFormProps: VbenFormProps = {
componentProps: {
placeholder: '请选省市区',
options: addressOption,
onChange: (value: any) => {
console.log(value);
},
},
fieldName: 'address',
label: '省市区',
@@ -216,6 +212,12 @@ export const modalFormProps: VbenFormProps = {
label: 'x',
rules: 'required',
hideLabel: true,
dependencies: {
show: (values) => {
return values.id === null;
},
triggerFields: ['id'],
},
renderComponentContent: () => {
return {
default: () => {
@@ -230,6 +232,12 @@ export const modalFormProps: VbenFormProps = {
componentProps: {
placeholder: '请输入开户人姓名',
},
dependencies: {
show: (values) => {
return values.id === null;
},
triggerFields: ['id'],
},
fieldName: 'bank_user_name',
label: '开户人姓名',
rules: 'required',
@@ -240,6 +248,12 @@ export const modalFormProps: VbenFormProps = {
componentProps: {
placeholder: '请输入银行卡号',
},
dependencies: {
show: (values) => {
return values.id === null;
},
triggerFields: ['id'],
},
fieldName: 'bank_card',
label: '银行卡号',
rules: 'required',
@@ -250,6 +264,12 @@ export const modalFormProps: VbenFormProps = {
componentProps: {
placeholder: '请输入开户行',
},
dependencies: {
show: (values) => {
return values.id === null;
},
triggerFields: ['id'],
},
fieldName: 'bank_name',
label: '开户行',
rules: 'required',
@@ -260,6 +280,12 @@ export const modalFormProps: VbenFormProps = {
componentProps: {
placeholder: '请输入银联行号',
},
dependencies: {
show: (values) => {
return values.id === null;
},
triggerFields: ['id'],
},
fieldName: 'bank_no',
label: '银联行号',
},
@@ -274,8 +300,8 @@ export const modalFormProps: VbenFormProps = {
placeholder: '请选择银行账户类型',
},
dependencies: {
disabled: (values) => {
return values.id != null;
show: (values) => {
return values.id === null;
},
triggerFields: ['id'],
},

View File

@@ -15,6 +15,15 @@ export const formOptions: VbenFormProps = {
fieldName: 'name',
label: '诊所名称',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '输入联系人手机号',
},
defaultValue: '',
fieldName: 'mobile',
label: '联系人手机号',
},
// {
// component: 'RangePicker',
// componentProps: {

View File

@@ -35,8 +35,9 @@ export const gridOptions: VxeGridProps<RowType> = {
// { field: 'erp_id', title: 'erpID' },
// { field: 'shouzimu', title: '诊所首字母' },
{ field: 'position', title: '详细地址' },
{ field: 'contact', title: '联系人' },
{ field: 'mobile', title: '联系电话' },
// { field: 'contact', title: '联系人' },
{ field: 'mobile', title: '联系人、电话', slots: { default: 'mobile' } },
{ field: 'start_time', title: '营业时间', slots: { default: 'start-time' } },
{ field: 'created_at', title: '注册时间' },
{ type: 'html', title: '操作', align: 'right', slots: { default: 'action' } },
],

View File

@@ -5,15 +5,18 @@ import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { Button, Image, message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteStore, openPcWindowsApiByStore } from './api';
import {deleteStore, openPcWindowsApiByStore, openQrCodeApi} from './api';
import { useClipboard } from '@vueuse/core';
import FormModalDemo from './components/modal.vue';
import QrCodePreview from "#/components/modal/QrCodePreview.vue";
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
import {Icon} from "#/components/icon";
const hasTopTableDropDownActions = ref(false);
@@ -36,6 +39,20 @@ const [Grid, gridApi] = useVbenVxeGrid({
gridEvents,
});
const [QrCodePreviewModal, QrCodePreviewApi] = useVbenModal({
connectedComponent: QrCodePreview,
});
const openQrCodeModal = (url, title, address) => {
QrCodePreviewApi.setData({
// 表单值
values: url,
title,
address,
});
QrCodePreviewApi.open();
};
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
@@ -71,11 +88,27 @@ function openPcWindows(id) {
gridApi.reload();
});
}
/**
* 开启后台账号
*/
function openQrCode(id) {
openQrCodeApi(id).then(() => {
message.success('操作成功!');
gridApi.reload();
});
}
function copyText(source) {
copy(source);
message.success('复制成功');
}
const { copy } = useClipboard({ legacy: true });
</script>
<template>
<Page auto-content-height title="诊所管理">
<FormModal />
<QrCodePreviewModal />
<Grid>
<template #toolbar-buttons>
<TableAction
@@ -109,8 +142,38 @@ function openPcWindows(id) {
</template>
</TableAction>
</template>
<template #mobile="{ row }">
<div>
诊所联系人{{ row.contact }}
<Button type="link" @click="copyText(row.contact)">复制</Button>
</div>
<div>
号码{{ row.mobile }}
<Button type="link" @click="copyText(row.mobile)">复制</Button>
</div>
</template>
<template #qr_code="{ row }">
<Image :src="row.qr_code" height="30" width="30" />
<div class="qr_code">
<Image
:preview="false"
:src="row.qr_code"
height="30"
width="30"
@click="
openQrCodeModal(
row.qr_code,
row.name,
`${row.province.name}${row.city.name}${row.position}`,
)
"
/>
</div>
</template>
<template #start-time="{ row }">
<Tag color="success">{{ row.start_time }}</Tag>
<br />
<br />
<Tag color="error">{{ row.end_time }}</Tag>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
@@ -167,9 +230,26 @@ function openPcWindows(id) {
confirm: openPcWindows.bind(null, row.id),
},
},
{
label: '生成诊所二维码',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['store', 'sys:role:detail'],
popConfirm: {
title: '确定生成诊所二维码吗',
confirm: openQrCode.bind(null, row.id),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>
<style scoped>
.qr_code:hover {
cursor: pointer;
}
</style>

View File

@@ -116,6 +116,7 @@
]
},
"dependencies": {
"html2canvas": "^1.4.1",
"js-base64": "^3.7.7",
"lodash-es": "^4.17.21"
}

58
pnpm-lock.yaml generated
View File

@@ -469,6 +469,9 @@ importers:
.:
dependencies:
html2canvas:
specifier: ^1.4.1
version: 1.4.1
js-base64:
specifier: ^3.7.7
version: 3.7.7
@@ -3146,8 +3149,8 @@ packages:
resolution: {integrity: sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==}
engines: {node: '>= 16'}
'@intlify/message-compiler@12.0.0-alpha.1':
resolution: {integrity: sha512-rS1Lc99D2uaGqWxlrpGPWdgkq2Jox8xxOS9gdIRhuF2CsuJISWQmwd/TjMnWNhwv9olE0aPEBh1323a61Tfp+g==}
'@intlify/message-compiler@12.0.0-alpha.2':
resolution: {integrity: sha512-PD9C+oQbb7BF52hec0+vLnScaFkvnfX+R7zSbODYuRo/E2niAtGmHd0wPvEMsDhf9Z9b8f/qyDsVeZnD/ya9Ug==}
engines: {node: '>= 16'}
'@intlify/shared@10.0.5':
@@ -3158,8 +3161,8 @@ packages:
resolution: {integrity: sha512-dF2iMMy8P9uKVHV/20LA1ulFLL+MKSbfMiixSmn6fpwqzvix38OIc7ebgnFbBqElvghZCW9ACtzKTGKsTGTWGA==}
engines: {node: '>= 16'}
'@intlify/shared@12.0.0-alpha.1':
resolution: {integrity: sha512-ZZ5rtlUcEnhhFS+MTrl0V1UoN3yRninGawP3f1YituJN9217xJvpqCSLa9t8NLaVwVIxsRcq4lQe48D0SigmBg==}
'@intlify/shared@12.0.0-alpha.2':
resolution: {integrity: sha512-P2DULVX9nz3y8zKNqLw9Es1aAgQ1JGC+kgpx5q7yLmrnAKkPR5MybQWoEhxanefNJgUY5ehsgo+GKif59SrncA==}
engines: {node: '>= 16'}
'@intlify/unplugin-vue-i18n@6.0.1':
@@ -4372,6 +4375,10 @@ packages:
bare-events@2.5.0:
resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -4870,6 +4877,9 @@ packages:
peerDependencies:
postcss: ^8.4
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
css-prefers-color-scheme@10.0.0:
resolution: {integrity: sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==}
engines: {node: '>=18'}
@@ -5987,6 +5997,10 @@ packages:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
@@ -6603,6 +6617,7 @@ packages:
lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
@@ -8658,6 +8673,9 @@ packages:
resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==}
engines: {node: '>=8'}
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
theme-colors@0.1.0:
resolution: {integrity: sha512-6gTEHQqWlQNiOEGHCSSQmU//E5SnXHJ4H7oHQOD8x77CvNYNQAmt73dqR71mzw5ULV87zaHLxK5pIBnsToFuZw==}
@@ -9007,6 +9025,9 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@@ -11184,8 +11205,8 @@ snapshots:
'@intlify/bundle-utils@10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))':
dependencies:
'@intlify/message-compiler': 12.0.0-alpha.1
'@intlify/shared': 12.0.0-alpha.1
'@intlify/message-compiler': 12.0.0-alpha.2
'@intlify/shared': 12.0.0-alpha.2
acorn: 8.14.0
escodegen: 2.1.0
estree-walker: 2.0.2
@@ -11206,16 +11227,16 @@ snapshots:
'@intlify/shared': 10.0.5
source-map-js: 1.2.1
'@intlify/message-compiler@12.0.0-alpha.1':
'@intlify/message-compiler@12.0.0-alpha.2':
dependencies:
'@intlify/shared': 12.0.0-alpha.1
'@intlify/shared': 12.0.0-alpha.2
source-map-js: 1.2.1
'@intlify/shared@10.0.5': {}
'@intlify/shared@11.1.2': {}
'@intlify/shared@12.0.0-alpha.1': {}
'@intlify/shared@12.0.0-alpha.2': {}
'@intlify/unplugin-vue-i18n@6.0.1(@vue/compiler-dom@3.5.13)(eslint@9.17.0(jiti@2.4.2))(rollup@4.28.1)(typescript@5.7.2)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
dependencies:
@@ -12677,6 +12698,8 @@ snapshots:
bare-events@2.5.0:
optional: true
base64-arraybuffer@1.0.2: {}
base64-js@1.5.1: {}
better-path-resolve@1.0.0:
@@ -13259,6 +13282,10 @@ snapshots:
postcss-selector-parser: 7.0.0
postcss-value-parser: 4.2.0
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
css-prefers-color-scheme@10.0.0(postcss@8.4.49):
dependencies:
postcss: 8.4.49
@@ -14537,6 +14564,11 @@ snapshots:
html-tags@3.3.1: {}
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
htmlparser2@8.0.2:
dependencies:
domelementtype: 2.3.0
@@ -17326,6 +17358,10 @@ snapshots:
text-extensions@2.4.0: {}
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
theme-colors@0.1.0: {}
thenify-all@1.6.0:
@@ -17664,6 +17700,10 @@ snapshots:
util-deprecate@1.0.2: {}
utrie@1.0.2:
dependencies:
base64-arraybuffer: 1.0.2
validate-npm-package-license@3.0.4:
dependencies:
spdx-correct: 3.2.0