From 3d495c047cab007d04522fa26dd153b01bb5f73a Mon Sep 17 00:00:00 2001 From: ray_wuhao <443547225@qq.com> Date: Sun, 15 Jan 2023 21:29:44 +0800 Subject: [PATCH] =?UTF-8?q?v3.0.6=20=E6=8A=BD=E7=A6=BB=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=20xlsx=20=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=B0=8F=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintignore | 4 +- .eslintrc.cjs | 3 + README.md | 32 +++++++-- src/axios/instance.ts | 10 ++- src/components/RayTable/src/hook.ts | 12 ---- src/components/RayTable/src/index.tsx | 40 +++-------- src/components/RayTable/src/type.ts | 2 - src/utils/cache.ts | 2 +- src/utils/crypto.ts | 14 ++-- src/utils/element.ts | 10 +-- src/utils/hook.ts | 20 ++++-- src/utils/xlsx.ts | 100 ++++++++++++++++++++++++++ vite.config.ts | 2 + 13 files changed, 177 insertions(+), 74 deletions(-) delete mode 100644 src/components/RayTable/src/hook.ts create mode 100644 src/utils/xlsx.ts diff --git a/.eslintignore b/.eslintignore index b3355b99..46f49ce7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,4 +9,6 @@ yarn.* vite-env.* .prettierrc.* .eslintrc -visualizer.* \ No newline at end of file +visualizer.* +visualizer.html +.env.* diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 5cda8d70..f634fc45 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -105,6 +105,9 @@ module.exports = { 'no-var': 'error', // 禁用 `var` 'no-with': 2, // 禁用 `with` 'no-undef': 0, + 'use-isnan': 2, // 强制使用 isNaN 判断 NaN + 'no-multi-assign': 2, // 禁止连续声明变量 + 'prefer-arrow-callback': 2, // 强制使用箭头函数作为回调 'vue/multi-word-component-names': [ 'off', { diff --git a/README.md b/README.md index afa84545..28380665 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # `Ray Template` +## 提示 + +> 项目默认启用严格模式 `eslint`,但是由于 `vite-plugin-eslint` 插件优先级最高,所以如果出现自动导入类型错误提示,请优先解决其他问题。 +> 建议开启 `vscode` 保存自动修复功能。 + ## 前言 > 该项目模板采用 `vue3.x` `vite3.2` `tsx` 进行开发,使用 `naive ui` 作为组件库。意在提供一个简洁、快速上手的模板。 @@ -26,13 +31,13 @@ ## 拉取依赖 -``` +```sh # yarn yarn ``` -``` +```sh # npm npm install @@ -40,13 +45,13 @@ npm install ## 启动项目 -``` +```sh # yarn yarn dev ``` -``` +```sh # npm npm run dev @@ -54,18 +59,32 @@ npm run dev ## 项目打包 -``` +```sh # yarn yarn build ``` -``` +```sh # npm npm run build ``` +## 预览项目 + +```sh +# yarn + +yarn preview +``` + +```sh +# npm + +npm run preview +``` + ## 项目依赖 - [pinia](https://pinia.vuejs.org/) `全局状态管理器` @@ -79,6 +98,7 @@ npm run build - [vite-plugin-svg-icons](https://github.com/vbenjs/vite-plugin-svg-icons/blob/main/README.zh_CN.md) `svg雪碧图` - [echarts5](https://echarts.apache.org/examples/zh/index.html#chart-type-line) `可视化` - [lodash-es](https://www.lodashjs.com/) `拓展方法` +- 还有一些后续补充的,懒得写了。。。自己看项目依赖页面 ## 基础组件 diff --git a/src/axios/instance.ts b/src/axios/instance.ts index e9fbd567..fdd20595 100644 --- a/src/axios/instance.ts +++ b/src/axios/instance.ts @@ -9,8 +9,7 @@ import type { RequestHeaderOptions } from './type' * @param instance axios instance * @param options axios headers options * - * @note - * 自定义 `axios` 请求头配置 + * @remark 自定义 `axios` 请求头配置 */ const appendRequestHeaders = ( instance: AxiosRequestConfig, @@ -41,7 +40,12 @@ server.interceptors.request.use( // TODO: 测试环境 } - appendRequestHeaders(request, [{ key: 'X-TOKEN', value: 'token' }]) // 自定义请求头 + appendRequestHeaders(request, [ + { + key: 'X-TOKEN', + value: 'token', + }, + ]) // 自定义请求头 return request }, diff --git a/src/components/RayTable/src/hook.ts b/src/components/RayTable/src/hook.ts deleted file mode 100644 index 82ea56fe..00000000 --- a/src/components/RayTable/src/hook.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ExportExcelHeader } from './type' -import type { DataTableColumns } from 'naive-ui' - -export const setupExportHeader = (columns: ExportExcelHeader[]) => { - const header = columns.reduce((pre, curr) => { - pre[curr.key] = curr.title - - return pre - }, {} as ExportExcelHeader) - - return header -} diff --git a/src/components/RayTable/src/index.tsx b/src/components/RayTable/src/index.tsx index 24c76d26..db79ff81 100644 --- a/src/components/RayTable/src/index.tsx +++ b/src/components/RayTable/src/index.tsx @@ -14,16 +14,15 @@ import { NDataTable, NCard, NDropdown, NDivider } from 'naive-ui' import TableSetting from './components/TableSetting/index' import TableAction from './components/TableAction/index' -import dayjs from 'dayjs' -import { utils, writeFileXLSX } from 'xlsx' -import { setupExportHeader } from './hook' import props from './props' import print from 'print-js' import { uuid } from '@use-utils/hook' +import { exportFileToXLSX } from '@use-utils/xlsx' -import type { ActionOptions, ExportExcelHeader } from './type' +import type { ActionOptions } from './type' import type { WritableComputedRef } from 'vue' import type { DropdownOption } from 'naive-ui' +import type { ExportExcelHeader } from '@use-utils/xlsx' const RayTable = defineComponent({ name: 'RayTable', @@ -119,35 +118,16 @@ const RayTable = defineComponent({ * * 按需导入 `xlsx` 减少体积, 不依赖传统 `file save` 插件导出方式 */ - const handleExportPositive = () => { + const handleExportPositive = async () => { if (props.data.length && props.columns.length) { try { - const exportHeader = setupExportHeader( + await exportFileToXLSX( + props.data, props.columns as ExportExcelHeader[], - ) // 获取所有列(设置为 `excel` 表头) - const sheetData = utils.json_to_sheet(props.data) // 将所有数据转换为表格数据类型 - const workBook = utils.book_new() - const filename = props.exportFilename - ? props.exportFilename + '.xlsx' - : dayjs(new Date()).format('YYYY-MM-DD') + '导出表格的.xlsx' - - utils.book_append_sheet(workBook, sheetData, 'Data') - - const range = utils.decode_range(sheetData['!ref'] as string) // 获取所有单元格 - - /** - * - * 替换表头 - * - * 方法有点蠢, 凑合凑合用吧 - */ - for (let c = range.s.c; c <= range.e.c; c++) { - const header = utils.encode_col(c) + '1' - - sheetData[header].v = exportHeader[sheetData[header].v] - } - - writeFileXLSX(workBook, filename) // 输出表格 + { + filename: props.exportFilename, + }, + ) emit('exportSuccess') } catch (e) { diff --git a/src/components/RayTable/src/type.ts b/src/components/RayTable/src/type.ts index 34fc18ad..a10a028f 100644 --- a/src/components/RayTable/src/type.ts +++ b/src/components/RayTable/src/type.ts @@ -68,5 +68,3 @@ export declare type VNodeChild = VNodeChildAtom | VNodeArrayChildren export declare type TableColumnTitle = | string | ((column: DataTableBaseColumn) => VNodeChild) - -export interface ExportExcelHeader extends DataTableBaseColumn {} diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 55f63c6b..e9ad865f 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -3,7 +3,7 @@ * @param key 需要设置的key * @param value 需要缓存的值 */ -export const setCache = ( +export const setCache = ( key: string, value: T, type: CacheType = 'sessionStorage', diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index ea6b7666..78fbd1c1 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -9,7 +9,7 @@ import BASE64 from 'crypto-js/enc-base64' * @param message 待加密信息 * @param key 加密key * - * HmacSHA256 加密 + * @remark HmacSHA256 加密 */ export const useHmacSHA256 = ( message: WordArray | string, @@ -26,7 +26,7 @@ export const useHmacSHA256 = ( * * @param message 待加密信息 * - * SHA256 加密 + * @remark SHA256 加密 */ export const useSHA256 = (message: WordArray | string) => { return new Promise((resolve) => { @@ -42,7 +42,7 @@ export const useSHA256 = (message: WordArray | string) => { * @param key 加密key * @param cfg 加密配置信息 * - * AES 加密 + * @remark AES 加密 */ export const useAESEncrypt = ( message: WordArray | string, @@ -62,7 +62,7 @@ export const useAESEncrypt = ( * @param key 解密key * @param cfg 解密配置信息 * - * AES 解密 + * @remark AES 解密 */ export const useAESDecrypt = ( ciphertext: CipherParams | string, @@ -81,7 +81,7 @@ export const useAESDecrypt = ( * @param message 待加密信息 * @param cfg md5 加密配置 * - * md5 加密 + * @remark md5 加密 */ export const useMD5 = (message: WordArray | string, cfg?: object) => { return new Promise((resolve) => { @@ -95,7 +95,7 @@ export const useMD5 = (message: WordArray | string, cfg?: object) => { * * @param wordArray 待转为 base64 信息 * - * base64 加密 + * @remark base64 加密 */ export const useBase64Stringify = (wordArray: WordArray) => { return new Promise((resolve) => { @@ -109,7 +109,7 @@ export const useBase64Stringify = (wordArray: WordArray) => { * * @param str 待转为 base64 信息 * - * base64 解密 + * @remark base64 解密 */ export const useBase64Parse = (str: string) => { return new Promise((resolve) => { diff --git a/src/utils/element.ts b/src/utils/element.ts index 1753678a..18548faa 100644 --- a/src/utils/element.ts +++ b/src/utils/element.ts @@ -30,7 +30,7 @@ export const getElementChildNodes = ( * @param event 绑定事件类型 * @param handler 事件触发方法 * - * @handle 给元素绑定某个事件柄方法 + * @remark 给元素绑定某个事件柄方法 */ export const on = ( element: HTMLElement | Document | Window, @@ -49,7 +49,7 @@ export const on = ( * @param event 卸载事件类型 * @param handler 所需卸载方法 * - * @handle 卸载元素上某个事件柄方法 + * @remark 卸载元素上某个事件柄方法 */ export const off = ( element: HTMLElement | Document | Window, @@ -67,7 +67,7 @@ export const off = ( * @param element Target element dom * @param className 所需添加className,可: 'xxx xxx' | 'xxx'格式添加 * - * @handle 添加元素className(可: 'xxx xxx' | 'xxx'格式添加) + * @remark 添加元素className(可: 'xxx xxx' | 'xxx'格式添加) */ export const addClass = (element: HTMLElement, className: string) => { if (element) { @@ -86,7 +86,7 @@ export const addClass = (element: HTMLElement, className: string) => { * @param element Target element dom * @param className 所需删除className,可: 'xxx xxx' | 'xxx'格式删除 * - * @handle 删除元素className(可: 'xxx xxx' | 'xxx'格式删除) + * @remark 删除元素className(可: 'xxx xxx' | 'xxx'格式删除) */ export const removeClass = (element: HTMLElement, className: string) => { if (element) { @@ -107,7 +107,7 @@ export const removeClass = (element: HTMLElement, className: string) => { * * @returns 返回boolean * - * @handle 元素是否含有某个className(可: 'xxx xxx' | 'xxx'格式查询) + * @remark 元素是否含有某个className(可: 'xxx xxx' | 'xxx'格式查询) */ export const hasClass = (element: HTMLElement, className: string) => { const elementClassName = element.className diff --git a/src/utils/hook.ts b/src/utils/hook.ts index 413a86f1..40409a9a 100644 --- a/src/utils/hook.ts +++ b/src/utils/hook.ts @@ -17,7 +17,7 @@ export const useDetermineEnv = () => { export const useImagebufferToBase64 = ( data: ArrayBufferLike | ArrayLike, ) => { - const _base64 = + const base64 = 'data:image/png;base64,' + window.btoa( new Uint8Array(data).reduce( @@ -26,7 +26,7 @@ export const useImagebufferToBase64 = ( ), ) - return _base64 + return base64 } /** @@ -34,10 +34,13 @@ export const useImagebufferToBase64 = ( * @param value 目标值 * @param type 类型 */ -export const validteValueType = (value: T, type: ValidteValueType) => { - const _v = Object.prototype.toString.call(value) +export const validteValueType = ( + value: T, + type: ValidteValueType, +) => { + const valid = Object.prototype.toString.call(value) - return _v.includes(type) + return valid.includes(type) } /** @@ -61,9 +64,12 @@ export const uuid = (length = 16, radix?: number) => { } else { let r - arr[8] = arr[13] = arr[18] = arr[23] = '-' - + arr[23] = '-' + arr[18] = arr[23] + arr[13] = arr[18] + arr[8] = arr[13] arr[14] = '4' + for (i = 0; i < 36; i++) { if (!arr[i]) { r = 0 | (Math.random() * 16) diff --git a/src/utils/xlsx.ts b/src/utils/xlsx.ts new file mode 100644 index 00000000..be49dd9c --- /dev/null +++ b/src/utils/xlsx.ts @@ -0,0 +1,100 @@ +/** + * + * @author Ray + * + * @date 2023-01-15 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import { utils, writeFileXLSX } from 'xlsx' +import dayjs from 'dayjs' + +import type { DataTableBaseColumn } from 'naive-ui' +import type { Range, WorkSheet } from 'xlsx' + +export interface ExportExcelHeader extends DataTableBaseColumn {} + +export type RowData = Record + +export interface ExportXLSXConfig { + filename?: string +} + +/** + * + * @param columns table columns + * @returns 处理后的表头 + */ +const setupSheetHeader = (columns: ExportExcelHeader[]) => { + const header = columns.reduce((pre, curr) => { + pre[curr.key] = curr.title + + return pre + }, {} as ExportExcelHeader) + + return header +} + +/** + * + * @param range table range + * @param sheetData sheet data + * @param sheetHeader table header + * + * @remark 替换表头 + * @remark 由于暂未想到更好的方法, 如果有好的想法可以戳我 + */ +const transformSheetHeader = ( + range: Range, + sheetData: WorkSheet, + sheetHeader: ExportExcelHeader, +) => { + for (let c = range.s.c; c <= range.e.c; c++) { + const header = utils.encode_col(c) + '1' + + sheetData[header].v = sheetHeader[sheetData[header].v] + } +} + +/** + * + * @param dataSource 表格数据源 + * @param columns 表头 + * @param config xlsx 输出配置 + * + * @remark 导出数据为 xlsx + * @remark 如果不设置表头, 则会使用 dataSource 第一行数据为默认表头 + */ +export const exportFileToXLSX = async ( + dataSource: RowData[], + columns?: ExportExcelHeader[], + config: ExportXLSXConfig = {}, +) => { + await new Promise((resolve, reject) => { + if (dataSource?.length) { + const sheetHeader = setupSheetHeader(columns ?? []) // 获取所有列(设置为 `excel` 表头) + const sheetData = utils.json_to_sheet(dataSource) // 将所有数据转换为表格数据类型 + const workBook = utils.book_new() + const filename = config.filename + ? config.filename + '.xlsx' + : dayjs().format('YYYY-MM-DD') + '导出表格.xlsx' + + utils.book_append_sheet(workBook, sheetData, 'Data') + + const range = utils.decode_range(sheetData['!ref'] as string) // 获取所有单元格 + + if (columns?.length) { + transformSheetHeader(range, sheetData, sheetHeader) + } + + writeFileXLSX(workBook, filename) // 输出表格 + + resolve() + } else { + reject() + } + }) +} diff --git a/vite.config.ts b/vite.config.ts index ee1826b1..0c2f9d2a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -76,10 +76,12 @@ export default defineConfig(async ({ mode }) => { }), useSVGIcon(), viteEslintPlugin({ + lintOnStart: true, // 构建时自动检查 failOnWarning: true, // 如果含有警告则构建失败 failOnError: true, // 如果有错误则构建失败 cache: true, // 缓存, 减少构建时间 exclude: ['**/node_modules/**', 'vite-env.d.ts'], + include: ['src/**/*.ts', 'src/**/*.vue', 'src/**/*.tsx'], }), vitePluginImp({ libList: [