From 2cdcb7153c48f7c70ef12951a97b7b65abe94861 Mon Sep 17 00:00:00 2001 From: bqy_fe <1743369777@qq.com> Date: Wed, 5 Jan 2022 13:01:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A8=E6=A0=BC=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 +- src/components/basic/excel/index.ts | 8 + .../basic/excel/src/Export2Excel.ts | 61 +++++ .../basic/excel/src/ExportExcelModal.tsx | 80 ++++++ .../basic/excel/src/ImportExcel.vue | 157 ++++++++++++ src/components/basic/excel/src/typing.ts | 27 ++ .../components/query-form/index.vue | 16 +- .../dynamic-table/components/table-action.vue | 2 - .../table-settings/column-setting.vue | 18 +- .../table-settings/refresh-setting.vue | 2 +- .../table-settings/size-setting.vue | 8 +- .../components/tool-bar/index.vue | 3 +- .../core/dynamic-table/dynamic-table.vue | 37 ++- .../core/dynamic-table/hooks/index.ts | 4 +- .../dynamic-table/hooks/useExpandLoading.ts | 29 --- .../hooks/useExportData2Excel.ts | 41 ++++ .../core/dynamic-table/hooks/usePagination.ts | 4 +- .../dynamic-table/hooks/useTableContext.ts | 12 +- src/components/core/dynamic-table/props.ts | 24 ++ src/components/core/dynamic-table/typing.ts | 16 +- src/components/core/schema-form/helper.ts | 9 +- src/hooks/functions/useContextMenu.ts | 1 + src/hooks/useI18n.ts | 37 +++ src/hooks/useModal/useFormModal.tsx | 12 +- src/layout/tabs/tabs-view.vue | 20 +- src/locales/index.ts | 2 +- src/locales/lang/en-US/component.ts | 28 +++ src/locales/lang/zh-CN/component.ts | 32 +++ src/utils/Export2Excel.js | 231 ++++++++++++++++++ src/utils/dateUtil.ts | 23 ++ src/utils/index.ts | 13 + src/views/demos/tables/lol-table/index.vue | 60 ++++- src/views/demos/tables/wzry-table/columns.tsx | 3 + src/views/demos/tables/wzry-table/index.vue | 96 ++++++-- src/views/system/permission/menu/index.vue | 2 +- src/views/system/permission/role/index.vue | 4 +- src/views/system/permission/user/index.vue | 4 +- yarn.lock | 21 +- 38 files changed, 1014 insertions(+), 138 deletions(-) create mode 100644 src/components/basic/excel/index.ts create mode 100644 src/components/basic/excel/src/Export2Excel.ts create mode 100644 src/components/basic/excel/src/ExportExcelModal.tsx create mode 100644 src/components/basic/excel/src/ImportExcel.vue create mode 100644 src/components/basic/excel/src/typing.ts delete mode 100644 src/components/core/dynamic-table/hooks/useExpandLoading.ts create mode 100644 src/components/core/dynamic-table/hooks/useExportData2Excel.ts create mode 100644 src/locales/lang/en-US/component.ts create mode 100644 src/locales/lang/zh-CN/component.ts create mode 100644 src/utils/Export2Excel.js create mode 100644 src/utils/dateUtil.ts diff --git a/package.json b/package.json index f60ca7ecd..f896159fb 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,11 @@ }, "dependencies": { "@vueuse/core": "^7.5.1", - "ant-design-vue": "3.0.0-beta.4", + "ant-design-vue": "3.0.0-beta.5", "axios": "^0.24.0", "core-js": "^3.20.2", "dayjs": "^1.10.7", + "file-saver": "^2.0.5", "lodash": "^4.17.21", "mitt": "^3.0.0", "mockjs": "^1.1.0", @@ -44,7 +45,7 @@ "@commitlint/cli": "^16.0.1", "@commitlint/config-conventional": "^16.0.0", "@types/lodash": "^4.14.178", - "@types/node": "^17.0.7", + "@types/node": "^17.0.8", "@types/webpack-env": "^1.16.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", diff --git a/src/components/basic/excel/index.ts b/src/components/basic/excel/index.ts new file mode 100644 index 000000000..58bcae06f --- /dev/null +++ b/src/components/basic/excel/index.ts @@ -0,0 +1,8 @@ +import { withInstall } from '@/utils'; +import impExcel from './src/ImportExcel.vue'; +export { useExportExcelModal } from './src/ExportExcelModal'; + +export const ImpExcel = withInstall(impExcel); +// export const ExpExcelModal = withInstall(expExcelModal); +export * from './src/typing'; +export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel'; diff --git a/src/components/basic/excel/src/Export2Excel.ts b/src/components/basic/excel/src/Export2Excel.ts new file mode 100644 index 000000000..24f7f1cf0 --- /dev/null +++ b/src/components/basic/excel/src/Export2Excel.ts @@ -0,0 +1,61 @@ +import xlsx from 'xlsx'; +import type { WorkBook } from 'xlsx'; +import type { JsonToSheet, AoAToSheet } from './typing'; + +const { utils, writeFile } = xlsx; + +const DEF_FILE_NAME = 'excel-list.xlsx'; + +export function jsonToSheetXlsx({ + data, + header, + filename = DEF_FILE_NAME, + json2sheetOpts = {}, + write2excelOpts = { bookType: 'xlsx' }, +}: JsonToSheet) { + let arrData = [...data]; + if (header) { + arrData.unshift(header); + const filterKeys = Object.keys(header); + arrData = arrData.map((item) => filterKeys.reduce((p, k) => ((p[k] = item[k]), p), {})); + json2sheetOpts.skipHeader = true; + } + + const worksheet = utils.json_to_sheet(arrData, json2sheetOpts); + + /* add worksheet to workbook */ + const workbook: WorkBook = { + SheetNames: [filename], + Sheets: { + [filename]: worksheet, + }, + }; + /* output format determined by filename */ + writeFile(workbook, filename, write2excelOpts); + /* at this point, out.xlsb will have been downloaded */ +} + +export function aoaToSheetXlsx({ + data, + header, + filename = DEF_FILE_NAME, + write2excelOpts = { bookType: 'xlsx' }, +}: AoAToSheet) { + const arrData = [...data]; + if (header) { + arrData.unshift(header); + } + + const worksheet = utils.aoa_to_sheet(arrData); + + /* add worksheet to workbook */ + const workbook: WorkBook = { + SheetNames: [filename], + Sheets: { + [filename]: worksheet, + }, + }; + /* output format determined by filename */ + writeFile(workbook, filename, write2excelOpts); + /* at this point, out.xlsb will have been downloaded */ +} diff --git a/src/components/basic/excel/src/ExportExcelModal.tsx b/src/components/basic/excel/src/ExportExcelModal.tsx new file mode 100644 index 000000000..4b34241a7 --- /dev/null +++ b/src/components/basic/excel/src/ExportExcelModal.tsx @@ -0,0 +1,80 @@ +import type { ExportModalResult } from './typing'; +import type { FormItemSchema } from '@/components/core/schema-form/types/form'; + +import { useFormModal } from '@/hooks/useModal/useFormModal'; + +import { useI18n } from 'vue-i18n'; + +export type OpenModalOptions = { + onOk: (val: ExportModalResult) => any; +}; + +const getSchemas = (t): FormItemSchema[] => [ + { + field: 'filename', + component: 'Input', + label: t('component.excel.fileName'), + rules: [{ required: true }], + }, + { + field: 'bookType', + component: 'Select', + label: t('component.excel.fileType'), + defaultValue: 'xlsx', + rules: [{ required: true }], + componentProps: { + options: [ + { + label: 'xlsx', + value: 'xlsx', + key: 'xlsx', + }, + { + label: 'html', + value: 'html', + key: 'html', + }, + { + label: 'csv', + value: 'csv', + key: 'csv', + }, + { + label: 'txt', + value: 'txt', + key: 'txt', + }, + ], + }, + }, +]; + +export const useExportExcelModal = () => { + const { t } = useI18n(); + const [showModal] = useFormModal(); + + const openModal = ({ onOk }: OpenModalOptions) => { + showModal({ + modalProps: { + title: t('component.excel.exportModalTitle'), + onFinish: async (values) => { + const { filename, bookType } = values; + + onOk({ + filename: `${filename.split('.').shift()}.${bookType}`, + bookType, + }); + }, + }, + formSchema: { + labelWidth: 100, + layout: 'vertical', + schemas: getSchemas(t), + }, + }); + }; + + return { + openModal, + }; +}; diff --git a/src/components/basic/excel/src/ImportExcel.vue b/src/components/basic/excel/src/ImportExcel.vue new file mode 100644 index 000000000..7a741d9c8 --- /dev/null +++ b/src/components/basic/excel/src/ImportExcel.vue @@ -0,0 +1,157 @@ + + diff --git a/src/components/basic/excel/src/typing.ts b/src/components/basic/excel/src/typing.ts new file mode 100644 index 000000000..e55e549ea --- /dev/null +++ b/src/components/basic/excel/src/typing.ts @@ -0,0 +1,27 @@ +import type { JSON2SheetOpts, WritingOptions, BookType } from 'xlsx'; + +export interface ExcelData { + header: string[]; + results: T[]; + meta: { sheetName: string }; +} + +export interface JsonToSheet { + data: T[]; + header?: T; + filename?: string; + json2sheetOpts?: JSON2SheetOpts; + write2excelOpts?: WritingOptions; +} + +export interface AoAToSheet { + data: T[][]; + header?: T[]; + filename?: string; + write2excelOpts?: WritingOptions; +} + +export interface ExportModalResult { + filename: string; + bookType: BookType; +} diff --git a/src/components/core/dynamic-table/components/query-form/index.vue b/src/components/core/dynamic-table/components/query-form/index.vue index 8b7f78ce2..ed9162370 100644 --- a/src/components/core/dynamic-table/components/query-form/index.vue +++ b/src/components/core/dynamic-table/components/query-form/index.vue @@ -4,12 +4,14 @@ diff --git a/src/views/demos/tables/wzry-table/columns.tsx b/src/views/demos/tables/wzry-table/columns.tsx index dcee3685e..57e2a12f0 100644 --- a/src/views/demos/tables/wzry-table/columns.tsx +++ b/src/views/demos/tables/wzry-table/columns.tsx @@ -32,6 +32,9 @@ export const columns: TableColumn[] = [ title: '皮肤', align: 'center', dataIndex: 'skin_name', + formItemProps: { + component: 'Select', + }, bodyCell: ({ record }) => ( <> {record.skin_name?.split('|')?.map((name) => ( diff --git a/src/views/demos/tables/wzry-table/index.vue b/src/views/demos/tables/wzry-table/index.vue index 8a8a30ff4..7206bba90 100644 --- a/src/views/demos/tables/wzry-table/index.vue +++ b/src/views/demos/tables/wzry-table/index.vue @@ -1,52 +1,94 @@ diff --git a/src/views/system/permission/menu/index.vue b/src/views/system/permission/menu/index.vue index b68318de6..93849f465 100644 --- a/src/views/system/permission/menu/index.vue +++ b/src/views/system/permission/menu/index.vue @@ -54,7 +54,7 @@ }; const openMenuModal = async (record: Partial) => { - const [formRef] = await showModal({ + const [formRef] = await showModal({ modalProps: { title: `${record.id ? '编辑' : '新增'}菜单`, width: 700, diff --git a/src/views/system/permission/role/index.vue b/src/views/system/permission/role/index.vue index 319812752..8e4305e43 100644 --- a/src/views/system/permission/role/index.vue +++ b/src/views/system/permission/role/index.vue @@ -63,12 +63,12 @@ * @description 打开新增/编辑弹窗 */ const openMenuModal = async (record: Partial) => { - const [formRef] = await showModal({ + const [formRef] = await showModal({ modalProps: { title: `${record.id ? '编辑' : '新增'}角色`, width: '50%', onFinish: async (values) => { - values.roleId = record.id; + record.id && (values.roleId = record.id); const menusRef = formRef.value?.compRefs?.menus; const deptsRef = formRef.value?.compRefs?.depts; const params = { diff --git a/src/views/system/permission/user/index.vue b/src/views/system/permission/user/index.vue index 169fc7b20..ef43474ca 100644 --- a/src/views/system/permission/user/index.vue +++ b/src/views/system/permission/user/index.vue @@ -154,7 +154,7 @@ const openDeptModal = async (record: Partial = {}) => { console.log('record', record); - const [formRef] = await showModal({ + const [formRef] = await showModal({ modalProps: { title: `${record.id ? '编辑' : '新增'}部门`, width: 700, @@ -227,7 +227,7 @@ * @description 打开用户弹窗 */ const openUserModal = async (record: Partial = {}) => { - const [formRef] = await showModal({ + const [formRef] = await showModal({ modalProps: { title: `${record.id ? '编辑' : '新增'}用户`, width: 700, diff --git a/yarn.lock b/yarn.lock index 898224e9a..fc31193a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1421,10 +1421,10 @@ resolved "https://registry.npmmirror.com/@types/node/download/@types/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0" integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw== -"@types/node@^17.0.7": - version "17.0.7" - resolved "https://registry.npmmirror.com/@types/node/download/@types/node-17.0.7.tgz#4a53d8332bb65a45470a2f9e2611f1ced637a5cb" - integrity sha512-1QUk+WAUD4t8iR+Oj+UgI8oJa6yyxaB8a8pHaC8uqM6RrS1qbL7bf3Pwl5rHv0psm2CuDErgho6v5N+G+5fwtQ== +"@types/node@^17.0.8": + version "17.0.8" + resolved "https://registry.npmmirror.com/@types/node/download/@types/node-17.0.8.tgz#50d680c8a8a78fe30abe6906453b21ad8ab0ad7b" + integrity sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2412,10 +2412,10 @@ ansi-styles@^6.0.0: resolved "https://registry.nlark.com/ansi-styles/download/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" integrity sha1-hzE8ECuBGKvVc3GvqzRhi/c1DtM= -ant-design-vue@3.0.0-beta.4: - version "3.0.0-beta.4" - resolved "https://registry.npmmirror.com/ant-design-vue/download/ant-design-vue-3.0.0-beta.4.tgz#e6f7861682875d77220d02ffe561b2b2022cd15b" - integrity sha512-WUkA8LyXw35vi1OOHTzvtcet0lEhNGCQNF85zEOHpPlA1Xf8KWCricFeiA38fgg2AHWvE1mrVw/wqB1kXBJM1Q== +ant-design-vue@3.0.0-beta.5: + version "3.0.0-beta.5" + resolved "https://registry.npmmirror.com/ant-design-vue/download/ant-design-vue-3.0.0-beta.5.tgz#9994ecedc7ad29600d85e73a754199d948869bb1" + integrity sha512-yX+JGkPlzXav56hHCg0aa1LAB9GQEEQ8Xfy0o7tc49zFH2v0Q8ekiWVXsvsiracLZgl7GcLm5HnEPp9tAy0BZQ== dependencies: "@ant-design/colors" "^6.0.0" "@ant-design/icons-vue" "^6.0.0" @@ -4572,6 +4572,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.npmmirror.com/file-saver/download/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + fill-range@^4.0.0: version "4.0.0" resolved "https://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"