diff --git a/.gitignore b/.gitignore index 8391fad5..25ad456d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ dist/ *.local # Editor directories and files +.vscode .vscode/* !.vscode/extensions.json .idea diff --git a/README.md b/README.md index 19929605..6bf4f9ef 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,67 @@ -# Ray template +# `Ray Template` ## 前言 -> 模板按照个人习惯进行搭建, 可以根据个人喜好进行更改. 预设了一些组件库、国际化库的东西. 建议使用 `naive-ui` 作为组件库. +> 该项目模板采用 `vue3.x` `vite3.2` `tsx` 进行开发,使用 `naive ui` 作为组件库。意在提供一个简洁、快速上手的模板。 -## 在线预览 +## 版本说明 -[在线预览](https://xiaodaigua-ray.github.io/#/) +> 做了一些大的改动升级,让模板更加好用了一点,默认主题色也做了变更更好看了一点。啰嗦两句,好像也没啥其他的了... -## 项目说明 +## 功能 -> 项目采用 `Vue 3` `TypeScript` `TSX` `Vite` 进行开发, 已经集成了一些常用的开发库, 进行了一些 `Vite` 相关配置, 例如全局自动引入、`GZ` 打包、按需引入打包、[reactivityTransform](https://vuejs.org/guide/extras/reactivity-transform.html)等, 解放你的双手. 国际化插件, 按照项目需求自己取舍. 引入了比较火的 `hook` 库 [@vueuse](https://vueuse.org/), 极大提高你的搬砖效率. `小提醒: 为了避免使用 @vueuse 时出现奇奇怪怪的错误(例如: useDraggable 在使用的时候, TSX 形式开发会失效), 建议采用 形式进行开发`. 可以根据自己项目实际需求进行配置 `px` 与 'rem' 转换比例(使用 `postcss-pxtorem` 与 `autoprefixer` 实现). +- 主题切换 +- 错误页 +- 封装了一些小组件 +- 还有一些不值一提的小东西... -> 项目已经预设了一些打包优化, 例如: 压缩, `base64` 转换, 按需打包. 但是值得注意的是, 禁止全局导入使用 `lodash-es` 这样会破坏按需打包. +## 预览地址 -> 项目暂时没有揉合乱七八糟的库, 仅仅是为了作为一个顺手的工具, 意在提供一个干净, 简单的脚手架. +[**`点击预览`**](https://xiaodaigua-ray.github.io/#/) + +## 拉取依赖 + +``` +# yarn + +yarn +``` + +``` +# npm + +npm install +``` ## 启动项目 -`yarn dev` / `npm run dev` +``` +# yarn + +yarn dev +``` + +``` +# npm + +npm run dev +``` ## 项目打包 -`yarn build` / `npm run build` +``` +# yarn -## 使用开源库 +yarn build +``` + +``` +# npm + +npm run build +``` + +## 项目依赖 - [pinia](https://pinia.vuejs.org/) `全局状态管理器` - [@vueuse](https://vueuse.org/) `vue3 hooks` @@ -36,25 +73,21 @@ - [vite-svg-loader](https://github.com/jpkleemans/vite-svg-loader) `svg组件化` - [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](https://www.lodashjs.com/) `拓展方法` +- [lodash-es](https://www.lodashjs.com/) `拓展方法` ## 基础组件 -`RayScrollReveal` 基于 `ScrollReveal` 进行开发, 可以实现滚动加载动画(暂时移除) - -`RayTransitionComponent` 路由过渡动画组件, 可根据自己喜好更改 `src/styles/animate.scss` 文件过渡效果 - -`RayChart` 基于 `echarts5` 封装, 可根据自己实际需求进行拓展 - -`RayIcon` `svg` 小图标组件 +- `RayIcon` `svg icon` +- `RayChart` 基于 `echarts5.x` 封装可视化组件 +- `RayTransitionComponent` 带过渡动画路由组件,效果与 `RouterView` 相同 +- `RayTable` 基于 `Naive UI DataTable` 组件封装,实现了一些小功能 ## 项目结构 ``` -- locales: 国际化多语言入口(本项目采用 `json` 格式) +- locales: 国际化多语言入口(本项目采用 json 格式) - assets: 项目静态资源入口 - - images: 项目图片资源 - component: 全局共用组件 @@ -67,32 +100,20 @@ - router: 路由表 - store: 全局状态管理入口 - - modules - - setting: demo - styles: 全局公共样式入口 - types: 全局 type - utils: 工具包 - - cache: 缓存方法 - - crypto: 常用的加密方法 - - element: dom 相关操作方法 - - hook: 常用 hook 方法 - views: 页面入口 - vite-plugin: 插件注册 ``` -## 如果你采用的 `naive-ui` 作为组件库, 可能需要它 +## 浏览器支持 -``` -# 如何在项目内使用提示组件 -window.$dialog -window.$message -window.$loadingBar -window.$notification -``` +> 仅支持现代浏览器,不支持 `IE` -### 祝大家搬砖愉快, 希望这个模板能帮你省很多时间 +## 最后,希望大家搬砖愉快 diff --git a/package.json b/package.json index 151e1bd1..962f7edd 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "amfe-flexible": "^2.2.1", "axios": "^1.2.0", "crypto-js": "^4.1.1", + "dayjs": "^1.11.7", "echarts": "^5.4.0", "lodash-es": "^4.17.21", "naive-ui": "^2.34.0", @@ -25,7 +26,8 @@ "vue": "^3.2.37", "vue-i18n": "^9.2.2", "vue-router": "^4.1.3", - "vuedraggable": "^4.1.0" + "vuedraggable": "^4.1.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@babel/core": "^7.20.2", diff --git a/src/components/RayTable/src/components/ExportExcel/index.tsx b/src/components/RayTable/src/components/ExportExcel/index.tsx new file mode 100644 index 00000000..a534ea6b --- /dev/null +++ b/src/components/RayTable/src/components/ExportExcel/index.tsx @@ -0,0 +1,76 @@ +/** + * + * @author Ray + * + * @date 2022-12-19 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import { NPopconfirm, NSpace, NButton } from 'naive-ui' +import RayIcon from '@/components/RayIcon/index' + +import type { ExportExcelProvider } from '@/components/RayTable/src/type' + +const ExportExcel = defineComponent({ + name: 'ExportExcel', + emits: ['exportPositive', 'exportNegative'], + setup(_, { emit }) { + const exportExcelProvider = inject( + 'exportExcelProvider', + {} as ExportExcelProvider, + ) + const showPopoconfirm = ref(false) + + const handleButtonClick = (type: string) => { + type === 'positive' ? emit('exportPositive') : emit('exportNegative') + + showPopoconfirm.value = false + } + + return { + ...exportExcelProvider, + handleButtonClick, + showPopoconfirm, + } + }, + render() { + return ( + + {{ + trigger: () => ( + + ), + default: () => this.exportTip, + action: () => ( + + + {this.exportNegativeText} + + + {this.exportPositiveText} + + + ), + }} + + ) + }, +}) + +export default ExportExcel diff --git a/src/components/RayTable/src/components/TableSetting/index.scss b/src/components/RayTable/src/components/TableSetting/index.scss index 222251ea..2a4cbcfb 100644 --- a/src/components/RayTable/src/components/TableSetting/index.scss +++ b/src/components/RayTable/src/components/TableSetting/index.scss @@ -7,9 +7,17 @@ $activedColor: #2080f0; transition: transform 0.3s var(--r-bezier); } +.table-setting__card { + padding: 12px 8px; + + & .n-card__content { + padding: 0 !important; + margin: 0 !important; + } +} + .ray-table__setting-option--draggable { display: grid; - grid-template-columns: repeat(1, $width); grid-row-gap: 10px; justify-items: center; align-items: center; @@ -19,8 +27,19 @@ $activedColor: #2080f0; display: flex; align-items: center; cursor: pointer; + padding: 8px 10px; + border-radius: 2px; + transition: background-color 0.3s var(--r-bezier); + + &.draggable-item--dark { + &:hover { + background-color: rgba(255, 255, 255, 0.08); + } + } &:hover { + background-color: #e8f2fd; + & .draggable-item__d--icon { opacity: 1; } @@ -49,8 +68,8 @@ $activedColor: #2080f0; } & .n-ellipsis { - max-width: 71px; - min-width: 71px; + max-width: 80px; + min-width: 80px; } } } diff --git a/src/components/RayTable/src/components/TableSetting/index.tsx b/src/components/RayTable/src/components/TableSetting/index.tsx index ecec51ce..0857c5fa 100644 --- a/src/components/RayTable/src/components/TableSetting/index.tsx +++ b/src/components/RayTable/src/components/TableSetting/index.tsx @@ -10,13 +10,14 @@ */ import './index.scss' -import { NCard, NPopover, NEllipsis, NCheckbox } from 'naive-ui' +import { NCard, NPopover, NEllipsis, NButton } from 'naive-ui' import RayIcon from '@/components/RayIcon/index' import VueDraggable from 'vuedraggable' import { setupSettingOptions } from './hook' +import { useSetting } from '@/store' import type { - RayTableProvider, + TableSettingProvider, ActionOptions, FixedType, TableSettingFixedPopoverIcon, @@ -26,11 +27,16 @@ const TableSetting = defineComponent({ name: 'TableSetting', emits: ['columnsUpdate'], setup(_, { emit }) { - const rayTableProvider = inject('rayTableProvider', {} as RayTableProvider) + const settingStore = useSetting() + const tableSettingProvider = inject( + 'tableSettingProvider', + {} as TableSettingProvider, + ) const settingOptions = ref( - setupSettingOptions(rayTableProvider.modelColumns.value), + setupSettingOptions(tableSettingProvider.modelColumns.value), ) // 表格表头 - const disableDraggable = ref(true) // 拖拽开关 + const disableDraggable = ref(true) // 拖拽开关(暂时弃用) + const { themeValue } = storeToRefs(settingStore) const handleDraggableEnd = () => { emit('columnsUpdate', settingOptions.value) @@ -57,6 +63,7 @@ const TableSetting = defineComponent({ ) } + /** * * @param type 列所需固定方向 @@ -111,6 +118,7 @@ const TableSetting = defineComponent({ disableDraggable, FixedPopoverIcon, handleResizeColumnClick, + themeValue, } }, render() { @@ -125,7 +133,7 @@ const TableSetting = defineComponent({ /> ), default: () => ( - + {{ default: () => ( ( - + ), - header: () => ( - - 拖拽 - - ), }} ), diff --git a/src/components/RayTable/src/hook.ts b/src/components/RayTable/src/hook.ts new file mode 100644 index 00000000..82ea56fe --- /dev/null +++ b/src/components/RayTable/src/hook.ts @@ -0,0 +1,12 @@ +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.scss b/src/components/RayTable/src/index.scss index b63d08e4..f91b17f6 100644 --- a/src/components/RayTable/src/index.scss +++ b/src/components/RayTable/src/index.scss @@ -1,4 +1,5 @@ -.ray-table__setting { +.ray-table__setting, +.ray-table-icon { cursor: pointer; outline: none; border: none; diff --git a/src/components/RayTable/src/index.tsx b/src/components/RayTable/src/index.tsx index 1d671d56..4f48a392 100644 --- a/src/components/RayTable/src/index.tsx +++ b/src/components/RayTable/src/index.tsx @@ -10,18 +10,22 @@ */ import './index.scss' -import { NDataTable, NCard, NDropdown } from 'naive-ui' +import { NDataTable, NCard, NDropdown, NSpace } from 'naive-ui' import props from './props' import TableSetting from './components/TableSetting/index' +import ExportExcel from './components/ExportExcel/index' +import { utils, writeFileXLSX } from 'xlsx' +import dayjs from 'dayjs' +import { setupExportHeader } from './hook' -import type { ActionOptions } from './type' +import type { ActionOptions, ExportExcelHeader } from './type' import type { WritableComputedRef } from 'vue' import type { DropdownOption } from 'naive-ui' const RayTable = defineComponent({ name: 'RayTable', props: props, - emits: ['update:columns', 'menuSelect'], + emits: ['update:columns', 'menuSelect', 'exportSuccess', 'exportError'], setup(props, { emit }) { const modelRightClickMenu = computed(() => props.rightClickMenu) const modelColumns = computed({ @@ -37,10 +41,17 @@ const RayTable = defineComponent({ }) let prevRightClickIndex = -1 - provide('rayTableProvider', { + provide('tableSettingProvider', { modelRightClickMenu, modelColumns, }) + provide('exportExcelProvider', { + exportTip: props.exportTip, + exportType: props.exportType, + exportPositiveText: props.exportPositiveText, + exportNegativeText: props.exportNegativeText, + exportFilename: props.exportFilename, + }) const handleColumnsUpdate = (arr: ActionOptions[]) => { modelColumns.value = arr @@ -92,11 +103,48 @@ const RayTable = defineComponent({ } } + const handleExportPositive = () => { + if (props.data.length && props.columns.length) { + try { + const exportHeader = setupExportHeader( + 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) // 输出表格 + + emit('exportSuccess') + } catch (e) { + emit('exportError') + } + } + } + return { handleColumnsUpdate, ...toRefs(menuConfig), handleRowProps, handleRightMenuSelect, + handleExportPositive, } }, render() { @@ -133,9 +181,14 @@ const RayTable = defineComponent({ header: () => this.title, 'header-extra': () => this.action ? ( - + + + + ) : ( '' ), diff --git a/src/components/RayTable/src/props.ts b/src/components/RayTable/src/props.ts index 61f52c6c..082ddc4c 100644 --- a/src/components/RayTable/src/props.ts +++ b/src/components/RayTable/src/props.ts @@ -68,6 +68,54 @@ const rayTableProps = { type: Boolean, default: true, }, + exportTip: { + /** + * + * 导出表格提示 + */ + type: String, + default: '是否导出为excel?', + }, + exportType: { + /** + * + * 导出类型 + * + * 默认为 `xlsx` + * + * 暂时只支持导出为 `xlsx` + */ + type: String, + default: 'xlsx', + }, + exportPositiveText: { + /** + * + * 导出确认按钮文字 + * + * 默认为 `确认` + */ + type: String, + default: '确认', + }, + exportNegativeText: { + /** + * + * 导出取消按钮文字 + * + * 默认为 `取消` + */ + type: String, + default: '取消', + }, + exportFilename: { + /** + * + * 导出表格名称 + */ + type: String, + default: '', + }, } as const export default rayTableProps diff --git a/src/components/RayTable/src/type.ts b/src/components/RayTable/src/type.ts index d932d867..c9c68279 100644 --- a/src/components/RayTable/src/type.ts +++ b/src/components/RayTable/src/type.ts @@ -5,7 +5,7 @@ import type { DropdownRenderOption, DataTableBaseColumn, } from 'naive-ui' -import type { ComputedRef, WritableComputedRef } from 'vue' +import type { ComputedRef, WritableComputedRef, VNode } from 'vue' export interface ActionOptions extends DataTableBaseColumn { leftFixedActivated?: boolean // 向左固定 @@ -35,7 +35,38 @@ export type SettingOptions = WritableComputedRef export type RightClickMenu = ComputedRef -export interface RayTableProvider { +export interface TableSettingProvider { modelRightClickMenu: RightClickMenu modelColumns: SettingOptions } + +export interface ExportExcelProvider { + exportTip: string + exportType: string + exportPositiveText: string + exportNegativeText: string + exportFilename: string +} + +export type ColumnKey = string | number + +declare type VNodeChildAtom = + | VNode + | string + | number + | boolean + | null + | undefined + | void + +export declare type VNodeArrayChildren = Array< + VNodeArrayChildren | VNodeChildAtom +> + +export declare type VNodeChild = VNodeChildAtom | VNodeArrayChildren + +export declare type TableColumnTitle = + | string + | ((column: DataTableBaseColumn) => VNodeChild) + +export interface ExportExcelHeader extends DataTableBaseColumn {} diff --git a/src/icons/export_excel.svg b/src/icons/export_excel.svg new file mode 100644 index 00000000..44937b41 --- /dev/null +++ b/src/icons/export_excel.svg @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/src/layout/components/SiderBar/Components/SettingDrawer/hook.ts b/src/layout/components/SiderBar/Components/SettingDrawer/hook.ts index 3d285190..9263a5a4 100644 --- a/src/layout/components/SiderBar/Components/SettingDrawer/hook.ts +++ b/src/layout/components/SiderBar/Components/SettingDrawer/hook.ts @@ -1,7 +1,7 @@ export const useSwatchesColorOptions = () => [ '#FFFFFF', '#18A058', - '#2080F0', + '#2d8cf0', '#F0A020', 'rgba(208, 48, 80, 1)', ] diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts index 5560d93e..d877385a 100644 --- a/src/store/modules/menu.ts +++ b/src/store/modules/menu.ts @@ -69,7 +69,7 @@ export const useMenu = defineStore('menu', () => { } } - matchMenuItem(menuState.options) + matchMenuItem(menuState.options as MenuOption[]) } /** diff --git a/src/store/modules/setting.ts b/src/store/modules/setting.ts index 0e854e0f..f413b9bf 100644 --- a/src/store/modules/setting.ts +++ b/src/store/modules/setting.ts @@ -7,7 +7,7 @@ export const useSetting = defineStore( drawerPlacement: 'right' as NaiveDrawerPlacement, primaryColorOverride: { common: { - primaryColor: '#18A058', // 主题色 + primaryColor: '#2d8cf0', // 主题色 }, }, themeValue: false, // `true` 为黑夜主题, `false` 为白色主题 diff --git a/src/types/micro.d.ts b/src/types/micro.d.ts index f419d1af..ab56ba7f 100644 --- a/src/types/micro.d.ts +++ b/src/types/micro.d.ts @@ -151,12 +151,12 @@ declare global { private id private eventObj constructor(id: string) - $on(event: string, fn: callback): EventBus + $on(event: string, fn: Function): EventBus /** 任何$emit都会导致监听函数触发,第一个参数为事件名,后续的参数为$emit的参数 */ $onAll(fn: (event: string, ...args: Array) => unknown): EventBus - $once(event: string, fn: callback): void - $off(event: string, fn: callback): EventBus - $offAll(fn: callback): EventBus + $once(event: string, fn: Function): void + $off(event: string, fn: Function): EventBus + $offAll(fn: Function): EventBus $emit(event: string, ...args: Array): EventBus $clear(): EventBus } diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 93a430c7..55f63c6b 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -38,16 +38,26 @@ export const removeCache = ( key: string | 'all' | 'all-sessionStorage' | 'all-localStorage', type: CacheType = 'sessionStorage', ) => { - if (key === 'all') { - window.window.localStorage.clear() - window.sessionStorage.clear() - } else if (key === 'all-sessionStorage') { - window.sessionStorage.clear() - } else if (key === 'all-localStorage') { - window.localStorage.clear() - } else { - type === 'localStorage' - ? window.localStorage.removeItem(key) - : window.sessionStorage.removeItem(key) + switch (key) { + case 'all': + window.window.localStorage.clear() + window.sessionStorage.clear() + + break + + case 'all-sessionStorage': + window.sessionStorage.clear() + + break + + case 'all-localStorage': + window.localStorage.clear() + + break + + default: + type === 'localStorage' + ? window.localStorage.removeItem(key) + : window.sessionStorage.removeItem(key) } } diff --git a/src/views/rely/views/rely-about/index.tsx b/src/views/rely/views/rely-about/index.tsx index fa5bd320..c31c2f80 100644 --- a/src/views/rely/views/rely-about/index.tsx +++ b/src/views/rely/views/rely-about/index.tsx @@ -33,7 +33,9 @@ const RelyAbout = defineComponent({ key: 'relyAddress', }, ] - const relyData = ref([]) + const dependenciesOptions = ref([]) + const devDependenciesOptions = ref([]) + const templateOptions = [ { name: '项目名称', @@ -62,10 +64,8 @@ const RelyAbout = defineComponent({ return pre }, [] as RelyDataOptions[]) - const arrDependencies = _arrayFrom(dependencies) - const arrDevDependencies = _arrayFrom(devDependencies) - - relyData.value = [...arrDependencies, ...arrDevDependencies] + dependenciesOptions.value = _arrayFrom(dependencies) + devDependenciesOptions.value = _arrayFrom(devDependencies) } const handleTagClick = (item: TemplateOptions) => { @@ -80,7 +80,8 @@ const RelyAbout = defineComponent({ return { columns, - relyData, + dependenciesOptions, + devDependenciesOptions, templateOptions, handleTagClick, } @@ -110,9 +111,18 @@ const RelyAbout = defineComponent({ ))} - + - {this.relyData.map((curr) => ( + {this.dependenciesOptions.map((curr) => ( + + {curr.relyVersion} + + ))} + + + + + {this.devDependenciesOptions.map((curr) => ( {curr.relyVersion} diff --git a/src/views/table/index.tsx b/src/views/table/index.tsx index a5c9afab..78062f23 100644 --- a/src/views/table/index.tsx +++ b/src/views/table/index.tsx @@ -131,7 +131,7 @@ const TableView = defineComponent({ 该组件基于 Naive UI DataTable 组件封装. 实现右键菜单, 表格标题, - 操作栏等功能 + 导出为 excel 操作栏等功能 RayTable 完全继承 DataTable 的所有属性与方法 @@ -158,6 +158,7 @@ const TableView = defineComponent({ 拖拽操作栏动态切换表格列 点击左右固定按钮, 即可动态固定列 点击修改列宽度, 即可拖动列修改宽度 + 点击导出按钮即可导出 excel 表格, 默认以列为表头输出 ), default: () => (
该组件基于 Naive UI DataTable 组件封装. 实现右键菜单, 表格标题, - 操作栏等功能 + 导出为 excel 操作栏等功能
RayTable 完全继承 DataTable 的所有属性与方法
@@ -158,6 +158,7 @@ const TableView = defineComponent({
拖拽操作栏动态切换表格列
点击左右固定按钮, 即可动态固定列
点击修改列宽度, 即可拖动列修改宽度
点击导出按钮即可导出 excel 表格, 默认以列为表头输出