diff --git a/package.json b/package.json index b74a464..40e68d9 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "dayjs": "~1.11.10", "echarts": "^5.5.0", "file-saver": "~2.0.5", + "js-file-download": "^0.4.12", "lodash-es": "~4.17.21", "mitt": "~3.0.1", "nprogress": "~1.0.0-1", @@ -78,13 +79,12 @@ "@vitejs/plugin-vue": "~5.0.4", "@vitejs/plugin-vue-jsx": "~3.1.0", "@vue/tsconfig": "^0.5.1", - "commitizen": "~4.3.0", "cliui": "^8.0.1", - "cz-customizable": "^7.0.0", + "commitizen": "~4.3.0", "conventional-changelog-cli": "~4.1.0", - "standard-version": "^9.5.0", "core-js": "^3.36.0", "cross-env": "~7.0.3", + "cz-customizable": "^7.0.0", "eslint": "~8.57.0", "eslint-config-prettier": "~9.1.0", "eslint-define-config": "~2.1.0", @@ -103,6 +103,7 @@ "prettier": "~3.2.5", "pretty-quick": "~4.0.0", "rimraf": "~5.0.5", + "standard-version": "^9.5.0", "stylelint": "~16.2.1", "stylelint-config-property-sort-order-smacss": "^10.0.0", "stylelint-config-recommended": "~14.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2477bc5..612707e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: file-saver: specifier: ~2.0.5 version: 2.0.5 + js-file-download: + specifier: ^0.4.12 + version: 0.4.12 lodash-es: specifier: ~4.17.21 version: 4.17.21 @@ -7727,6 +7730,10 @@ packages: resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} dev: true + /js-file-download@0.4.12: + resolution: {integrity: sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} diff --git a/src/api/backend/api/materialsInventory.ts b/src/api/backend/api/materialsInventory.ts index 8bf4c82..d842d72 100644 --- a/src/api/backend/api/materialsInventory.ts +++ b/src/api/backend/api/materialsInventory.ts @@ -28,6 +28,19 @@ export async function materialsInventoryList( }); } +/** 导出原材料盘点表 GET /api/materials-inventory/export*/ +export async function materialsInventoryExport( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.MaterialsInventoryExportParams, + options?: RequestOptions, +) { + const { ...queryParams } = params; + return request(`/api/materials-inventory/export`, { + method: 'GET', + params: { ...queryParams }, + ...(options || { responseType: 'blob', isReturnResult: false }), + }); +} /** 新增原材料盘点 POST /api/materials-inventory */ export async function materialsInventoryCreate( body: API.MaterialsInventoryDto, diff --git a/src/api/backend/api/typings.d.ts b/src/api/backend/api/typings.d.ts index ae253f2..98789b5 100644 --- a/src/api/backend/api/typings.d.ts +++ b/src/api/backend/api/typings.d.ts @@ -1361,6 +1361,11 @@ declare namespace API { id: number; }; + type MaterialsInventoryExportParams = { + time: string; + projectId: number; + }; + type MaterialsInventoryInfoParams = { id: number; }; @@ -1667,7 +1672,7 @@ declare namespace API { time?: string; inventoryNumber?: string; isCreateOut?: boolean; - project?:string; + project?: string; field?: string; order?: 'ASC' | 'DESC'; _t?: number; @@ -1701,7 +1706,7 @@ declare namespace API { /** 项目 */ project: ProjectEntity; /** 项目ID */ - projectId?:number; + projectId?: number; /** 备注 */ remark: string; /** 附件 */ @@ -1755,7 +1760,7 @@ declare namespace API { /** 领料单号 */ issuanceNumber?: number; /** 项目 */ - projectId?:number; + projectId?: number; /** 备注 */ remark?: string; /** 附件 */ diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 2e543dc..a7479b1 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -6,3 +6,4 @@ export { useI18n } from './useI18n'; export { useOnline } from './useOnline'; export { useTime } from './useTime'; export { useSortable } from './useSortable'; +export { useGlobalLoading } from './useGlobalLoading'; diff --git a/src/hooks/useGlobalLoading/index.tsx b/src/hooks/useGlobalLoading/index.tsx new file mode 100644 index 0000000..62af57c --- /dev/null +++ b/src/hooks/useGlobalLoading/index.tsx @@ -0,0 +1,28 @@ +import { createVNode, render, h, onMounted, onUnmounted } from 'vue'; +import { Spin as ASpin } from 'ant-design-vue'; + +export function useGlobalLoading() { + let loadingElement: HTMLElement | null = null; + + const showLoading = () => { + if (!loadingElement) { + loadingElement = document.createElement('div'); + loadingElement.id = 'global-loading'; + document.body.appendChild(loadingElement); + + const spinVNode = h(ASpin, { spinning: true }); + render(spinVNode, loadingElement); + } + }; + + const hideLoading = () => { + if (loadingElement) { + document.body.removeChild(loadingElement); + loadingElement = null; + } + }; + + onUnmounted(hideLoading); + + return { showLoading, hideLoading }; +} diff --git a/src/hooks/useModal/useFormModal.tsx b/src/hooks/useModal/useFormModal.tsx index 55826f5..1e9e1b8 100644 --- a/src/hooks/useModal/useFormModal.tsx +++ b/src/hooks/useModal/useFormModal.tsx @@ -34,7 +34,6 @@ export function useFormModal() { const onSubmit = async (values) => { await modalProps?.onFinish?.(values); - // formRef.value?.resetFields(); ModalRender.hide(); }; diff --git a/src/styles/index.less b/src/styles/index.less index 9b4354a..08d2f41 100644 --- a/src/styles/index.less +++ b/src/styles/index.less @@ -5,3 +5,17 @@ @import './transition.less'; @import './common.less'; @import './antdv.override.less'; + +#global-loading { + display: flex; + position: fixed; + z-index: 9999; + top: 0; + right: 0; + left: 0; + align-items: center; + justify-content: center; + width: 100%; + height: 100vh; + background: #0004; +} diff --git a/src/utils/request.ts b/src/utils/request.ts index 478476e..64f6044 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -52,9 +52,12 @@ service.interceptors.request.use( service.interceptors.response.use( (response: AxiosResponse) => { const res = response.data; - - // if the custom code is not 200, it is judged as an error. + // 若是文件下载直接跳过解析 + if (response.request.responseType === 'blob') { + return response; + } if (res.code !== ResultEnum.SUCCESS) { + // if the custom code is not 200, it is judged as an error. $message.error(res.message || UNKNOWN_ERROR); // Illegal token if ([1101, 1105].includes(res.code)) { diff --git a/src/views/materials-inventory/in-out/exportSchema.ts b/src/views/materials-inventory/in-out/exportSchema.ts new file mode 100644 index 0000000..1290e7e --- /dev/null +++ b/src/views/materials-inventory/in-out/exportSchema.ts @@ -0,0 +1,72 @@ +import type { FormSchema } from '@/components/core/schema-form/'; + +import Api from '@/api'; +import { debounce } from 'lodash-es'; +export const exportSchemas: FormSchema[] = [ + { + field: 'projectId', + component: 'Select', + label: '项目', + colProps: { + span: 12, + }, + required: true, + helpMessage: '如未找到对应项目,请先去项目管理添加项目。', + componentProps: ({ formInstance, schema, formModel }) => ({ + showSearch: true, + filterOption: false, + fieldNames: { + label: 'label', + value: 'value', + }, + getPopupContainer: () => document.body, + defaultActiveFirstOption: true, + onClear: async () => { + const newSchema = { + field: schema.field, + componentProps: { + options: [] as LabelValueOptions, + }, + }; + const options = await getProjectOptions().finally(() => (schema.loading = false)); + newSchema.componentProps.options = options; + formInstance?.updateSchema([newSchema]); + }, + request: () => { + return getProjectOptions(); + }, + onSearch: debounce(async (keyword) => { + schema.loading = true; + const newSchema = { + field: schema.field, + componentProps: { + options: [] as LabelValueOptions, + }, + }; + formInstance?.updateSchema([newSchema]); + const options = await getProjectOptions(keyword).finally(() => (schema.loading = false)); + newSchema.componentProps.options = options; + formInstance?.updateSchema([newSchema]); + }, 500), + }), + }, + { + label: '时间', + field: 'time', + component: 'MonthPicker', + required: true, + colProps: { + span: 12, + }, + }, +]; + +const getProjectOptions = async (keyword?: string): Promise => { + const { items: result } = await Api.project.projectList({ pageSize: 100, name: keyword }); + return ( + result?.map((item) => ({ + label: `${item.name}`, + value: item.id, + })) || [] + ); +}; diff --git a/src/views/materials-inventory/in-out/index.vue b/src/views/materials-inventory/in-out/index.vue index e7fa2f8..6b00906 100644 --- a/src/views/materials-inventory/in-out/index.vue +++ b/src/views/materials-inventory/in-out/index.vue @@ -3,7 +3,7 @@ 导出原材料盘点表 @@ -41,10 +41,15 @@ import Api from '@/api/'; import { onMounted, ref, type FunctionalComponent, unref } from 'vue'; import { useFormModal, useModal } from '@/hooks/useModal'; - import { Button, message } from 'ant-design-vue'; + import { Button } from 'ant-design-vue'; import { formSchemas } from './formSchemas'; + import { exportSchemas } from './exportSchema'; import AttachmentManage from '@/components/business/attachment-manage/index.vue'; import AttachmentUpload from '@/components/business/attachment-upload/index.vue'; + import fileDownload from 'js-file-download'; + import { useGlobalLoading } from '@/hooks'; + import { waitTime } from '@/utils/common'; + import dayjs from 'dayjs'; defineOptions({ name: 'MaterialsInOut', }); @@ -107,10 +112,30 @@ const { time } = unref( dynamicTableInstance?.queryFormRef?.formModel as TableQueryItem, ); - if (!time) { - message.warn('请先在查询区选择导出月份时间'); + + const [formRef] = await showModal({ + modalProps: { + title: `导出条件选择`, + width: '50%', + okText: '导出', + onFinish: async (values) => { + const response = await Api.materialsInventory.materialsInventoryExport(values); + const { time } = values; + fileDownload(response, `${dayjs(time).format('YYYY年MM月')}.xlsx`); + }, + }, + formProps: { + labelWidth: 100, + schemas: exportSchemas, + }, + }); + + // auto fill export search fields + if (time) { + formRef?.setFieldsValue({ + time, + }); } - console.log('exportMI'); }; const openAttachmentUploadModal = async (record: TableListItem) => { isUploadPopupVisiable.value = true; diff --git a/vite.config.ts b/vite.config.ts index d22018d..2e26abc 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -147,6 +147,9 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { // minifyInternalExports: false, //TODO fix circular imports manualChunks(id) { + if (id.includes('/node_modules/')) { + return 'vendor'; + } if (id.includes('/src/locales/helper.ts')) { return 'vendor'; } else if (id.includes('ant-design-vue')) {