From 0c0746cc6a9e5f67dc2caf52c4990bec844e4d9a Mon Sep 17 00:00:00 2001 From: Jachin Date: Tue, 8 Aug 2023 20:24:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20:sparkles:=20=E6=B7=BB=E5=8A=A0demo:=20?= =?UTF-8?q?=E5=8A=A8=E6=80=81Table,=E6=8B=96=E6=8B=BDTable,=E7=BB=BC?= =?UTF-8?q?=E5=90=88Table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc-auto-import.json | 21 +- mock/article.ts | 168 +++++ mock/router.ts | 46 ++ package.json | 1 + src/api/article.ts | 88 +++ src/assets/icons/star.svg | 1 + src/assets/icons/table.svg | 1 + src/layout/components/TagsView/index.vue | 2 - src/styles/index.scss | 10 + src/types/auto-imports.d.ts | 13 +- src/types/components.d.ts | 32 +- src/views/table/Export2Excel.js | 257 ++++++++ src/views/table/complex-table.vue | 617 ++++++++++++++++++ src/views/table/drag-table.vue | 171 +++++ .../dynamic-table/components/FixedThead.vue | 55 ++ .../dynamic-table/components/UnfixedThead.vue | 45 ++ src/views/table/dynamic-table/index.vue | 24 + 17 files changed, 1517 insertions(+), 35 deletions(-) create mode 100644 mock/article.ts create mode 100644 src/api/article.ts create mode 100644 src/assets/icons/star.svg create mode 100644 src/assets/icons/table.svg create mode 100644 src/views/table/Export2Excel.js create mode 100644 src/views/table/complex-table.vue create mode 100644 src/views/table/drag-table.vue create mode 100644 src/views/table/dynamic-table/components/FixedThead.vue create mode 100644 src/views/table/dynamic-table/components/UnfixedThead.vue create mode 100644 src/views/table/dynamic-table/index.vue diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 3620c18..f4738bf 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -1,10 +1,16 @@ { "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, "EffectScope": true, - "ElForm": true, "ElMessage": true, "ElMessageBox": true, - "ElTree": true, + "ElNotification": true, + "InjectionKey": true, + "PropType": true, + "Ref": true, + "VNode": true, "asyncComputed": true, "autoResetRef": true, "computed": true, @@ -19,7 +25,9 @@ "createGlobalState": true, "createInjectionState": true, "createReactiveFn": true, + "createReusableTemplate": true, "createSharedComposable": true, + "createTemplatePromise": true, "createUnrefFn": true, "customRef": true, "debouncedRef": true, @@ -75,7 +83,6 @@ "refThrottled": true, "refWithControl": true, "resolveComponent": true, - "resolveDirective": true, "resolveRef": true, "resolveUnref": true, "shallowReactive": true, @@ -90,6 +97,7 @@ "toReactive": true, "toRef": true, "toRefs": true, + "toValue": true, "triggerRef": true, "tryOnBeforeMount": true, "tryOnBeforeUnmount": true, @@ -100,11 +108,14 @@ "unrefElement": true, "until": true, "useActiveElement": true, + "useAnimate": true, + "useArrayDifference": true, "useArrayEvery": true, "useArrayFilter": true, "useArrayFind": true, "useArrayFindIndex": true, "useArrayFindLast": true, + "useArrayIncludes": true, "useArrayJoin": true, "useArrayMap": true, "useArrayReduce": true, @@ -190,6 +201,8 @@ "useOnline": true, "usePageLeave": true, "useParallax": true, + "useParentElement": true, + "usePerformanceObserver": true, "usePermission": true, "usePointer": true, "usePointerLock": true, @@ -255,8 +268,10 @@ "watchArray": true, "watchAtMost": true, "watchDebounced": true, + "watchDeep": true, "watchEffect": true, "watchIgnorable": true, + "watchImmediate": true, "watchOnce": true, "watchPausable": true, "watchPostEffect": true, diff --git a/mock/article.ts b/mock/article.ts new file mode 100644 index 0000000..8563e6e --- /dev/null +++ b/mock/article.ts @@ -0,0 +1,168 @@ +import { MockMethod } from "vite-plugin-mock"; + +const article_list: any = []; +const count = 100; + +for (let i = 0; i < count; i++) { + article_list.push({ + id: i, + timestamp: new Date().getTime(), + author: `Author ${i}`, + reviewer: `reviewer ${i}`, + title: `Title ${i}`, + importance: Math.floor(Math.random() * 3) + 1, + type: ["CN", "US", "JP", "EU"][Math.floor(Math.random() * 4)], + status: ["published", "draft"][Math.floor(Math.random() * 2)], + display_time: new Date().toISOString(), + pageviews: Math.floor(Math.random() * (5000 - 300)) + 300, + remark: `remark ${i}`, + }); +} + +export default [ + { + url: "/api/v1/article/list", + timeout: 200, + method: "get", + response: ({ query }) => { + const { importance, type, title, page = 1, limit = 10, sort } = query; + let mock_list = article_list.filter((item: any) => { + if (importance && item.importance !== +importance) return false; + if (type && item.type !== type) return false; + if (title && item.title.indexOf(title) < 0) return false; + if (item.status === "deleted") return false; + return true; + }); + if (sort === "-id") { + mock_list = mock_list.reverse(); + } + const page_list = mock_list.filter( + (item: any, index: number) => + index < limit * page && index >= limit * (page - 1) + ); + + return { + code: "00000", + data: { total: mock_list.length, page: page, items: page_list }, + msg: "一切ok", + }; + }, + }, + { + url: "/api/v1/article/detail", + timeout: 200, + method: "get", + response: ({ query }) => { + const { id } = query; + for (const article of article_list) { + if (article.id === +id) { + return { + code: "00000", + data: article, + msg: "一切ok", + }; + } + } + }, + }, + { + url: "/api/v1/article/pv", + timeout: 200, + method: "get", + response: ({ query }) => { + const { id } = query; + for (const article of article_list) { + if (article.id === +id) { + return { + code: "00000", + data: { + pv: article.pageviews, + pvData: [ + { key: "PC", pv: 1024 }, + { key: "mobile", pv: 1024 }, + { key: "ios", pv: 1024 }, + { key: "android", pv: 1024 }, + ], + }, + msg: "一切ok", + }; + } + } + }, + }, + { + url: "/api/v1/article/update", + timeout: 200, + method: "post", + response: ({ body }) => { + const { id, ...updatedFields } = body; + // 查找要更新的文章 + const articleToUpdate = article_list.find( + (article: any) => article.id === id + ); + + // 如果找到了要更新的文章 + if (articleToUpdate) { + // 使用 Object.assign 方法更新文章 + Object.assign(articleToUpdate, updatedFields); + return { + code: "00000", + data: { + article: articleToUpdate, + }, + msg: "一切ok", + }; + } else { + console.error(`Article with id ${id} not found.`); + } + }, + }, + { + url: "/api/v1/article/create", + timeout: 200, + method: "post", + response: ({ body }) => { + const { title, author, importance, type, status, remark, timestamp } = + body; + // article_list最大的id值; + const maxId = article_list.reduce((maxId: number, article: any) => { + return Math.max(maxId, article.id); + }, -1); + const article = { + id: maxId + 1, + timestamp, + author, + reviewer: `reviewer ${maxId + 1}`, + title, + importance, + type, + status, + display_time: new Date(timestamp).toISOString(), + pageviews: Math.floor(Math.random() * (5000 - 300)) + 300, + remark, + }; + article_list.push(article); + return { + code: "00000", + data: { + article, + }, + msg: "一切ok", + }; + }, + }, + { + url: "/api/v1/article/delete", + timeout: 200, + method: "post", + response: ({ body }) => { + const { id } = body; + const index = article_list.findIndex((article: any) => article.id === id); + article_list.splice(index, 1); + return { + code: "00000", + msg: "一切ok", + }; + }, + }, +] as MockMethod[]; diff --git a/mock/router.ts b/mock/router.ts index 6272b8c..6a67bb2 100644 --- a/mock/router.ts +++ b/mock/router.ts @@ -291,6 +291,52 @@ const data = { }, ], }, + { + path: "/table", + component: "Layout", + meta: { + title: "Table", + icon: "table", + hidden: false, + roles: ["ADMIN"], + keepAlive: true, + }, + children: [ + { + path: "dynamic-table", + component: "table/dynamic-table/index", + name: "DynamicTable", + meta: { + title: "动态Table", + hidden: false, + roles: ["ADMIN"], + keepAlive: true, + }, + }, + { + path: "drag-table", + component: "table/drag-table", + name: "DragTable", + meta: { + title: "拖拽Table", + hidden: false, + roles: ["ADMIN"], + keepAlive: true, + }, + }, + { + path: "complex-table", + component: "table/complex-table", + name: "ComplexTable", + meta: { + title: "综合Table", + hidden: false, + roles: ["ADMIN"], + keepAlive: true, + }, + }, + ], + }, { path: "/function", component: "Layout", diff --git a/package.json b/package.json index 6eba793..9cd8fe7 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "path-to-regexp": "^6.2.0", "pinia": "^2.0.33", "screenfull": "^6.0.0", + "sortablejs": "^1.15.0", "vue": "^3.3.4", "vue-i18n": "9.2.2", "vue-router": "^4.2.0", diff --git a/src/api/article.ts b/src/api/article.ts new file mode 100644 index 0000000..f2277b4 --- /dev/null +++ b/src/api/article.ts @@ -0,0 +1,88 @@ +import request from "@/utils/request"; + +export interface ArticleQuery { + page?: number; + limit?: number; + sort?: string; + title?: string; + type?: string; + importance?: number; +} + +export interface ArticleDetail { + id: number; + timestamp: number; + title: string; + type: string; + status: string; + importance: number; + content?: string; + remark?: string; +} + +export interface ArticleCreate { + type: string; + timestamp: Date; + title: string; + status?: string; + importance?: number; + remark?: string; +} + +export interface ArticleUpdate { + id: number; + type?: string; + timestamp?: Date; + title?: string; + status?: string; + importance?: number; + remark?: string; +} + +export function fetchList(query: ArticleQuery) { + return request({ + url: "/api/v1/article/list", + method: "get", + params: query, + }); +} + +export function fetchArticle(id: number) { + return request({ + url: "/api/v1/article/detail", + method: "get", + params: { id }, + }); +} + +export function fetchPv(id: number) { + return request({ + url: "/api/v1/article/pv", + method: "get", + params: { id }, + }); +} + +export function createArticle(data: ArticleCreate) { + return request({ + url: "/api/v1/article/create", + method: "post", + data, + }); +} + +export function updateArticle(data: ArticleUpdate) { + return request({ + url: "/api/v1/article/update", + method: "post", + data, + }); +} + +export function deleteArticle(id: number) { + return request({ + url: "/api/v1/article/delete", + method: "post", + params: { id }, + }); +} diff --git a/src/assets/icons/star.svg b/src/assets/icons/star.svg new file mode 100644 index 0000000..6cf86e6 --- /dev/null +++ b/src/assets/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/table.svg b/src/assets/icons/table.svg new file mode 100644 index 0000000..0e3dc9d --- /dev/null +++ b/src/assets/icons/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue index f71f017..717c6c9 100644 --- a/src/layout/components/TagsView/index.vue +++ b/src/layout/components/TagsView/index.vue @@ -203,8 +203,6 @@ function closeAllTags(view: TagView) { function openTagMenu(tag: TagView, e: MouseEvent) { const menuMinWidth = 105; - console.log("test", proxy?.$el); - const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left const offsetWidth = proxy?.$el.offsetWidth; // container width const maxLeft = offsetWidth - menuMinWidth; // left boundary diff --git a/src/styles/index.scss b/src/styles/index.scss index cccbd99..6b635af 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -14,3 +14,13 @@ border-radius: 4px; box-shadow: var(--el-box-shadow-light); } + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32 160 255); + } +} diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index fd4e5fd..4cd09b1 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -5,10 +5,9 @@ export {} declare global { const EffectScope: typeof import("vue")["EffectScope"]; - const ElForm: typeof import("element-plus/es")["ElForm"]; const ElMessage: typeof import("element-plus/es")["ElMessage"]; const ElMessageBox: typeof import("element-plus/es")["ElMessageBox"]; - const ElTree: typeof import("element-plus/es")["ElTree"]; + const ElNotification: typeof import("element-plus/es")["ElNotification"]; const asyncComputed: typeof import("@vueuse/core")["asyncComputed"]; const autoResetRef: typeof import("@vueuse/core")["autoResetRef"]; const computed: typeof import("vue")["computed"]; @@ -297,14 +296,15 @@ import { UnwrapRef } from "vue"; declare module "vue" { interface ComponentCustomProperties { readonly EffectScope: UnwrapRef; - readonly ElForm: UnwrapRef; readonly ElMessage: UnwrapRef< typeof import("element-plus/es")["ElMessage"] >; readonly ElMessageBox: UnwrapRef< typeof import("element-plus/es")["ElMessageBox"] >; - readonly ElTree: UnwrapRef; + readonly ElNotification: UnwrapRef< + typeof import("element-plus/es")["ElNotification"] + >; readonly asyncComputed: UnwrapRef< typeof import("@vueuse/core")["asyncComputed"] >; @@ -943,14 +943,15 @@ declare module "vue" { declare module "@vue/runtime-core" { interface ComponentCustomProperties { readonly EffectScope: UnwrapRef; - readonly ElForm: UnwrapRef; readonly ElMessage: UnwrapRef< typeof import("element-plus/es")["ElMessage"] >; readonly ElMessageBox: UnwrapRef< typeof import("element-plus/es")["ElMessageBox"] >; - readonly ElTree: UnwrapRef; + readonly ElNotification: UnwrapRef< + typeof import("element-plus/es")["ElNotification"] + >; readonly asyncComputed: UnwrapRef< typeof import("@vueuse/core")["asyncComputed"] >; diff --git a/src/types/components.d.ts b/src/types/components.d.ts index d243962..fdf9855 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -12,12 +12,15 @@ declare module "@vue/runtime-core" { AppMain: typeof import("./../layout/components/AppMain.vue")["default"]; BarChart: typeof import("./../views/dashboard/components/BarChart.vue")["default"]; Breadcrumb: typeof import("./../components/Breadcrumb/index.vue")["default"]; - ElAlert: typeof import("element-plus/es")["ElAlert"]; + Dictionary: typeof import("./../components/Dictionary/index.vue")["default"]; ElBreadcrumb: typeof import("element-plus/es")["ElBreadcrumb"]; ElBreadcrumbItem: typeof import("element-plus/es")["ElBreadcrumbItem"]; ElButton: typeof import("element-plus/es")["ElButton"]; ElCard: typeof import("element-plus/es")["ElCard"]; + ElCheckbox: typeof import("element-plus/es")["ElCheckbox"]; + ElCheckboxGroup: typeof import("element-plus/es")["ElCheckboxGroup"]; ElCol: typeof import("element-plus/es")["ElCol"]; + ElDatePicker: typeof import("element-plus/es")["ElDatePicker"]; ElDialog: typeof import("element-plus/es")["ElDialog"]; ElDivider: typeof import("element-plus/es")["ElDivider"]; ElDropdown: typeof import("element-plus/es")["ElDropdown"]; @@ -25,17 +28,13 @@ declare module "@vue/runtime-core" { ElDropdownMenu: typeof import("element-plus/es")["ElDropdownMenu"]; ElForm: typeof import("element-plus/es")["ElForm"]; ElFormItem: typeof import("element-plus/es")["ElFormItem"]; - ElIcon: typeof import("element-plus/es")["ElIcon"]; ElInput: typeof import("element-plus/es")["ElInput"]; - ElInputNumber: typeof import("element-plus/es")["ElInputNumber"]; ElLink: typeof import("element-plus/es")["ElLink"]; ElMenu: typeof import("element-plus/es")["ElMenu"]; ElMenuItem: typeof import("element-plus/es")["ElMenuItem"]; ElOption: typeof import("element-plus/es")["ElOption"]; ElPagination: typeof import("element-plus/es")["ElPagination"]; - ElPopover: typeof import("element-plus/es")["ElPopover"]; - ElRadio: typeof import("element-plus/es")["ElRadio"]; - ElRadioGroup: typeof import("element-plus/es")["ElRadioGroup"]; + ElRate: typeof import("element-plus/es")["ElRate"]; ElRow: typeof import("element-plus/es")["ElRow"]; ElScrollbar: typeof import("element-plus/es")["ElScrollbar"]; ElSelect: typeof import("element-plus/es")["ElSelect"]; @@ -45,31 +44,15 @@ declare module "@vue/runtime-core" { ElTableColumn: typeof import("element-plus/es")["ElTableColumn"]; ElTag: typeof import("element-plus/es")["ElTag"]; ElTooltip: typeof import("element-plus/es")["ElTooltip"]; - ElTree: typeof import("element-plus/es")["ElTree"]; - ElTreeSelect: typeof import("element-plus/es")["ElTreeSelect"]; - ElUpload: typeof import("element-plus/es")["ElUpload"]; + FixedThead: typeof import("./../views/table/dynamic-table/components/FixedThead.vue")["default"]; FunnelChart: typeof import("./../views/dashboard/components/FunnelChart.vue")["default"]; GithubCorner: typeof import("./../components/GithubCorner/index.vue")["default"]; Hamburger: typeof import("./../components/Hamburger/index.vue")["default"]; IconSelect: typeof import("./../components/IconSelect/index.vue")["default"]; - IEpArrowDown: typeof import("~icons/ep/arrow-down")["default"]; IEpCaretBottom: typeof import("~icons/ep/caret-bottom")["default"]; - IEpCaretTop: typeof import("~icons/ep/caret-top")["default"]; IEpClose: typeof import("~icons/ep/close")["default"]; - IEpCollection: typeof import("~icons/ep/collection")["default"]; - IEpDelete: typeof import("~icons/ep/delete")["default"]; IEpDownload: typeof import("~icons/ep/download")["default"]; - IEpEdit: typeof import("~icons/ep/edit")["default"]; - IEpPlus: typeof import("~icons/ep/plus")["default"]; - IEpPosition: typeof import("~icons/ep/position")["default"]; - IEpRefresh: typeof import("~icons/ep/refresh")["default"]; - IEpRefreshLeft: typeof import("~icons/ep/refresh-left")["default"]; - IEpSearch: typeof import("~icons/ep/search")["default"]; IEpSetting: typeof import("~icons/ep/setting")["default"]; - IEpSortDown: typeof import("~icons/ep/sort-down")["default"]; - IEpSortUp: typeof import("~icons/ep/sort-up")["default"]; - IEpTop: typeof import("~icons/ep/top")["default"]; - IEpUploadFilled: typeof import("~icons/ep/upload-filled")["default"]; LangSelect: typeof import("./../components/LangSelect/index.vue")["default"]; Link: typeof import("./../layout/components/Sidebar/Link.vue")["default"]; Logo: typeof import("./../layout/components/Sidebar/Logo.vue")["default"]; @@ -88,10 +71,11 @@ declare module "@vue/runtime-core" { SingleUpload: typeof import("./../components/Upload/SingleUpload.vue")["default"]; SizeSelect: typeof import("./../components/SizeSelect/index.vue")["default"]; SvgIcon: typeof import("./../components/SvgIcon/index.vue")["default"]; + SwitchRoles: typeof import("./../views/permission/components/SwitchRoles.vue")["default"]; TagInput: typeof import("./../components/TagInput/index.vue")["default"]; TagsView: typeof import("./../layout/components/TagsView/index.vue")["default"]; + UnfixedThead: typeof import("./../views/table/dynamic-table/components/UnfixedThead.vue")["default"]; WangEditor: typeof import("./../components/WangEditor/index.vue")["default"]; - Dictionary: typeof import("./../components/Dictionary/index.vue")["default"]; } export interface ComponentCustomProperties { vLoading: typeof import("element-plus/es")["ElLoadingDirective"]; diff --git a/src/views/table/Export2Excel.js b/src/views/table/Export2Excel.js new file mode 100644 index 0000000..c660d6b --- /dev/null +++ b/src/views/table/Export2Excel.js @@ -0,0 +1,257 @@ +/* eslint-disable */ +import * as XLSX from "xlsx"; + +// TODO: this is a toy example, may be file-saver is a better choice +// import { saveAs } from 'file-saver' +function saveAs(blob, fileName) { + const type = fileName.split(".")[1]; + console.log(type); + const file = new window.File([blob], fileName, { type: type }); + console.log(file); + // 创建一个指向 File 对象的 URL + const url = URL.createObjectURL(file); + + // 创建一个 a 标签 + const a = document.createElement("a"); + a.href = url; + a.download = fileName; + + // 将 a 标签添加到文档中 + document.body.appendChild(a); + + // 模拟点击 a 标签,开始下载 + a.click(); + + // 下载完成后,从文档中移除 a 标签,并释放 URL + document.body.removeChild(a); + URL.revokeObjectURL(url); + return file; +} + +function generateArray(table) { + var out = []; + var rows = table.querySelectorAll("tr"); + var ranges = []; + for (var R = 0; R < rows.length; ++R) { + var outRow = []; + var row = rows[R]; + var columns = row.querySelectorAll("td"); + for (var C = 0; C < columns.length; ++C) { + var cell = columns[C]; + var colspan = cell.getAttribute("colspan"); + var rowspan = cell.getAttribute("rowspan"); + var cellValue = cell.innerText; + if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; + + //Skip ranges + ranges.forEach(function (range) { + if ( + R >= range.s.r && + R <= range.e.r && + outRow.length >= range.s.c && + outRow.length <= range.e.c + ) { + for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); + } + }); + + //Handle Row Span + if (rowspan || colspan) { + rowspan = rowspan || 1; + colspan = colspan || 1; + ranges.push({ + s: { + r: R, + c: outRow.length, + }, + e: { + r: R + rowspan - 1, + c: outRow.length + colspan - 1, + }, + }); + } + + //Handle Value + outRow.push(cellValue !== "" ? cellValue : null); + + //Handle Colspan + if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null); + } + out.push(outRow); + } + return [out, ranges]; +} + +function datenum(v, date1904) { + if (date1904) v += 1462; + var epoch = Date.parse(v); + return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); +} + +function sheet_from_array_of_arrays(data, opts) { + var ws = {}; + var range = { + s: { + c: 10000000, + r: 10000000, + }, + e: { + c: 0, + r: 0, + }, + }; + for (var R = 0; R != data.length; ++R) { + for (var C = 0; C != data[R].length; ++C) { + if (range.s.r > R) range.s.r = R; + if (range.s.c > C) range.s.c = C; + if (range.e.r < R) range.e.r = R; + if (range.e.c < C) range.e.c = C; + var cell = { + v: data[R][C], + }; + if (cell.v == null) continue; + var cell_ref = XLSX.utils.encode_cell({ + c: C, + r: R, + }); + + if (typeof cell.v === "number") cell.t = "n"; + else if (typeof cell.v === "boolean") cell.t = "b"; + else if (cell.v instanceof Date) { + cell.t = "n"; + cell.z = XLSX.SSF._table[14]; + cell.v = datenum(cell.v); + } else cell.t = "s"; + + ws[cell_ref] = cell; + } + } + if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range); + return ws; +} + +function Workbook() { + if (!(this instanceof Workbook)) return new Workbook(); + this.SheetNames = []; + this.Sheets = {}; +} + +function s2ab(s) { + var buf = new ArrayBuffer(s.length); + var view = new Uint8Array(buf); + for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff; + return buf; +} + +export function export_table_to_excel(id) { + var theTable = document.getElementById(id); + var oo = generateArray(theTable); + var ranges = oo[1]; + + /* original data */ + var data = oo[0]; + var ws_name = "SheetJS"; + + var wb = new Workbook(), + ws = sheet_from_array_of_arrays(data); + + /* add ranges to worksheet */ + // ws['!cols'] = ['apple', 'banan']; + ws["!merges"] = ranges; + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, { + bookType: "xlsx", + bookSST: false, + type: "binary", + }); + + saveAs( + new Blob([s2ab(wbout)], { + type: "application/octet-stream", + }), + "test.xlsx" + ); +} + +export function export_json_to_excel({ + multiHeader = [], + header, + data, + filename, + merges = [], + autoWidth = true, + bookType = "xlsx", +} = {}) { + /* original data */ + filename = filename || "excel-list"; + data = [...data]; + data.unshift(header); + + for (let i = multiHeader.length - 1; i > -1; i--) { + data.unshift(multiHeader[i]); + } + + var ws_name = "SheetJS"; + var wb = new Workbook(), + ws = sheet_from_array_of_arrays(data); + + if (merges.length > 0) { + if (!ws["!merges"]) ws["!merges"] = []; + merges.forEach((item) => { + ws["!merges"].push(XLSX.utils.decode_range(item)); + }); + } + + if (autoWidth) { + /*设置worksheet每列的最大宽度*/ + const colWidth = data.map((row) => + row.map((val) => { + /*先判断是否为null/undefined*/ + if (val == null) { + return { + wch: 10, + }; + } else if (val.toString().charCodeAt(0) > 255) { + /*再判断是否为中文*/ + return { + wch: val.toString().length * 2, + }; + } else { + return { + wch: val.toString().length, + }; + } + }) + ); + /*以第一行为初始值*/ + let result = colWidth[0]; + for (let i = 1; i < colWidth.length; i++) { + for (let j = 0; j < colWidth[i].length; j++) { + if (result[j]["wch"] < colWidth[i][j]["wch"]) { + result[j]["wch"] = colWidth[i][j]["wch"]; + } + } + } + ws["!cols"] = result; + } + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, { + bookType: bookType, + bookSST: false, + type: "binary", + }); + saveAs( + new Blob([s2ab(wbout)], { + type: "application/octet-stream", + }), + `${filename}.${bookType}` + ); +} diff --git a/src/views/table/complex-table.vue b/src/views/table/complex-table.vue new file mode 100644 index 0000000..a2d8139 --- /dev/null +++ b/src/views/table/complex-table.vue @@ -0,0 +1,617 @@ + + + + + diff --git a/src/views/table/drag-table.vue b/src/views/table/drag-table.vue new file mode 100644 index 0000000..fb24c7d --- /dev/null +++ b/src/views/table/drag-table.vue @@ -0,0 +1,171 @@ + + + + + + + diff --git a/src/views/table/dynamic-table/components/FixedThead.vue b/src/views/table/dynamic-table/components/FixedThead.vue new file mode 100644 index 0000000..84ce9de --- /dev/null +++ b/src/views/table/dynamic-table/components/FixedThead.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/views/table/dynamic-table/components/UnfixedThead.vue b/src/views/table/dynamic-table/components/UnfixedThead.vue new file mode 100644 index 0000000..8056267 --- /dev/null +++ b/src/views/table/dynamic-table/components/UnfixedThead.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/views/table/dynamic-table/index.vue b/src/views/table/dynamic-table/index.vue new file mode 100644 index 0000000..acabac1 --- /dev/null +++ b/src/views/table/dynamic-table/index.vue @@ -0,0 +1,24 @@ + + +