Pārlūkot izejas kodu

feat: 增加药品耗材功能

Tong 2 gadi atpakaļ
vecāks
revīzija
4ddf99fe66

+ 3 - 0
src/api/biz/README.md

@@ -12,6 +12,9 @@
     - vitalsHistoryApi 历史体征数据
   - bed 预约排床
     - scheduledMemoApi 排床备忘录
+  - inventory 库存管理
+  - pharmaceuticalsApi 药品管理
+  - suppliesApi 耗材管理
   - visit 今日就诊
   - engineer 工程师端
     - bedApi 床位管理

+ 65 - 0
src/api/biz/inventory/pharmaceuticalsApi.ts

@@ -0,0 +1,65 @@
+import { defHttp } from '/@/utils/http/axios';
+import { UploadFileParams } from '#/axios';
+import { setParams } from '/@/utils/index';
+import { useGlobSetting } from '/@/hooks/setting';
+const globSetting = useGlobSetting();
+
+enum Api {
+  pharmaceuticalsNumber = '/biz/drug/number',
+  pharmaceuticalsList = '/biz/drug/query/page',
+  pharmaceuticalsDel = '/biz/drug/status',
+  pharmaceuticalsAdd = '/biz/drug/add',
+  pharmaceuticalsEdit = '/biz/drug/edit',
+  pharmaceuticalsById = '/biz/drug/detail',
+  pharmaceuticalsExport = '/biz/supplies/drug/export',
+  pharmaceuticalsImport = '/biz/supplies/drug/import',
+}
+
+export const getpharmaceuticalsList = (params?: object) => {
+  return defHttp.post({ url: Api.pharmaceuticalsList, params: setParams(params) });
+};
+
+export const pharmaceuticalsDel = (id?: String) => {
+  return defHttp.post({ url: Api.pharmaceuticalsDel + '/' + id });
+};
+
+export const getStatusNumber = () => {
+  return defHttp.get({ url: Api.pharmaceuticalsNumber });
+};
+
+export const pharmaceuticalsAdd = (params?: object) => {
+  return defHttp.post({ url: Api.pharmaceuticalsAdd, params: params });
+};
+
+export const pharmaceuticalsEdit = (params?: object) => {
+  return defHttp.post({ url: Api.pharmaceuticalsEdit, params: params });
+};
+
+export const pharmaceuticalsById = (id: string) => {
+  return defHttp.get({ url: Api.pharmaceuticalsById + '/' + id });
+};
+
+/**
+ * @description: 批量导入,权限 - archives:patientBasic:import
+ * @method: POST
+ */
+export function pharmaceuticalsImportBatch(
+  params: UploadFileParams,
+  onUploadProgress: (progressEvent: any) => void,
+) {
+  return defHttp.uploadFile(
+    {
+      url: globSetting.apiUrl + Api.pharmaceuticalsImport,
+      onUploadProgress,
+    },
+    params,
+  );
+}
+
+/**
+ * @description: 导出,权限 - archives:patientBasic:export
+ * @method: POST
+ */
+export const pharmaceuticalsExport = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.pharmaceuticalsExport, params: params });
+};

+ 66 - 0
src/api/biz/inventory/suppliesApi.ts

@@ -0,0 +1,66 @@
+import { defHttp } from '/@/utils/http/axios';
+import { UploadFileParams } from '#/axios';
+import { setParams } from '/@/utils/index';
+import { useGlobSetting } from '/@/hooks/setting';
+const globSetting = useGlobSetting();
+
+enum Api {
+  suppliesNumber = '/biz/consumable/number',
+  suppliesList = '/biz/consumable/query/page',
+
+  suppliesDel = '/biz/consumable/status',
+  suppliesAdd = '/biz/consumable/add',
+  suppliesEdit = '/biz/consumable/edit',
+  suppliesById = '/biz/consumable/detail',
+  suppliesExport = '/biz/supplies/consumable/export',
+  suppliesImport = '/biz/supplies/consumable/import',
+}
+
+export const getsuppliesList = (params?: object) => {
+  return defHttp.post({ url: Api.suppliesList, params: setParams(params) });
+};
+
+export const suppliesDel = (id?: String) => {
+  return defHttp.post({ url: Api.suppliesDel + '/' + id });
+};
+
+export const getStatusNumber = () => {
+  return defHttp.get({ url: Api.suppliesNumber });
+};
+
+export const suppliesAdd = (params?: object) => {
+  return defHttp.post({ url: Api.suppliesAdd, params: params });
+};
+
+export const suppliesEdit = (params?: object) => {
+  return defHttp.post({ url: Api.suppliesEdit, params: params });
+};
+
+export const suppliesById = (id: string) => {
+  return defHttp.get({ url: Api.suppliesById + '/' + id });
+};
+
+/**
+ * @description: 批量导入,权限 - archives:patientBasic:import
+ * @method: POST
+ */
+export function suppliesImportBatch(
+  params: UploadFileParams,
+  onUploadProgress: (progressEvent: any) => void,
+) {
+  return defHttp.uploadFile(
+    {
+      url: globSetting.apiUrl + Api.suppliesImport,
+      onUploadProgress,
+    },
+    params,
+  );
+}
+
+/**
+ * @description: 导出,权限 - archives:patientBasic:export
+ * @method: POST
+ */
+export const suppliesExport = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.suppliesExport, params: params });
+};

+ 1 - 1
src/api/biz/visit/transferApi.ts

@@ -9,7 +9,7 @@ enum Api {
 }
 
 /**
- * @description: 获取透前准备页面人员信息
+ * @description: 获取透前选项卡数量
  * @method: POET
  */
 export const getTypeNumber = () => {

+ 3 - 0
src/views/biz/README.md

@@ -21,6 +21,9 @@
     - dialysis 透析设备
     - bio 生化设备
     - dics 业务字典
+  - inventory 库存管理
+    - pharmaceuticals 药品管理
+    - supplies 耗材管理
   - mission 宣教管理
     - article 宣教库
     - recycling 回收站

+ 85 - 0
src/views/biz/inventory/pharmaceuticals/data.ts

@@ -0,0 +1,85 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel } from '/@/api/common';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '药品类型',
+    dataIndex: 'supplierCategory',
+  },
+  {
+    title: '药品名称',
+    dataIndex: 'name',
+  },
+  {
+    title: '助记码',
+    dataIndex: 'helpCode',
+  },
+  {
+    title: '规格',
+    dataIndex: 'supplierModel',
+  },
+  {
+    title: '单位',
+    dataIndex: 'unit',
+  },
+  {
+    title: '生产厂商',
+    dataIndex: 'makers',
+  },
+  {
+    title: '药品状态',
+    dataIndex: 'disable',
+  },
+  {
+    title: '使用量',
+    dataIndex: 'usageAmount',
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '药品类型',
+    field: 'supplierCategory',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'pht',
+      },
+      placeholder: '请选择药品类型',
+    },
+  },
+  {
+    label: '药品名称',
+    field: 'name',
+    component: 'Input',
+    componentProps: {},
+  },
+  {
+    label: '助记码',
+    field: 'helpCode',
+    component: 'Input',
+  },
+  {
+    label: '规格',
+    field: 'supplierModel',
+    component: 'Input',
+  },
+  {
+    label: '生产商',
+    field: 'makers',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入生产商',
+    },
+  },
+  {
+    label: '单位',
+    field: 'unit',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入单位',
+    },
+  },
+];

+ 94 - 0
src/views/biz/inventory/pharmaceuticals/formModal.vue

@@ -0,0 +1,94 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="880"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm" />
+      </div>
+    </BasicModal>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { dataFormSchema } from './data';
+
+  import {
+    pharmaceuticalsAdd,
+    pharmaceuticalsById,
+    pharmaceuticalsEdit,
+  } from '/@/api/biz/inventory/pharmaceuticalsApi';
+
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增药品' : '编辑药品'));
+  const isUpdate = ref(false);
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 24,
+    },
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      rowId.value = data.record.id;
+      const resData = await pharmaceuticalsById(data.record.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await pharmaceuticalsAdd({ ...values })
+        : await pharmaceuticalsEdit({ ...values, id: rowId.value });
+      !unref(isUpdate) ? createMessage.success('新增成功!') : createMessage.success('编辑成功!');
+      closeModal();
+      emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
+    } finally {
+      setModalProps({ confirmLoading: false, canFullscreen: false });
+    }
+  }
+
+  async function handleCancel() {
+    closeModal();
+    emit('cancel');
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-modal) {
+    background-color: #000;
+  }
+</style>

+ 306 - 0
src/views/biz/inventory/pharmaceuticals/index.vue

@@ -0,0 +1,306 @@
+<template>
+  <div class="m-4 modals">
+    <div>
+      <XTTitle title="药品管理" :right-data="titleData" @click="callTitleClick" />
+      <div class="flex items-center justify-between my-4">
+        <XTTab
+          type="illness"
+          :width="180"
+          :selected="tabSelected"
+          :data="typeOptions"
+          @item-click="callTab"
+        />
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'disable'">
+          <span
+            :class="['table-dot']"
+            :style="{ backgroundColor: formatDictPreColor(responseTypeOptions, record.disable) }"
+          />
+          <span> {{ formatDictValue(responseTypeOptions, record.disable) }}</span>
+        </template>
+        <template v-if="column.key === 'supplierCategory'">
+          <span>
+            {{ formatDictValue(responsesupplierCategoryOptions, record.supplierCategory) }}</span
+          >
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'biz:drug:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'biz:drug:updateStatus',
+                icon: 'icon-tingyong-moren|iconfont',
+                tooltip: '停用',
+                ifShow: record.disable === 0,
+                popConfirm: {
+                  title: '是否确认停用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+              {
+                auth: 'biz:drug:updateStatus',
+                icon: 'icon-xt-revocation_default|iconfont',
+                tooltip: '启用',
+                ifShow: record.disable === 1,
+                popConfirm: {
+                  title: '是否确认启用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+    <ImportModal @register="registerImpModal" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './formModal.vue';
+  import { ImportModal } from '/@/components/XTImport/index';
+
+  import { formatDictValue, formatDictPreColor } from '/@/utils';
+  import { columns } from './data';
+
+  import {
+    getpharmaceuticalsList,
+    pharmaceuticalsDel,
+    getStatusNumber,
+    pharmaceuticalsExport,
+    pharmaceuticalsImportBatch,
+  } from '/@/api/biz/inventory/pharmaceuticalsApi';
+  import { listDictModel } from '/@/api/common';
+  import { useModal } from '/@/components/Modal';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+
+  // 标题数据
+  const titleData = [
+    {
+      type: 'print',
+      icon: 'icon-xt-print_default',
+    },
+    {
+      type: 'import',
+      auth: ['archives:drug:import'],
+      icon: 'icon-xt-import_default',
+    },
+    {
+      type: 'add',
+      auth: ['biz:drug:add'],
+      btnIcon: 'icon-xt-add_default',
+      btnText: '新增药品',
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      name: 'shiftDate',
+      componentType: 'RangePicker',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择日期',
+      width: 240,
+    },
+    {
+      name: 'searchNames',
+      componentType: 'Input',
+      prefix: 'icon-xt-search',
+      placeholder: '请输入药品名称',
+      width: 240,
+    },
+  ];
+  // tab 切换选中
+  const tabSelected = ref();
+  // 操作名称
+  const searchNames = ref('');
+  const shiftDate = ref([]);
+
+  const typeOptions = ref();
+  const responseTypeOptions = ref();
+  const responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+    getTab();
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const [registerImpModal, { openModal: openImpModal }] = useModal();
+
+  // const tableSort = ref([
+  //   {
+  //     field: 'create_time',
+  //     direction: 'DESC',
+  //   },
+  // ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: getpharmaceuticalsList,
+    batchDelApi: pharmaceuticalsDel,
+    // batchExportApi: pharmaceuticalsExport,
+    delAuthList: ['biz:consumable:remove'],
+    rowKey: 'id',
+    columns,
+    showIndexColumn: true,
+    bordered: true,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+  });
+  // 详情按钮事件
+  function handleEdit(record) {
+    openModal(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+
+  // 新增按钮事件
+  function callTitleClick(data) {
+    if (data.type == 'add') {
+      openModal(true, {
+        isUpdate: false,
+        record: data,
+      });
+    } else if (data.type == 'print') {
+      console.log('打印中...');
+    } else if (data.type == 'import') {
+      openImpModal(true, {
+        title: '导入药品数据',
+        importUrl: pharmaceuticalsImportBatch,
+        exportUrl: pharmaceuticalsExport,
+      });
+    }
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    if (record) {
+      await pharmaceuticalsDel(record.id);
+      createMessage.success('停用成功!');
+      clearSelectedRowKeys();
+      await reload();
+      await getTab();
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  async function handleBeforeFetch(params) {
+    console.log('searchNames:::', searchNames.value);
+    return {
+      ...params,
+      // orders: tableSort.value,
+      name: searchNames.value == '' ? undefined : searchNames.value,
+      status: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftDate.value.length <= 0 ? undefined : shiftDate.value,
+    };
+  }
+
+  async function getTab() {
+    typeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    const typeNums = await getStatusNumber(); // 获取各类型数量
+    let typeList = [];
+    typeOptions.value.forEach(ele => {
+      // 变量各类型放置对应数量
+      let typeData = {};
+      Object.keys(typeNums).forEach(numKey => {
+        if (ele.value == numKey) {
+          typeData = {
+            key: ele.value,
+            label: ele.label,
+            value: typeNums[numKey],
+            hasValue: true,
+            prefixColor: ele.prefixColor,
+            hasBracket: true,
+          };
+          typeList.push(typeData);
+        }
+      });
+    });
+    typeList = typeList.reverse();
+    typeList.splice(0, 0, {
+      key: '',
+      label: '全部',
+      value: typeNums.total,
+      hasValue: true,
+      hasBracket: true,
+    });
+    typeOptions.value = typeList;
+    tabSelected.value = typeOptions.value[0].key;
+  }
+
+  //取消按钮事件
+  async function handleCancel() {
+    clearSelectedRowKeys();
+    await reload();
+    await getTab();
+  }
+
+  // 弹窗回调事件
+  async function callSuccess({ isUpdate, values }) {
+    console.log(isUpdate);
+    console.log(values);
+    await reload();
+    await getTab();
+  }
+  // 选项卡组件回调
+  async function callTab(data) {
+    tabSelected.value = data.value;
+    await reload();
+  }
+
+  // 查询组件回调
+  async function callForm(data) {
+    shiftDate.value = data.shiftDate ? data.shiftDate : '';
+    searchNames.value = data.searchNames ? data.searchNames : '';
+    console.log('callForm:::', searchNames.value);
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+
+  ::v-deep(.ant-btn-link) {
+    color: #3d4155;
+  }
+
+  .colUpdateAvatar {
+    display: flex;
+    justify-content: center;
+    text-align: center;
+    line-height: 28px;
+  }
+
+  .colImg {
+    width: 28px;
+    height: 28px;
+    margin-right: 5px;
+  }
+</style>

+ 85 - 0
src/views/biz/inventory/supplies/data.ts

@@ -0,0 +1,85 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel } from '/@/api/common';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '耗材类型',
+    dataIndex: 'supplierCategory',
+  },
+  {
+    title: '耗材名称',
+    dataIndex: 'name',
+  },
+  {
+    title: '助记码',
+    dataIndex: 'helpCode',
+  },
+  {
+    title: '规格',
+    dataIndex: 'supplierModel',
+  },
+  {
+    title: '单位',
+    dataIndex: 'unit',
+  },
+  {
+    title: '生产厂商',
+    dataIndex: 'makers',
+  },
+  {
+    title: '耗材状态',
+    dataIndex: 'disable',
+  },
+  {
+    title: '使用量',
+    dataIndex: 'usageAmount',
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '耗材类型',
+    field: 'supplierCategory',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'pht',
+      },
+      placeholder: '请选择耗材类型',
+    },
+  },
+  {
+    label: '耗材名称',
+    field: 'name',
+    component: 'Input',
+    componentProps: {},
+  },
+  {
+    label: '助记码',
+    field: 'helpCode',
+    component: 'Input',
+  },
+  {
+    label: '规格',
+    field: 'supplierModel',
+    component: 'Input',
+  },
+  {
+    label: '生产商',
+    field: 'makers',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入生产商',
+    },
+  },
+  {
+    label: '单位',
+    field: 'unit',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入单位',
+    },
+  },
+];

+ 90 - 0
src/views/biz/inventory/supplies/formModal.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="880"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm" />
+      </div>
+    </BasicModal>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { dataFormSchema } from './data';
+
+  import { suppliesAdd, suppliesById, suppliesEdit } from '/@/api/biz/inventory/suppliesApi';
+
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增耗材' : '编辑耗材'));
+  const isUpdate = ref(false);
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 24,
+    },
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      rowId.value = data.record.id;
+      const resData = await suppliesById(data.record.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await suppliesAdd({ ...values })
+        : await suppliesEdit({ ...values, id: rowId.value });
+      !unref(isUpdate) ? createMessage.success('新增成功!') : createMessage.success('编辑成功!');
+      closeModal();
+      emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
+    } finally {
+      setModalProps({ confirmLoading: false, canFullscreen: false });
+    }
+  }
+
+  async function handleCancel() {
+    closeModal();
+    emit('cancel');
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-modal) {
+    background-color: #000;
+  }
+</style>

+ 323 - 0
src/views/biz/inventory/supplies/index.vue

@@ -0,0 +1,323 @@
+<template>
+  <div class="m-4 modals">
+    <div>
+      <XTTitle title="耗材管理" :right-data="titleData" @click="callTitleClick" />
+      <div class="flex items-center justify-between my-4">
+        <XTTab
+          type="illness"
+          :width="180"
+          :selected="tabSelected"
+          :data="typeOptions"
+          @item-click="callTab"
+        />
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'disable'">
+          <span
+            :class="['table-dot']"
+            :style="{ backgroundColor: formatDictPreColor(responseTypeOptions, record.disable) }"
+          />
+          <span> {{ formatDictValue(responseTypeOptions, record.disable) }}</span>
+        </template>
+        <template v-if="column.key === 'supplierCategory'">
+          <span>
+            {{ formatDictValue(responsesupplierCategoryOptions, record.supplierCategory) }}</span
+          >
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'biz:consumable:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'biz:consumable:updateStatus',
+                icon: 'icon-tingyong-moren|iconfont',
+                tooltip: '停用',
+                ifShow: record.disable === 0,
+                popConfirm: {
+                  title: '是否确认停用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+              {
+                auth: 'biz:consumable:updateStatus',
+                icon: 'icon-xt-revocation_default|iconfont',
+                tooltip: '启用',
+                ifShow: record.disable === 1,
+                popConfirm: {
+                  title: '是否确认启用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+    <ImportModal @register="registerImpModal" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './formModal.vue';
+  import { ImportModal } from '/@/components/XTImport/index';
+
+  import { formatDictValue, formatDictPreColor } from '/@/utils';
+  import { columns } from './data';
+
+  import {
+    getsuppliesList,
+    suppliesDel,
+    getStatusNumber,
+    suppliesExport,
+    suppliesImportBatch,
+  } from '/@/api/biz/inventory/suppliesApi';
+  import { listDictModel } from '/@/api/common';
+  import { useModal } from '/@/components/Modal';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+
+  // 标题数据
+  const titleData = [
+    {
+      type: 'print',
+      icon: 'icon-xt-print_default',
+    },
+    {
+      type: 'import',
+      auth: ['archives:consumable:import'],
+      icon: 'icon-xt-import_default',
+    },
+    {
+      type: 'add',
+      btnIcon: 'icon-xt-add_default',
+      auth: ['biz:consumable:add'],
+      btnText: '新增耗材',
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      name: 'shiftDate',
+      componentType: 'RangePicker',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择日期',
+      width: 240,
+    },
+    {
+      name: 'searchNames',
+      componentType: 'Input',
+      prefix: 'icon-xt-search',
+      placeholder: '请输入耗材名称',
+      width: 240,
+    },
+  ];
+  // tab 切换选中
+  const tabSelected = ref();
+  // 操作名称
+  const searchNames = ref('');
+  const shiftDate = ref([]);
+
+  const typeOptions = ref();
+  const responseTypeOptions = ref();
+  const responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+    getTab();
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const [registerImpModal, { openModal: openImpModal }] = useModal();
+
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: getsuppliesList,
+    batchDelApi: suppliesDel,
+    // batchExportApi: suppliesExport,
+    delAuthList: ['biz:consumable:remove'],
+    rowKey: 'id',
+    columns,
+    showIndexColumn: true,
+    bordered: true,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  // 详情按钮事件
+  function handleEdit(record) {
+    openModal(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+
+  // 新增按钮事件
+  function callTitleClick(data) {
+    if (data.type == 'add') {
+      openModal(true, {
+        isUpdate: false,
+        record: data,
+      });
+    } else if (data.type == 'print') {
+      console.log('打印中...');
+    } else if (data.type == 'import') {
+      openImpModal(true, {
+        title: '导入耗材数据',
+        importUrl: suppliesImportBatch,
+        exportUrl: suppliesExport,
+      });
+    }
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    if (record) {
+      await suppliesDel(record.id);
+      createMessage.success('停用成功!');
+      clearSelectedRowKeys();
+      await reload();
+      await getTab();
+    }
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  async function handleBeforeFetch(params) {
+    console.log('searchNames:::', searchNames.value);
+    return {
+      ...params,
+      orders: tableSort.value,
+      name: searchNames.value == '' ? undefined : searchNames.value,
+      status: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftDate.value.length <= 0 ? undefined : shiftDate.value,
+    };
+  }
+
+  async function getTab() {
+    typeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    const typeNums = await getStatusNumber(); // 获取各类型数量
+    let typeList = [];
+    typeOptions.value.forEach(ele => {
+      // 变量各类型放置对应数量
+      let typeData = {};
+      Object.keys(typeNums).forEach(numKey => {
+        if (ele.value == numKey) {
+          typeData = {
+            key: ele.value,
+            label: ele.label,
+            value: typeNums[numKey],
+            hasValue: true,
+            prefixColor: ele.prefixColor,
+            hasBracket: true,
+          };
+          typeList.push(typeData);
+        }
+      });
+    });
+    typeList = typeList.reverse();
+    typeList.splice(0, 0, {
+      key: '',
+      label: '全部',
+      value: typeNums.total,
+      hasValue: true,
+      hasBracket: true,
+    });
+    typeOptions.value = typeList;
+    tabSelected.value = typeOptions.value[0].key;
+  }
+
+  //取消按钮事件
+  async function handleCancel() {
+    clearSelectedRowKeys();
+    await reload();
+    await getTab();
+  }
+
+  // 弹窗回调事件
+  async function callSuccess({ isUpdate, values }) {
+    console.log(isUpdate);
+    console.log(values);
+    await reload();
+    await getTab();
+  }
+  // 选项卡组件回调
+  async function callTab(data) {
+    tabSelected.value = data.value;
+    await reload();
+  }
+
+  // 查询组件回调
+  async function callForm(data) {
+    shiftDate.value = data.shiftDate ? data.shiftDate : '';
+    searchNames.value = data.searchNames ? data.searchNames : '';
+    console.log('callForm:::', searchNames.value);
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+
+  ::v-deep(.ant-btn-link) {
+    color: #3d4155;
+  }
+
+  ::v-deep(.ant-input-prefix) {
+    color: #8a99ac;
+  }
+
+  .colUpdateAvatar {
+    display: flex;
+    justify-content: center;
+    text-align: center;
+    line-height: 28px;
+  }
+
+  .colImg {
+    width: 28px;
+    height: 28px;
+    margin-right: 5px;
+  }
+</style>