车间能级提升-智能设备管理系统
zhuguifei
2025-04-16 609b918b24e8dbbe75bf2eaef7a532308d83a708
完成备件模块
已添加5个文件
已修改14个文件
805 ■■■■■ 文件已修改
eims-ui/apps/web-antd/.env.production 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/api/eims/spare-inout/model.d.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/api/eims/spare-inoutdt/index.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/api/eims/spare-inoutdt/model.d.ts 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/api/eims/spare/index.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/components/basis-sub-table.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/components/spare-modal.vue 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-in/data.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-in/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-in/spare-in-drawer.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-inoutdt/data.tsx 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-out/data.tsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-out/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-out/select-spare-table.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare-out/spare-out-drawer.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare/data.tsx 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/src/views/eims/spare/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims/ruoyi-admin/src/main/resources/application-prod.yml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/DictConstants.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eims-ui/apps/web-antd/.env.production
@@ -19,7 +19,7 @@
VITE_GLOB_API_URL=/prod-api
# å…¨å±€åР坆开关(即开启了加解密功能才会生效 ä¸æ˜¯å…¨éƒ¨æŽ¥å£åР坆 éœ€è¦å’ŒåŽç«¯å¯¹åº”)
VITE_GLOB_ENABLE_ENCRYPT=true
VITE_GLOB_ENABLE_ENCRYPT=false
# RSA公钥 è¯·æ±‚加密使用 æ³¨æ„è¿™ä¸¤ä¸ªæ˜¯ä¸¤å¯¹RSA公私钥 è¯·æ±‚加密-后端解密是一对 å“åº”解密-后端加密是一对
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
# RSA私钥 å“åº”解密使用 æ³¨æ„è¿™ä¸¤ä¸ªæ˜¯ä¸¤å¯¹RSA公私钥 è¯·æ±‚加密-后端解密是一对 å“åº”解密-后端加密是一对
@@ -28,5 +28,5 @@
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# å¼€å¯SSE
VITE_GLOB_SSE_ENABLE=true
VITE_GLOB_SSE_ENABLE=false
eims-ui/apps/web-antd/src/api/eims/spare-inout/model.d.ts
@@ -20,6 +20,11 @@
  chargeUser: number;
  /**
   * ç»åŠžéƒ¨é—¨
   */
  chargeDept: number;
  /**
   * å·¥å•类型(1-入库单  2-出库单) å­—å…¸
   */
  type: string;
@@ -38,4 +43,8 @@
   * å¤‡æ³¨
   */
  remark: string;
  /**
   * å‡ºå…¥åº“选择的备件列表
   */
  spareList: any;
}
eims-ui/apps/web-antd/src/api/eims/spare-inoutdt/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
import type { IDS, PageQuery, PageResult } from '#/api/common';
import type { SpareInoutdtVO } from '#/api/eims/spare-inoutdt/model';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
  root = '/eims/spareInoutdt',
  spareInoutdtExport = '/eims/spareInoutdt/export',
  spareInoutdtList = '/eims/spareInoutdt/list'
}
/**
 * æŸ¥è¯¢ã€å¤‡ä»¶å‡ºå…¥åº“明细】列表
 * @param query
 * @returns {*}
 */
export function listSpareInoutdt(params?: PageQuery) {
  return requestClient.get<PageResult<SpareInoutdtVO>>(Api.spareInoutdtList, { params });
}
/**
 * æŸ¥è¯¢ã€å¤‡ä»¶å‡ºå…¥åº“明细】详细
 * @param spareInoutdtId
 */
export function getSpareInoutdt(spareInoutdtId: any) {
  return requestClient.get<SpareInoutdtVO>(`${Api.root}/${spareInoutdtId}`);
}
/**
 * æ–°å¢žã€å¤‡ä»¶å‡ºå…¥åº“明细】
 * @param data
 */
export function addSpareInoutdt(data: any) {
  return requestClient.postWithMsg<void>(Api.root, data);
}
/**
 * ä¿®æ”¹ã€å¤‡ä»¶å‡ºå…¥åº“明细】
 * @param data
 */
export function updateSpareInoutdt(data: any) {
  return requestClient.putWithMsg<void>(Api.root, data);
}
/**
 * åˆ é™¤ã€å¤‡ä»¶å‡ºå…¥åº“明细】
 * @param spareInoutdtIds
 */
export function delSpareInoutdt(spareInoutdtIds: IDS) {
  return requestClient.deleteWithMsg<void>(`${Api.root}/${spareInoutdtIds}`);
}
/**
 * å¯¼å‡ºã€å¤‡ä»¶å‡ºå…¥åº“明细】
 * @param data
 */
export function spareInoutdtExport(data: any) {
  return commonExport(Api.spareInoutdtExport, data);
}
eims-ui/apps/web-antd/src/api/eims/spare-inoutdt/model.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
export interface SpareInoutdtVO {
  /**
   *
   */
  id: number | string;
  /**
   * å‡ºåº“单或入库单id
   */
  inoutId: number | string;
  /**
   * å¤‡ä»¶id
   */
  spareId: number | string;
  /**
   * ä¹‹å‰åº“å­˜
   */
  beforeStock: number;
  /**
   * å®žé™…库存
   */
  actualStock: number;
  /**
   * æ•°é‡
   */
  quantity: number;
  /**
   * å•ä»·
   */
  unitPrice: number;
  /**
   * é‡‘额
   */
  amount: number;
  /**
   * å¤‡æ³¨
   */
  remark: string;
}
eims-ui/apps/web-antd/src/api/eims/spare/index.ts
@@ -5,6 +5,7 @@
import { requestClient } from '#/api/request';
enum Api {
  inoutList = '/eims/spare/listInout',
  root = '/eims/spare',
  spareExport = '/eims/spare/export',
  spareList = '/eims/spare/list'
@@ -20,6 +21,10 @@
  return requestClient.get<PageResult<SpareVO>>(Api.spareList, { params });
}
export function listInout(params?: PageQuery) {
  return requestClient.get<PageResult<any>>(Api.inoutList, { params });
}
/**
 * æŸ¥è¯¢ã€å¤‡ä»¶å°è´¦ã€‘详细
 * @param spareId
eims-ui/apps/web-antd/src/views/eims/components/basis-sub-table.vue
@@ -16,6 +16,7 @@
const columns = props?.columns?.filter((i) => i.field !== 'action');
const gridOptions: VxeGridProps = {
  size: 'mini',
  checkboxConfig: {
    // é«˜äº®
    highlight: true,
eims-ui/apps/web-antd/src/views/eims/components/spare-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
<script setup lang="ts">
import type { VxeGridProps } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { DictEnum } from '@vben/constants';
import { message } from 'ant-design-vue';
import { renderDict } from '#/utils/render';
import InnerView from '#/views/eims/spare/index.vue';
const emit = defineEmits<{ updateSelect: [any] }>();
const [BasicModal, modalApi] = useVbenModal({
  fullscreenButton: false,
  draggable: true,
  onCancel: handleCancel,
  onConfirm: handleConfirm
});
const innerView = ref();
async function handleConfirm() {
  try {
    modalApi.modalLoading(true);
    const tableSelect = innerView.value.tableSelect();
    const eList = tableSelect.filter((item: any) => !item.actualStock && item.actualStock <= 0);
    // æ£€æµ‹é€‰æ‹©çš„备件库存是否正常
    if (eList.length > 0) {
      message.error('存在库存不足备件,请重新选择');
      return false;
    }
    emit('updateSelect', tableSelect);
    await handleCancel();
  } catch (error) {
    console.error(error);
  } finally {
    modalApi.modalLoading(false);
  }
}
async function handleCancel() {
  modalApi.close();
}
</script>
<template>
  <BasicModal :fullscreen-button="true" class="w-[800px]">
    <InnerView ref="innerView" />
  </BasicModal>
</template>
<style scoped></style>
eims-ui/apps/web-antd/src/views/eims/spare-in/data.tsx
@@ -118,6 +118,17 @@
    label: '供应商'
  },
  {
    component: 'Input',
    fieldName: 'openSpare',
    label: '选择备件',
    formItemClass: 'col-span-1 w-[80px]'
  },
  {
    component: 'Input',
    fieldName: 'outSpareList',
    label: ''
  },
  {
    component: 'TreeSelect',
    // åœ¨drawer里更新 è¿™é‡Œä¸éœ€è¦é»˜è®¤çš„componentProps
    defaultValue: undefined,
eims-ui/apps/web-antd/src/views/eims/spare-in/index.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { $t } from '@vben/locales';
@@ -16,6 +16,9 @@
import { columns, querySchema } from './data';
import drawer from './spare-in-drawer.vue';
import { columns as inoutCol } from '#/views/eims/spare-inoutdt/data';
import { listSpareInoutdt } from '#/api/eims/spare-inoutdt';
import BasisSubTable from '#/views/eims/components/basis-sub-table.vue';
const formOptions: VbenFormProps = {
  commonConfig: {
@@ -69,12 +72,16 @@
  },
  id: 'spre-inout-index'
};
const inoutId = ref<string>();
const [BasicTable, tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions,
  gridEvents: {
    sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams)
    sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
    cellClick: (e: any) => {
      const { row } = e;
      inoutId.value = row.id;
    }
  }
});
@@ -190,8 +197,8 @@
<template>
  <Page :auto-content-height="true">
    <div class="flex h-full gap-[8px]">
      <BasicTable class="flex-1 overflow-hidden" table-title="备件入库单列表">
    <div class="flex h-full gap-[8px] flex-col">
      <BasicTable class="h-2/3" table-title="备件入库单列表">
        <template #toolbar-tools>
          <Space>
            <a-button v-access:code="['eims:spareInout:export']" @click="handleDownloadExcel">
@@ -231,6 +238,14 @@
          </Space>
        </template>
      </BasicTable>
      <BasisSubTable
        :columns="inoutCol"
        :list-api="listSpareInoutdt"
        :req-value="inoutId"
        class="h-1/3"
        req-key="inoutId"
        title="入库明细"
      />
    </div>
    <Drawer @reload="tableApi.query()" />
  </Page>
eims-ui/apps/web-antd/src/views/eims/spare-in/spare-in-drawer.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { addFullName, cloneDeep, getPopupContainer } from '@vben/utils';
@@ -11,7 +11,72 @@
import { drawerSchema } from './data';
import CodeInput from '#/views/eims/components/code-input.vue';
import spareModal from '#/views/eims/components/spare-modal.vue';
import SelectSpareTable from '#/views/eims/spare-out/select-spare-table.vue';
import { message } from 'ant-design-vue';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { renderDict } from '#/utils/render';
import { DictEnum } from '@vben/constants';
/**
 * å‡ºåº“单选择的备件数据
 */
const outSpareList = ref([]);
const selectSpareTable = ref();
const outCol: VxeGridProps['columns'] = [
  {
    field: 'action',
    slots: { default: 'action' },
    title: '删除',
    width: 60
  },
  {
    title: '备件名称',
    field: 'name',
    width: 180
  },
  {
    title: '备件编码',
    field: 'code',
    width: 120
  },
  {
    title: '备件型号',
    field: 'modelNo',
    width: 100
  },
  {
    title: '计量单位',
    field: 'unit',
    slots: {
      default: ({ row }) => {
        if (!row.unit || row.unit === '') {
          return '';
        }
        return renderDict(row.unit, DictEnum.EIMS_SPARE_UNIT);
      }
    },
    width: 80
  },
  {
    title: '实际库存',
    field: 'actualStock',
    width: 100
  },
  {
    title: '数量',
    field: 'quantity',
    editRender: {
      name: 'input'
    },
    width: 80
  },
  {
    title: '参考价',
    field: 'referPrice',
    width: 90
  }
];
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
@@ -39,6 +104,7 @@
    if (!isOpen) {
      return null;
    }
    outSpareList.value = [];
    drawerApi.drawerLoading(true);
    const { id } = drawerApi.getData() as { id?: number | string };
    isUpdate.value = !!id;
@@ -48,6 +114,10 @@
    if (isUpdate.value && id) {
      const record = await getSpareInout(id);
      await formApi.setValues(record);
      outSpareList.value = record?.spareList;
      if (isUpdate.value && record.chargeDept) {
        await setupUserOptions(record.chargeDept);
      }
    }
    drawerApi.drawerLoading(false);
@@ -127,7 +197,15 @@
    if (!valid) {
      return;
    }
    const selectSpareList = selectSpareTable.value.tableData();
    // æ£€æµ‹æ˜¯å¦è¾“入出库数量
    const eList = selectSpareList.filter((item: any) => !item.quantity || item.quantity <= 0);
    if (selectSpareList.length<= 0 || eList.length > 0) {
      message.error('入库数量为空,请检查!');
      return false;
    }
    const data = cloneDeep(await formApi.getValues());
    data.spareList = selectSpareList;
    await (isUpdate.value ? updateSpareInout(data) : addSpareInout(data));
    emit('reload');
    await handleCancel();
@@ -142,14 +220,42 @@
  drawerApi.close();
  await formApi.resetForm();
}
// å¤‡ä»¶modal
const [SpareModal, spareModalApi] = useVbenModal({
  connectedComponent: spareModal,
  draggable: true,
  title: '选择备件'
});
function handleSpareModal() {
  spareModalApi.setData({});
  spareModalApi.open();
}
/**
 * é€‰æ‹©çš„备件
 * @param spareList
 */
function selectSpare(spareList: any) {
  outSpareList.value = spareList;
}
</script>
<template>
  <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
  <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[1000px]">
    <BasicForm>
      <template #orderCode="slotProps">
        <CodeInput v-bind="slotProps" :disabled="isUpdate" prefix="RK" />
      </template>
      <template #openSpare="slotProps">
        <a-button type="primary" v-bind="slotProps" :disabled="isUpdate" @click.stop="handleSpareModal">添加备件</a-button>
      </template>
      <template #outSpareList>
        <SelectSpareTable ref="selectSpareTable" :columns="outCol" :data="outSpareList" :is-update="isUpdate" />
      </template>
    </BasicForm>
    <SpareModal class="w-[1200px]" @update-select="selectSpare" />
  </BasicDrawer>
</template>
eims-ui/apps/web-antd/src/views/eims/spare-inoutdt/data.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { type FormSchemaGetter } from '#/adapter/form';
import { renderDict } from '#/utils/render';
import { DictEnum } from '@vben/constants';
export const querySchema: FormSchemaGetter = () => [];
export const columns: VxeGridProps['columns'] = [
  { type: 'checkbox', width: 60, fixed: 'left' },
  {
    title: '备件名称',
    field: 'spareName',
    minWidth: 120
  },
  {
    title: '备件编号',
    field: 'spareCode',
    minWidth: 120
  },
  {
    title: '规格型号',
    field: 'modelNo',
    minWidth: 100
  },
  {
    title: '计量单位',
    field: 'unit',
    sortable: true,
    slots: {
      default: ({ row }) => {
        if (!row.unit || row.unit === '') {
          return '';
        }
        return renderDict(row.unit, DictEnum.EIMS_SPARE_UNIT);
      }
    },
    width: 100
  },
  {
    title: '之前库存',
    field: 'beforeStock',
    minWidth: 100
  },
  {
    title: '当前库存',
    field: 'actualStock',
    minWidth: 100
  },
  {
    title: '数量',
    field: 'quantity',
    minWidth: 80
  },
  {
    title: '单价',
    field: 'unitPrice',
    minWidth: 80
  },
  {
    title: '金额',
    field: 'amount',
    minWidth: 80
  }
];
export const drawerSchema: FormSchemaGetter = () => [];
eims-ui/apps/web-antd/src/views/eims/spare-out/data.tsx
@@ -84,6 +84,8 @@
  }
];
export const drawerSchema: FormSchemaGetter = () => [
  {
    component: 'Input',
@@ -118,6 +120,17 @@
    label: '客户'
  },
  {
    component: 'Input',
    fieldName: 'openSpare',
    label: '选择备件',
    formItemClass: 'col-span-1 w-[80px]'
  },
  {
    component: 'Input',
    fieldName: 'outSpareList',
    label: ''
  },
  {
    component: 'TreeSelect',
    // åœ¨drawer里更新 è¿™é‡Œä¸éœ€è¦é»˜è®¤çš„componentProps
    defaultValue: undefined,
eims-ui/apps/web-antd/src/views/eims/spare-out/index.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { $t } from '@vben/locales';
@@ -16,6 +16,9 @@
import { columns, querySchema } from './data';
import drawer from './spare-out-drawer.vue';
import { columns as inoutCol } from '#/views/eims/spare-inoutdt/data';
import { listSpareInoutdt } from '#/api/eims/spare-inoutdt';
import BasisSubTable from '#/views/eims/components/basis-sub-table.vue';
const formOptions: VbenFormProps = {
  commonConfig: {
@@ -69,12 +72,16 @@
  },
  id: 'spre-inout-index'
};
const inoutId = ref<string>();
const [BasicTable, tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions,
  gridEvents: {
    sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams)
    sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
    cellClick: (e: any) => {
      const { row } = e;
      inoutId.value = row.id;
    }
  }
});
@@ -190,8 +197,8 @@
<template>
  <Page :auto-content-height="true">
    <div class="flex h-full gap-[8px]">
      <BasicTable class="flex-1 overflow-hidden" table-title="备件出库单列表">
    <div class="flex h-full gap-[8px] flex-col">
      <BasicTable class="h-2/3" table-title="备件出库单列表">
        <template #toolbar-tools>
          <Space>
            <a-button v-access:code="['eims:spareInout:export']" @click="handleDownloadExcel">
@@ -231,6 +238,14 @@
          </Space>
        </template>
      </BasicTable>
      <BasisSubTable
        :columns="inoutCol"
        :list-api="listSpareInoutdt"
        :req-value="inoutId"
        class="h-1/3"
        req-key="inoutId"
        title="出库明细"
      />
    </div>
    <Drawer @reload="tableApi.query()" />
  </Page>
eims-ui/apps/web-antd/src/views/eims/spare-out/select-spare-table.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { reactive, ref, watch } from 'vue';
import { $t } from '@vben/locales';
import { getVxePopupContainer } from '@vben/utils';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps, vxeSortEvent } from '#/adapter/vxe-table';
interface Props {
  title?: string;
  columns?: VxeGridProps['columns'];
  data: any;
  isUpdate?: boolean;
}
const props = defineProps<Props>();
const responsiveData = reactive(props.data);
defineExpose({
  tableData
});
watch(
  () => props.data,
  (data) => {
    responsiveData.splice(0, responsiveData.length, ...data);
  }
);
const gridOptions: VxeGridProps = {
  checkboxConfig: {
    // é«˜äº®
    highlight: true,
    // ç¿»é¡µæ—¶ä¿ç•™é€‰ä¸­çŠ¶æ€
    reserve: true
    // ç‚¹å‡»è¡Œé€‰ä¸­
    // trigger: 'row'
  },
  columns: props.columns,
  height: 'auto',
  keepSource: true,
  data: responsiveData,
  pagerConfig: {
    enabled: false
  },
  toolbarConfig: {
    enabled: false
  },
  rowConfig: {
    isHover: true,
    keyField: 'id'
  },
  sortConfig: {
    // è¿œç¨‹æŽ’序
    remote: true,
    // æ”¯æŒå¤šå­—段排序 é»˜è®¤å…³é—­
    multiple: true
  },
  editConfig: {
    mode: 'cell',
    trigger: 'click'
  },
  id: 'local-table'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
  gridOptions,
  gridEvents: {
    sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams)
  }
});
function handleDelete(row: Recordable<any>) {
  const index = responsiveData.findIndex((item: any) => item.id === row.id);
  if (index !== -1) {
    responsiveData.splice(index, 1);
  }
}
// é€‰ä¸­æ•°æ®
function tableData() {
  return tableApi.grid.getData();
}
/**
 * TODO åŽç»­æ‰©å±•点击事件
 */
const slotName = ref<string>('equName');
</script>
<template>
  <div class="w-full h-min">
    <BasicTable :table-title="title" size="small">
      <template #[slotName]="{ row }">
        <Space>
          <span>{{ row[slotName] }}</span>
        </Space>
      </template>
      <template #action="{ row }">
        <Space>
          <Popconfirm :get-popup-container="getVxePopupContainer" placement="left" title="确认删除?" @confirm="handleDelete(row)">
            <ghost-button :disabled="isUpdate" danger @click.stop="">
              {{ $t('pages.common.delete') }}
            </ghost-button>
          </Popconfirm>
        </Space>
      </template>
    </BasicTable>
  </div>
</template>
<style lang="scss" scoped>
:deep(.p-2) {
  padding: 0;
}
</style>
eims-ui/apps/web-antd/src/views/eims/spare-out/spare-out-drawer.vue
@@ -1,18 +1,88 @@
<script setup lang="ts">
import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { DictEnum } from '@vben/constants';
import { $t } from '@vben/locales';
import { addFullName, cloneDeep, getPopupContainer } from '@vben/utils';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { addSpareInout, getSpareInout, updateSpareInout } from '#/api/eims/spare-inout';
import { getDeptTree, userList } from '#/api/system/user';
import { renderDict } from '#/utils/render';
import CodeInput from '#/views/eims/components/code-input.vue';
import spareModal from '#/views/eims/components/spare-modal.vue';
import { drawerSchema } from './data';
import CodeInput from '#/views/eims/components/code-input.vue';
import SelectSpareTable from './select-spare-table.vue';
const emit = defineEmits<{ reload: [] }>();
/**
 * å‡ºåº“单选择的备件数据
 */
const outSpareList = ref([]);
const selectSpareTable = ref();
const outCol: VxeGridProps['columns'] = [
  {
    field: 'action',
    slots: { default: 'action' },
    title: '删除',
    width: 60
  },
  {
    title: '备件名称',
    field: 'name',
    width: 180
  },
  {
    title: '备件编码',
    field: 'code',
    width: 120
  },
  {
    title: '备件型号',
    field: 'modelNo',
    width: 100
  },
  {
    title: '计量单位',
    field: 'unit',
    slots: {
      default: ({ row }) => {
        if (!row.unit || row.unit === '') {
          return '';
        }
        return renderDict(row.unit, DictEnum.EIMS_SPARE_UNIT);
      }
    },
    width: 80
  },
  {
    title: '实际库存',
    field: 'actualStock',
    width: 100
  },
  {
    title: '数量',
    field: 'quantity',
    editRender: {
      name: 'input'
    },
    width: 80
  },
  {
    title: '参考价',
    field: 'referPrice',
    width: 90
  }
];
const isUpdate = ref(false);
const title = computed(() => {
@@ -42,12 +112,18 @@
    drawerApi.drawerLoading(true);
    const { id } = drawerApi.getData() as { id?: number | string };
    isUpdate.value = !!id;
    outSpareList.value = [];
    // åˆå§‹åŒ–
    await setupDeptSelect();
    // æ›´æ–° && èµ‹å€¼
    if (isUpdate.value && id) {
      const record = await getSpareInout(id);
      await formApi.setValues(record);
      // æ›´æ–°å‡ºåº“单的备件明细
      outSpareList.value = record?.spareList;
      if (isUpdate.value && record.chargeDept) {
        await setupUserOptions(record.chargeDept);
      }
    }
    drawerApi.drawerLoading(false);
@@ -104,7 +180,7 @@
          /** æ ¹æ®éƒ¨é—¨ID加载用户 */
          await setupUserOptions(deptId);
          /** å˜åŒ–后需要重新选择用户 */
          formModel.operatorId = undefined;
          formModel.chargeUser = undefined;
        },
        placeholder: '请选择',
        showSearch: true,
@@ -120,6 +196,7 @@
    }
  ]);
}
async function handleConfirm() {
  try {
    drawerApi.drawerLoading(true);
@@ -127,7 +204,15 @@
    if (!valid) {
      return;
    }
    const selectSpareList = selectSpareTable.value.tableData();
    // æ£€æµ‹æ˜¯å¦è¾“入出库数量
    const eList = selectSpareList.filter((item: any) => !item.quantity || item.quantity <= 0 || item.quantity > item.actualStock);
    if (selectSpareList.length<= 0 ||eList.length > 0) {
      message.error('出库数量为空或大于库存,请检查!');
      return false;
    }
    const data = cloneDeep(await formApi.getValues());
    data.spareList = selectSpareList;
    await (isUpdate.value ? updateSpareInout(data) : addSpareInout(data));
    emit('reload');
    await handleCancel();
@@ -142,14 +227,43 @@
  drawerApi.close();
  await formApi.resetForm();
}
// å¤‡ä»¶modal
const [SpareModal, spareModalApi] = useVbenModal({
  connectedComponent: spareModal,
  draggable: true,
  title: '选择备件'
});
function handleSpareModal() {
  spareModalApi.setData({});
  spareModalApi.open();
}
/**
 * é€‰æ‹©çš„备件
 * @param spareList
 */
function selectSpare(spareList: any) {
  outSpareList.value = spareList;
}
</script>
<template>
  <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
  <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[1000px]">
    <BasicForm>
      <template #orderCode="slotProps">
        <CodeInput v-bind="slotProps" :disabled="isUpdate" prefix="CK" />
      </template>
      <template #openSpare="slotProps">
        <a-button type="primary" v-bind="slotProps" :disabled="isUpdate" @click.stop="handleSpareModal">添加备件</a-button>
      </template>
      <template #outSpareList>
        <SelectSpareTable ref="selectSpareTable" :columns="outCol" :data="outSpareList" :is-update="isUpdate" />
      </template>
    </BasicForm>
    <SpareModal class="w-[1200px]" @update-select="selectSpare" />
  </BasicDrawer>
</template>
eims-ui/apps/web-antd/src/views/eims/spare/data.tsx
@@ -1,11 +1,13 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { getPopupContainer } from '@vben/utils';
import { Tag } from 'ant-design-vue';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';
import { getPopupContainer } from '@vben/utils';
export const querySchema: FormSchemaGetter = () => [
  {
@@ -84,7 +86,7 @@
    sortable: true,
    slots: {
      default: ({ row }) => {
        if (row.unit === null || row.unit === '') {
        if (!row.unit || row.unit === '') {
          return '';
        }
        return renderDict(row.unit, DictEnum.EIMS_SPARE_UNIT);
@@ -138,6 +140,87 @@
  }
];
export const inoutCol: VxeGridProps['columns'] = [
  {
    title: '出入库单号',
    field: 'orderCode',
    width: 180
  },
  {
    title: '日期',
    field: 'orderTime',
    width: 180
  },
  {
    title: '方向',
    field: 'type1',
    width: 80,
    slots: {
      default: ({ row }) => {
        const type = row.type;
        switch (type) {
          case '1': {
            return <Tag color="green"> å…¥åº“ </Tag>;
          }
          case '2': {
            return <Tag color="blue"> å‡ºåº“ </Tag>;
          }
          // No default
        }
        return '';
      }
    }
  },
  {
    title: '类型',
    field: 'type',
    width: 100,
    slots: {
      default: ({ row }) => {
        if (!row.type || row.type === '') {
          return '';
        }
        return renderDict(row.type, DictEnum.SPARE_INOUT_TYPE);
      }
    }
  },
  {
    title: '入库',
    field: 'inQuantity',
    width: 120,
    slots: {
      default: ({ row }) => {
        return row.type && row.type === '1' ? row.quantity : '';
      }
    }
  },
  {
    title: '出库',
    field: 'outQuantity',
    width: 120,
    slots: {
      default: ({ row }) => {
        return row.type && row.type === '2' ? row.quantity : '';
      }
    }
  },
  {
    title: '库存',
    field: 'actualStock',
    width: 80
  },
  {
    title: '单价',
    field: 'unitPrice',
    width: 80
  },
  {
    title: '金额',
    field: 'amount',
    width: 80
  }
];
export const drawerSchema: FormSchemaGetter = () => [
  {
    component: 'Input',
@@ -173,7 +256,7 @@
      show: () => false,
      triggerFields: ['imgUrl']
    },
    label: '备件预览',
    label: '备件预览'
  },
  {
    component: 'Input',
eims-ui/apps/web-antd/src/views/eims/spare/index.vue
@@ -10,11 +10,12 @@
import { Image, Modal, Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, vxeCheckboxChecked, type VxeGridProps, vxeSortEvent } from '#/adapter/vxe-table';
import { delSpare, listSpare, spareExport } from '#/api/eims/spare';
import { delSpare, listInout, listSpare, spareExport } from '#/api/eims/spare';
import { configInfoByKey } from '#/api/system/config';
import { commonDownloadExcel } from '#/utils/file/download';
import BasisSubTable from '#/views/eims/components/basis-sub-table.vue';
import { columns, querySchema } from './data';
import { columns, inoutCol, querySchema } from './data';
import spareDrawer from './spare-drawer.vue';
import SpareTypeTree from './spare-type-tree.vue';
@@ -59,7 +60,7 @@
  height: 'auto',
  keepSource: true,
  pagerConfig: {
    enabled: false,
    enabled: false
  },
  proxyConfig: {
    enabled: true,
@@ -84,7 +85,7 @@
    keyField: 'id'
  },
  columnConfig: {
    resizable: true,
    resizable: true
  },
  sortConfig: {
    // è¿œç¨‹æŽ’序
@@ -94,12 +95,16 @@
  },
  id: 'eims-spare-index'
};
const id = ref<string>();
const [BasicTable, tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions,
  gridEvents: {
    sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams)
    sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
    cellClick: (e: any) => {
      const { row } = e;
      id.value = row.id;
    }
  }
});
@@ -163,7 +168,9 @@
  <Page :auto-content-height="true">
    <div class="flex h-full gap-[8px]">
      <SpareTypeTree v-model:select-type-id="selectTypeId" class="w-[260px]" @reload="() => tableApi.reload()" @select="() => tableApi.reload()" />
      <BasicTable class="flex-1 overflow-hidden" table-title="备件台账">
      <div class="flex-1 overflow-hidden">
        <div class="flex h-full gap-[8px] flex-col">
          <BasicTable class="h-2/3" table-title="备件台账">
        <template #toolbar-tools>
          <Space>
            <a-button v-access:code="['eims:spare:export']" @click="handleDownloadExcel">
@@ -202,12 +209,15 @@
          </Space>
        </template>
      </BasicTable>
          <BasisSubTable :columns="inoutCol" :list-api="listInout" :req-value="id" class="h-1/3" req-key="id" title="出入库明细" />
        </div>
      </div>
    </div>
    <SpareDrawer @reload="tableApi.query()" />
  </Page>
</template>
<style>
<style lang="scss" scoped>
/* ç»Ÿä¸€æ‰€æœ‰åˆ—的边框和行高 */
.vxe-table--body .vxe-body--row .vxe-body--column {
  height: 56px !important;
eims/ruoyi-admin/src/main/resources/application-prod.yml
@@ -48,9 +48,9 @@
          driverClassName: com.mysql.cj.jdbc.Driver
          # jdbc æ‰€æœ‰å‚数配置参考 https://lionli.blog.csdn.net/article/details/122018562
          # rewriteBatchedStatements=true æ‰¹å¤„理优化 å¤§å¹…提升批量插入更新删除性能(对数据库有性能损耗 ä½¿ç”¨æ‰¹é‡æ“ä½œåº”考虑性能问题)
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
          url: jdbc:mysql://localhost:3306/eims?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
          username: root
          password: root
          password: 123456
        # ä»Žåº“数据源
        slave:
          lazy: true
@@ -103,7 +103,7 @@
    # æ•°æ®åº“索引
    database: 0
    # redis å¯†ç å¿…须配置
    password: ruoyi123
    #password: ruoyi123
    # è¿žæŽ¥è¶…æ—¶æ—¶é—´
    timeout: 10s
    # æ˜¯å¦å¼€å¯ssl
eims/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/DictConstants.java
@@ -161,4 +161,16 @@
    }
    /**
     *备件出入库类型
     */
    String SPARE_INOUT_TYPE = "spare_inout_type";
    interface SPARE_INOUT_TYPE_DETAIL {
        String RK = "1";// é‡‡è´­å…¥åº“
        String CK = "2"; // é¢†ç”¨å‡ºåº“
    }
}