فهرست منبع

feat: 增加药品用量统计模块

Tong 2 سال پیش
والد
کامیت
f177000d41

+ 12 - 0
src/api/biz/stats/suppliesStatsApi.ts

@@ -0,0 +1,12 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  drugStats = '/biz/SuppliesStats/drugStats',
+}
+// 查询药品用量API
+export const getDrugStats = (params?: object) => {
+  return defHttp.post({
+    url: Api.drugStats,
+    params,
+  });
+};

+ 133 - 0
src/views/biz/stats/chart/pie.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="pie">
+    <!-- <div class="pie-percentage no-print" @click="handlePercentage"
+      >{{ showPercentage ? '隐藏' : '显示' }}百分比</div> -->
+    <div ref="chartRef" :style="{ height, width }" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, PropType, ref, Ref, watch } from 'vue';
+
+  import { useECharts } from '/@/hooks/web/useECharts';
+  const props = defineProps({
+    width: {
+      type: String as PropType<string>,
+      default: '100%',
+    },
+    height: {
+      type: String as PropType<string>,
+      default: '400px',
+    },
+    data: {
+      type: Object,
+      default: () => {},
+    },
+  });
+  // console.log('props', props);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  const showPercentage = ref(true);
+  onMounted(async () => {
+    setChart();
+  });
+  watch(
+    () => props.data,
+    _newVal => {
+      setChart();
+    },
+  );
+  // function handlePercentage() {
+  //   showPercentage.value = !showPercentage.value;
+  //   setChart();
+  // }
+  function setChart() {
+    const data = props.data?.content;
+    const sum = data?.reduce((pre, cur) => pre + cur.value, 0) || 0;
+    let legend = {} as any;
+    legend =
+      props.data.id != 'bubble' &&
+      props.data.id != 'jam' &&
+      props.data.id != 'noBox' &&
+      props.data.id != 'limit' &&
+      props.data.id != 'lowBattery' &&
+      props.data.id != 'outOfControl' &&
+      props.data.id != 'machine'
+        ? {
+            orient: 'vertical',
+            right: 20,
+            top: 40,
+            bottom: 20,
+          }
+        : {
+            orient: 'horizontal',
+            right: 20,
+            top: 40,
+            type: 'scroll',
+          };
+
+    const lableFormatter = showPercentage.value ? '{b} (数量: {c}, 占比: {d}%)' : '{b} ({c})';
+    setOptions({
+      title: {
+        show: false,
+        text: props.data?.description || '',
+        left: 'left',
+      },
+      tooltip: {
+        trigger: 'item',
+        valueFormatter: (value: number) =>
+          '数量: ' + value + ' 占比: ' + (Number(((value / sum) * 100).toFixed(2)) || 0) + '%',
+      },
+      toolbox: {
+        feature: {
+          saveAsImage: {
+            type: 'png',
+          },
+        },
+      },
+      legend,
+      series: [
+        {
+          type: 'pie',
+          radius: '50%',
+          data: props.data?.content || [],
+          minAngle: 5,
+          avoidLabelOverlap: true,
+          label: {
+            show: true,
+            formatter: lableFormatter,
+            overflow: 'break',
+          },
+          emphasis: {
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)',
+            },
+          },
+        },
+      ],
+    });
+  }
+</script>
+<style lang="less" scoped>
+  .pie {
+    position: relative;
+
+    &-percentage {
+      position: absolute;
+      top: 4px;
+      right: 35px;
+      z-index: 99;
+      border: 1px solid #ccc;
+      border-radius: 4px;
+      padding: 2px 4px;
+      font-size: 12px;
+      cursor: pointer;
+      transition: all 0.3s ease-in-out;
+
+      &:hover {
+        box-shadow: 2px 4px 4px rgb(12 12 12 / 10%);
+      }
+    }
+  }
+</style>

+ 6 - 0
src/views/biz/stats/supplies/data.ts

@@ -0,0 +1,6 @@
+export const BasicTab = [
+  { key: 'suppliesStats', value: 0, title: '耗材用量' },
+  { key: 'pharmaceuticalsStats', value: 1, title: '药品用量' },
+];
+
+export const BasicTabActive = BasicTab[0].key;

+ 48 - 3
src/views/biz/stats/supplies/index.vue

@@ -1,7 +1,52 @@
 <template>
-  <div> 占位符 </div>
+  <div class="m-4">
+    <div class="mb-4">
+      <XTTitle :title="title" :go-back="false" />
+    </div>
+    <div class="py-2 bg-white">
+      <a-tabs v-model:activeKey="activeKey" :destroyInactiveTabPane="true">
+        <a-tab-pane
+          v-for="item in tabData"
+          :key="item.key"
+          :tab="item.title"
+          :class="'tab-' + item.key"
+        >
+          <!-- 耗材用量 0 -->
+          <div v-if="item.value == 0 && info.id"> 耗材用量 </div>
+          <!-- 药品用量 1 -->
+          <div v-if="item.value == 1 && info.id">
+            <PharmaceuticalsStats />
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTab, BasicTabActive } from './data';
+  import PharmaceuticalsStats from './pharmaceuticalsStats/index.vue';
+  import { useRoute } from 'vue-router';
+  import { XTTitle } from '/@/components/XTTitle/index';
 
-<style lang="less" scoped></style>
+  const route = useRoute();
+  console.log('🚀 ~ file: index.vue:25 ~ route:', route);
+  const info = ref({
+    id: String(route.query?.id),
+    name: route.query?.name,
+  });
+  const activeKey = ref(BasicTabActive);
+  const tabData = ref(BasicTab);
+  const title = '药品耗材';
+</script>
+
+<style lang="less" scoped>
+  .tab-medicalDocuments {
+    padding: 0;
+  }
+
+  ::v-deep(.ant-tabs-tab) {
+    padding: 12px;
+  }
+</style>

+ 37 - 0
src/views/biz/stats/supplies/pharmaceuticalsStats/data.ts

@@ -0,0 +1,37 @@
+import { BasicColumn } from '/@/components/Table';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '药品类型',
+    dataIndex: 'type',
+    align: 'left',
+  },
+  {
+    title: '数量',
+    dataIndex: 'sum',
+    align: 'left',
+  },
+  {
+    title: '占比',
+    dataIndex: 'proportion',
+    align: 'left',
+  },
+];
+
+export const detailColumns: BasicColumn[] = [
+  {
+    title: '药品名称',
+    dataIndex: 'type',
+    align: 'left',
+  },
+  {
+    title: '数量',
+    dataIndex: 'sum',
+    align: 'left',
+  },
+  {
+    title: '占比',
+    dataIndex: 'proportion',
+    align: 'left',
+  },
+];

+ 154 - 0
src/views/biz/stats/supplies/pharmaceuticalsStats/index.vue

@@ -0,0 +1,154 @@
+<template>
+  <div class="flex items-center xt-form">
+    <XTForm :form-data="formData" @change="callForm" />
+    <Button type="default" @click="handleDownload"
+      ><Icon icon="icon-xt-download-download_default|iconfont" :size="14" />
+    </Button>
+  </div>
+  <div class="mb-4">
+    <XTTitle :title="title" :go-back="false" />
+  </div>
+  <Row style="max-height: 400px">
+    <Col :span="16">
+      <Pie :data="pieData" />
+    </Col>
+    <Col :span="8">
+      <BasicTable @register="registerTable">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'type'">
+            <span> {{ formatDictValue(durgTypeOptions, record.type) }}</span>
+          </template>
+          <template v-if="column.key === 'sum'">
+            <a @click="handleDetail(record)">{{ record.sum }}</a>
+          </template>
+        </template>
+      </BasicTable>
+    </Col>
+  </Row>
+  <Row style="max-height: 200px">
+    <Col :span="24">
+      <BasicTable @register="registerDetailTable">
+        <template #headerCell="{ column }">
+          <template v-if="column.key === 'type'"> {{ detailName }} </template>
+          <template v-if="column.key === 'sum'"> 数量 </template>
+          <template v-if="column.key === 'proportion'"> 占比 </template>
+        </template>
+      </BasicTable>
+    </Col>
+  </Row>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { Row, Col, Button } from 'ant-design-vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { getDrugStats } from '/@/api/biz/stats/suppliesStatsApi';
+  import { BasicTable, useTable } from '@/components/Table';
+  import { columns, detailColumns } from './data';
+  import { Icon } from '/@/components/Icon';
+  import { formatDictValue } from '/@/utils';
+  import { listDictModel } from '/@/api/common';
+  import Pie from '../../chart/pie.vue';
+  // formdata
+  const title = ref('药品用量统计');
+  const pieData = ref({} as any); // Echart图表组件赋值变量
+  const tableData = ref([]); // 药品类型统计数据
+  const detailTableData = ref([]);
+  const patrolTime = ref([]); // 时间查询参数
+  const detailName = ref('');
+  const formData = [
+    {
+      name: 'patrolTime',
+      componentType: 'RangePicker',
+      placeholder: '请选择维修时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+    {},
+  ];
+  // 药品类型统计表格配置
+  const [registerTable, { setTableData }] = useTable({
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    pagination: false,
+    maxHeight: 200,
+    dataSource: tableData.value,
+    columns: columns,
+  });
+
+  const [registerDetailTable, { setTableData: setDetailTableData }] = useTable({
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    pagination: false,
+    maxHeight: 200,
+    dataSource: detailTableData.value,
+    columns: detailColumns,
+  });
+
+  const durgTypeOptions = ref();
+  onMounted(async () => {
+    durgTypeOptions.value = await listDictModel({ dictCode: 'pht' }); // 查询药品类型字典数据
+    getDatas(); // 执行获取统计数据方法
+  });
+
+  // 查询组件回调
+  async function callForm(data) {
+    patrolTime.value = data.patrolTime || [];
+    getDatas();
+  }
+  // 获取统计数据
+  async function getDatas() {
+    const params = {
+      statsTime: patrolTime.value,
+    };
+    const res = await getDrugStats(params); // 请求药品统计接口
+    tableData.value = res.data;
+    await setTableData(res.data); // 右侧统计数据表格赋值
+    // 组合Echart图表数据格式
+    const dataList = {
+      content: [],
+      description: title.value,
+    };
+    res.data.forEach(item => {
+      dataList.content.push({
+        name: formatDictValue(durgTypeOptions.value, item.type),
+        value: item.sum,
+      });
+    });
+    pieData.value = dataList; // Echart图表赋值
+    handleDetail(tableData.value[0]);
+  }
+  // 统计下载事件
+  function handleDownload() {
+    console.log('下载按钮');
+  }
+  // 点击表格中数字
+  async function handleDetail(record) {
+    const params = {
+      statsTime: patrolTime.value,
+      drugType: record.type,
+    };
+    const res = await getDrugStats(params); // 请求药品统计接口
+    detailName.value = formatDictValue(durgTypeOptions.value, record.type);
+    await setDetailTableData(res.data);
+  }
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.xt-title) {
+    padding-left: 40px;
+    background-color: #f6f8fa;
+  }
+
+  .xt-form {
+    justify-content: flex-end;
+    margin-right: 20px;
+  }
+
+  ::v-deep(.ant-row) {
+    max-height: 460px;
+  }
+</style>