From 9949a3ff0b65c0941e096228bcb34f8500e91ba1 Mon Sep 17 00:00:00 2001 From: donghao Date: Tue, 8 Jul 2025 15:02:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=80=E5=A7=8B=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Dialog/src/dialog.tsx | 2 +- src/renderer/src/components/Table/index.ts | 11 + .../Table/src/baseAction.vue} | 0 .../src/components/Table/src/baseColumn.vue | 104 ++++ .../src/components/Table/src/baseTable.scss | 153 ++++++ .../src/components/Table/src/baseTable.tsx | 488 ++++++++++++++++++ .../src/components/Table/src/type.d.ts | 42 ++ src/renderer/src/styles/element-plus.scss | 10 +- .../src/views/Design/Controls/headCtrl.vue | 4 +- .../views/Design/LogManagement/logList.scss | 4 + .../views/Design/LogManagement/logList.vue | 106 ++++ src/renderer/src/views/Design/Settings/01.txt | 49 -- .../views/Design/Settings/settingList.scss | 12 +- .../src/views/Design/Settings/settingList.vue | 3 +- .../Design/System/logManagement/index.vue | 0 src/renderer/src/views/Design/index.vue | 9 +- 16 files changed, 926 insertions(+), 71 deletions(-) create mode 100644 src/renderer/src/components/Table/index.ts rename src/renderer/src/{views/Design/LogManagement/index.vue => components/Table/src/baseAction.vue} (100%) create mode 100644 src/renderer/src/components/Table/src/baseColumn.vue create mode 100644 src/renderer/src/components/Table/src/baseTable.scss create mode 100644 src/renderer/src/components/Table/src/baseTable.tsx create mode 100644 src/renderer/src/components/Table/src/type.d.ts create mode 100644 src/renderer/src/views/Design/LogManagement/logList.scss create mode 100644 src/renderer/src/views/Design/LogManagement/logList.vue delete mode 100644 src/renderer/src/views/Design/Settings/01.txt delete mode 100644 src/renderer/src/views/Design/System/logManagement/index.vue diff --git a/src/renderer/src/components/Dialog/src/dialog.tsx b/src/renderer/src/components/Dialog/src/dialog.tsx index e0aa4c1..a9a96a8 100644 --- a/src/renderer/src/components/Dialog/src/dialog.tsx +++ b/src/renderer/src/components/Dialog/src/dialog.tsx @@ -169,7 +169,7 @@ export default defineComponent({ > {slots.header?.()} {slots.default?.() || renderContent()} - {slots.footer?.() || renderFooter()} + {slots.footer?.()} ) } diff --git a/src/renderer/src/components/Table/index.ts b/src/renderer/src/components/Table/index.ts new file mode 100644 index 0000000..9b9d223 --- /dev/null +++ b/src/renderer/src/components/Table/index.ts @@ -0,0 +1,11 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2025-07-08 14:28:02 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2025-07-08 14:28:24 + * @FilePath: \Robot-Al-Platform-Web\src\renderer\src\components\Table\index.ts + * @Description: 表格控件集 + */ +import baseTable from './src/baseTable' + +export const DSTable = baseTable diff --git a/src/renderer/src/views/Design/LogManagement/index.vue b/src/renderer/src/components/Table/src/baseAction.vue similarity index 100% rename from src/renderer/src/views/Design/LogManagement/index.vue rename to src/renderer/src/components/Table/src/baseAction.vue diff --git a/src/renderer/src/components/Table/src/baseColumn.vue b/src/renderer/src/components/Table/src/baseColumn.vue new file mode 100644 index 0000000..df991b9 --- /dev/null +++ b/src/renderer/src/components/Table/src/baseColumn.vue @@ -0,0 +1,104 @@ + + + + + + diff --git a/src/renderer/src/components/Table/src/baseTable.scss b/src/renderer/src/components/Table/src/baseTable.scss new file mode 100644 index 0000000..cdbc876 --- /dev/null +++ b/src/renderer/src/components/Table/src/baseTable.scss @@ -0,0 +1,153 @@ +.baseTable_wrap { + /* 去掉表格整体边框 */ + .el-table { + border: none !important; + background-color: transparent; + } + + /* 去掉表头下边框 */ + .el-table__header-wrapper thead th { + border-bottom: none !important; + border: none !important; + } + /* 去掉单元格边框 */ + .el-table td, + .el-table th.is-leaf { + border-bottom: none !important; + border: none !important; + } + /* 去掉纵向分割线 */ + .el-table--border::after, + .el-table--group::after, + .el-table::before { + display: none; + } + .el-table--border, + .el-table--group { + border-right: none !important; + border-bottom: none !important; + border: none !important; + } + .el-table td, + .el-table th { + border-right: none !important; + border: none !important; + border-collapse: collapse !important; + } + + .el-scrollbar__view { + background: transparent !important; + } + + .el-table--large .el-table__cell { + padding: 8.5px 0; + } + + .baseTable_box { + cursor: pointer; + .el-table__body { + background: transparent; + border-collapse: collapse !important; + border: none !important; + tr { + color: #fff; + background: transparent; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + + &:hover td { + background: transparent !important; + } + &:hover { + color: #37dbff; + background: linear-gradient( + 90deg, + rgba(30, 54, 88, 0) 0%, + #0c4fad 53%, + rgba(65, 117, 190, 0) 100% + ); + border-top: 1px solid rgba(69, 174, 250, 0.3); + border-bottom: 1px solid rgba(69, 174, 250, 0.3); + } + &.selected-row { + color: #37dbff; + background: linear-gradient( + 90deg, + rgba(30, 54, 88, 0) 0%, + #0c4fad 53%, + rgba(65, 117, 190, 0) 100% + ); + border-top: 1px solid rgba(69, 174, 250, 0.3); + border-bottom: 1px solid rgba(69, 174, 250, 0.3); + } + } + } + + .el-table__header > thead { + color: #9fb5d7; + background-color: #104284 !important; + tr { + background-color: #104284 !important; + } + th { + background-color: #104284 !important; + } + } + } + .fixed_pagination { + padding: 12px 20px 0; + } + /* full_table */ + &.full_table { + .el-table--large .el-table__cell { + padding: 4px 0; + } + .baseTable_box { + cursor: default; + .el-table__body { + border: none !important; + background: linear-gradient(90deg, #082050 0%, #02102a 100%); + tr { + color: #fff; + background: linear-gradient(90deg, #082050 0%, #02102a 100%); + &:nth-child(odd) { + background: linear-gradient(90deg, #082050 0%, #02102a 100%); + } + &:nth-child(even) { + background: linear-gradient(90deg, #102d65 0%, #081736 100%); + } + &:hover { + border: none !important; + } + &:hover td { + background-color: transparent; + } + } + } + + .el-table__header > thead { + color: #9fb5d7; + background-color: #104284 !important; + tr { + background-color: #104284 !important; + } + th { + background-color: #104284 !important; + } + } + } + .fixed_pagination { + padding: 28px 20px; + } + } +} + +.pagination_box { + margin-top: 50px; + width: 100%; + // position: fixed; + // bottom: 100px; + // right: 40px; + background-color: white; + z-index: 9; +} diff --git a/src/renderer/src/components/Table/src/baseTable.tsx b/src/renderer/src/components/Table/src/baseTable.tsx new file mode 100644 index 0000000..190effa --- /dev/null +++ b/src/renderer/src/components/Table/src/baseTable.tsx @@ -0,0 +1,488 @@ +import { ElLoading, ElPagination, ElTable, ElTableColumn } from "element-plus"; +import BaseColumn from "./baseColumn.vue"; +import { isUndefined } from "@/utils/is"; +import { + computed, + defineComponent, + nextTick, + reactive, + type PropType +} from "vue"; +import "./baseTable.scss"; + +function getDefaultSort(attrs: Record): any { + return attrs["default-sort"] || attrs.defaultSort; +} + +export default defineComponent({ + name: "XTable", + directives: { + loading: ElLoading.directive + }, + inheritAttrs: false, + props: { + /** + * 表格的列描述信息 + */ + columns: { + type: Array as PropType, + required: true + }, + + /** + * 表格的数据 + */ + dataSource: { + type: Array as PropType, + required: true + }, + + /** + * loading + */ + loading: { + type: Boolean, + default: false + }, + + /** + * 最大高度,包含分页高度 + */ + maxHeight: { + type: [Number, String] as PropType, + default: "auto" + }, + + /** + * 是否显示分页 + * 为 always 时,将一直显示 + * 为 false 时,总是不显示 + * 为 true 时,只有有数据时才显示 + */ + pageable: { + type: [Boolean, String] as PropType, + default: true, + validator(value: boolean | "always") { + return ["always", true, false].includes(value); + } + }, + + /** + * 分页布局 + */ + pagerLayout: { + type: String, + default: "total, sizes, prev, pager, next, jumper", + validator(value: string) { + return value + .split(",") + .map(item => item.trim()) + .every(item => + ["total", "sizes", "prev", "pager", "next", "jumper"].includes(item) + ); + } + }, + + /** + * 总条数 + */ + total: { + type: Number, + default: 0 + }, + + /** + * 每页显示数量 + */ + pageSize: { + type: Number, + default: 10 + }, + + /** + * 页码 + */ + page: { + type: Number, + default: 1 + }, + + /** + * 分页选择器的选项设置 + */ + pageSizes: { + type: Array as PropType, + default() { + return [15, 20, 30, 50]; + } + }, + + /** + * 行数据的 Key + */ + rowKey: { + type: [Function, String] as PropType< + (row: XTableData) => string | string + >, + default: "id" + }, + + /** + * 打开自定义列 + */ + visibleColumn: { + type: Boolean, + default: undefined + }, + + /** + * 固定分页器 + */ + isFixedPagination: { + type: Boolean, + default: true + } + + // handleDel: { + // type: Function, + // default: () => {} + // } + // customActions: { + // type: Function, + // default: () => {} + // } + }, + emits: ["change", "columnChange", "update:visibleColumn", "actions"], + setup(props, { slots, attrs, emit }) { + const nonPropsAttrs = attrs; + const { prop: sortBy, order: sortOrder } = getDefaultSort(attrs) || {}; + const tableState = reactive({ + tid: 0, + sortBy, + sortOrder + }); + + const showPagination = computed(() => { + if (props.pageable === "always") return true; + return props.pageable && props.dataSource.length > 0; + }); + + const mHeight = computed(() => { + if (props.maxHeight === "auto") { + return "auto"; + } + return showPagination.value ? props.maxHeight - 44 : props.maxHeight; + }); + + /** + * 获取插槽 + */ + function getSlot(column: XTableColumn, suffix?: string) { + const name = column.prop || column.type; + if (name) { + const key = suffix ? `${name}-${suffix}` : name; + return slots[key]; + } + } + + /** + * 改变表格列的排序和分页 + */ + function onChange(data: XTableChangeData) { + emit("change", data); + } + + /** + * 改变页数 + */ + function handlePageNumChange(page: number) { + const { sortBy, sortOrder } = tableState; + const { pageSize } = props; + onChange({ + page, + pageSize, + prop: sortBy, + order: sortOrder, + type: "number" + }); + } + + /** + * 改变每页显示的条数 + */ + function handlePageSizeChange(pageSize: number) { + const { sortBy, sortOrder } = tableState; + nextTick(() => { + // 下拉框溢出可能导致溢出 body 出现滚动条 + // 加个延迟,等下拉隐藏 + onChange({ + page: 1, + pageSize, + prop: sortBy, + order: sortOrder, + type: "size" + }); + }); + } + + /** + * 排序 + */ + function handleTableSortChange({ prop, order }: XTableSort) { + const { pageSize } = props; + onChange({ page: 1, pageSize, prop, order, type: "sort" }); + } + + /** + * 自定义列回调 + */ + function handleColumnChange(cols: XTableColumn[]) { + emit("columnChange", cols); + } + + /** + * 自定义列隐藏 + */ + function handleVisibleChange(val: boolean) { + emit("update:visibleColumn", val); + } + + /** + * 获取表格列的属性 + */ + function getColumnProps(column: XTableColumn) { + const col = { ...column }; + Reflect.deleteProperty(col, "children"); + Reflect.deleteProperty(col, "hidden"); + col.key = column.key || column.prop || column.type; + col.showOverflowTooltip = col.showOverflowTooltip ?? true; + col.showOverflowTooltip = + column.prop === "action" ? false : column.showOverflowTooltip; + return col; + } + + /** + * 渲染特殊列 + */ + function renderTypeColumn(column: XTableColumn) { + if (column.type === "expand") { + return ( + + {{ + default: (scope: Record) => { + const slot = getSlot(column); + return slot?.(scope); + } + }} + + ); + } + if (column.type === "action") { + return ( + + {{ + default: ({ row }: { row: Record }) => { + return ( +
+ {slots.actionBar &&
{slots.actionBar({ row })}
} + {/*
    +
  • handleDel(row)} + > + + + + 删除 + + +
  • +
*/} +
+ ); + } + }} +
+ ); + } + return ; + } + + /**操作按钮事件 */ + + // function handleDel(row) { + // console.log(row, "handleDel"); + // emit("actions", { + // type: "delete", + // data: { ...row } + // }); + // } + + /** + * 渲染普通列 + */ + function renderBaseColumn(column: XTableColumn) { + const columnSlots: { + default?: (scope: Record) => any; + header?: (scope: Record) => any; + } = {}; + const slot = getSlot(column); + const headerSlot = getSlot(column, "header"); + + if (slot) { + columnSlots.default = scope => slot(scope); + } + + if (headerSlot) { + columnSlots.header = scope => headerSlot(scope); + } + + return ( + {columnSlots} + ); + } + + /** + * 渲染列 + */ + function renderTableColumn(column: XTableColumn) { + if (column.hidden) return; + if (column.type) { + return renderTypeColumn(column); + } + return renderBaseColumn(column); + } + + /** + * 渲染多级列 + */ + function renderColumnChildren( + column: XTableColumn, + children: Required["children"] + ) { + if (column.hidden) return; + return ( + + {children.map(column => renderTableColumn(column))} + + ); + } + + /** + * 渲染分页 + */ + function renderPagination() { + const paginationProps = { + size: "small", + background: false, + total: props.total, + layout: props.pagerLayout, + pageSize: props.pageSize, + pageSizes: props.pageSizes, + currentPage: props.page, + onSizeChange: handlePageSizeChange, + onCurrentChange: handlePageNumChange + }; + + return ( +
+ +
+ ); + } + + /** + * 渲染自定义列 + */ + function renderCustomColumn() { + const customColumnProps = { + columns: props.columns, + visible: props.visibleColumn, + onChange: handleColumnChange, + onVisibleChange: handleVisibleChange + }; + + return ; + } + + return () => { + const tableProps = { + ref: "elTableRef", + ...nonPropsAttrs, + maxHeight: mHeight.value, + data: props.dataSource, + rowKey: props.rowKey, + onSortChange: handleTableSortChange + }; + + const extraSlots: { + append?: () => any; + empty?: () => any; + } = {}; + + if (slots.append) { + extraSlots.append = () => slots.append?.(); + } + + if (slots.empty) { + extraSlots.empty = () => slots.empty?.(); + } + + return ( +
+ + {props.columns.map(column => { + if (Array.isArray(column.children)) { + return renderColumnChildren(column, column.children); + } + return renderTableColumn(column); + })} + {/* 使用插槽引入操作栏的内容 */} + {/* + {{ + default: ({ row }: { row: Record }) => { + return extraSlots.customActions?.({ + row + }); + } + }} + */} + {/* + + */} + + {showPagination.value && renderPagination()} + {!isUndefined(props.visibleColumn) && renderCustomColumn()} +
+ ); + }; + } +}); \ No newline at end of file diff --git a/src/renderer/src/components/Table/src/type.d.ts b/src/renderer/src/components/Table/src/type.d.ts new file mode 100644 index 0000000..11a8d05 --- /dev/null +++ b/src/renderer/src/components/Table/src/type.d.ts @@ -0,0 +1,42 @@ +import type ElTable from "element-plus/lib/components/table"; +import type { ElTableColumn } from "element-plus/lib/components/table"; + +export {}; + +type ElTableType = InstanceType; +type ElTableProps = ElTableType["$props"]; +type ElTableColumnProps = InstanceType["$props"]; +type ElTableSort = Pick< + Required["defaultSort"], + "prop" | "order" +>; + +type ElTableAction = { + type: "delete" | "update" | string; // 操作类型 + confirmType?: "popup" | "modal"; // 确认组件 +}; + +declare global { + type XTableSort = ElTableSort; + + interface XTableColumn extends ElTableColumnProps { + children?: XTableColumn[]; + hidden?: boolean; + } + + interface XTableData { + [key: string]: any; + } + + interface XTableState { + tid: number; + sortBy?: XTableSort["prop"]; + sortOrder?: XTableSort["order"]; + } + + interface XTableChangeData extends Partial { + type: "size" | "number" | "sort"; + pageNum: number; + pageSize: number; + } +} diff --git a/src/renderer/src/styles/element-plus.scss b/src/renderer/src/styles/element-plus.scss index 92bf265..a3d0b64 100644 --- a/src/renderer/src/styles/element-plus.scss +++ b/src/renderer/src/styles/element-plus.scss @@ -46,21 +46,23 @@ } /* 弹框 */ -.el-dialog { - --el-dialog-padding-primary: 0; // 弹窗内边距 -} .ds-dialog { + --el-dialog-padding-primary: 0; // 弹窗内边距 + --el-dialog-bg-color: var(--ds-dialog-background-color) !important; // 弹窗背景颜色 padding: 0 !important; overflow: hidden; .el-dialog__header { background-color: var(--ds-color-primary); - .el-dialog__title{ + .el-dialog__title { color: var(--ds-color-info); line-height: var(--ds-dialog-header-height); padding-left: var(--ds-dialog-header-padding); } } + .el-dialog__body { + color: var(--ds-color-info); + } .el-dialog__footer { --el-dialog-padding-primary: 0; // 弹窗内边距 diff --git a/src/renderer/src/views/Design/Controls/headCtrl.vue b/src/renderer/src/views/Design/Controls/headCtrl.vue index 542353d..d39e431 100644 --- a/src/renderer/src/views/Design/Controls/headCtrl.vue +++ b/src/renderer/src/views/Design/Controls/headCtrl.vue @@ -62,7 +62,8 @@ defineOptions({ name: 'TitleBar' }) interface Emits { - (e: 'setting'): void + (e: 'setting'): void; + (e: 'open-log'): void; } const emit = defineEmits() @@ -76,6 +77,7 @@ const handleMenuClick = (command) => { switch (command) { case 'log': // 打开日志页面 + emit('open-log') break case 'communication': // 打开通信管理 diff --git a/src/renderer/src/views/Design/LogManagement/logList.scss b/src/renderer/src/views/Design/LogManagement/logList.scss new file mode 100644 index 0000000..76fa258 --- /dev/null +++ b/src/renderer/src/views/Design/LogManagement/logList.scss @@ -0,0 +1,4 @@ +.logList-dialog{ + + +} \ No newline at end of file diff --git a/src/renderer/src/views/Design/LogManagement/logList.vue b/src/renderer/src/views/Design/LogManagement/logList.vue new file mode 100644 index 0000000..cb569dd --- /dev/null +++ b/src/renderer/src/views/Design/LogManagement/logList.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/renderer/src/views/Design/Settings/01.txt b/src/renderer/src/views/Design/Settings/01.txt deleted file mode 100644 index 9c373c5..0000000 --- a/src/renderer/src/views/Design/Settings/01.txt +++ /dev/null @@ -1,49 +0,0 @@ - - -
- - - - - - - - - - - - -
- -
\ No newline at end of file diff --git a/src/renderer/src/views/Design/Settings/settingList.scss b/src/renderer/src/views/Design/Settings/settingList.scss index 6ec381f..37f220e 100644 --- a/src/renderer/src/views/Design/Settings/settingList.scss +++ b/src/renderer/src/views/Design/Settings/settingList.scss @@ -1,9 +1,6 @@ .settings-dialog { /* Your dialog styles */ - // background-color: #363940 !important; - - background-color: #2d3a4b; - color: #fff; + .settings-container { display: flex; min-height: 632px; @@ -27,21 +24,14 @@ align-items: center; margin-bottom: 16px; opacity: 0.3; - color: #fff; &.active { opacity: 1; } } - // .sidebar-item:hover, - // .sidebar-item.active { - // background-color: #3c4b64; - // } - .settings-content { flex: 1; padding: 24px 24px 12px; - } .section { diff --git a/src/renderer/src/views/Design/Settings/settingList.vue b/src/renderer/src/views/Design/Settings/settingList.vue index 40ebf98..38f5ae3 100644 --- a/src/renderer/src/views/Design/Settings/settingList.vue +++ b/src/renderer/src/views/Design/Settings/settingList.vue @@ -2,7 +2,7 @@ * @Author: donghao donghao@supervision.ltd * @Date: 2025-07-03 11:12:04 * @LastEditors: donghao donghao@supervision.ltd - * @LastEditTime: 2025-07-08 14:04:49 + * @LastEditTime: 2025-07-08 14:50:21 * @FilePath: \electron-project\Robot-Al\Robot-Al-Platform-Web\src\renderer\src\views\Design\Settings\setPermissions.vue * @Description: 设置权限 --> @@ -65,7 +65,6 @@ interface Props { /** 弹窗显隐 */ value: boolean info: Record - image: any } interface Emits { (e: 'update:value', val: boolean): void diff --git a/src/renderer/src/views/Design/System/logManagement/index.vue b/src/renderer/src/views/Design/System/logManagement/index.vue deleted file mode 100644 index e69de29..0000000 diff --git a/src/renderer/src/views/Design/index.vue b/src/renderer/src/views/Design/index.vue index 42b6be3..dabe0f0 100644 --- a/src/renderer/src/views/Design/index.vue +++ b/src/renderer/src/views/Design/index.vue @@ -2,14 +2,14 @@ * @Author: donghao donghao@supervision.ltd * @Date: 2025-07-02 16:17:29 * @LastEditors: donghao donghao@supervision.ltd - * @LastEditTime: 2025-07-08 14:05:57 + * @LastEditTime: 2025-07-08 14:52:43 * @FilePath: \electron-project\Robot-Al\Robot-Al-Platform-Web\src\renderer\src\views\Design\index.vue * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE -->