From 89e792dccadf1c8c1dc704f5a5c111d881423b5d Mon Sep 17 00:00:00 2001 From: zhang zhuo Date: Tue, 21 Oct 2025 18:08:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 22 +- src/api/model/system.ts | 13 + src/assets/icons/Wifi.vue | 3 + src/components/piAsset/index.vue | 136 ++++ src/components/piAsset/move.vue | 63 ++ src/components/piAsset/picker.vue | 646 +++++++++++++++++++ src/components/piAsset/save.vue | 99 +++ src/components/piCharts/echarts-theme-T.ts | 74 +++ src/components/piCharts/index.vue | 64 ++ src/components/piDialogSelect/index.vue | 181 ++++++ src/components/piEditor/ext/FontSize.ts | 18 + src/components/piEditor/ext/Image/Action.vue | 111 ++++ src/components/piEditor/ext/Image/index.ts | 29 + src/components/piEditor/ext/Indent.ts | 109 ++++ src/components/piEditor/ext/Selection.ts | 79 +++ src/components/piEditor/ext/Table/Action.vue | 86 +++ src/components/piEditor/ext/Table/index.ts | 137 ++++ src/components/piEditor/ext/Video/Action.vue | 105 +++ src/components/piEditor/ext/Video/index.ts | 92 +++ src/components/piEditor/index.vue | 569 ++++++++++++++++ src/components/piIcon/index.vue | 1 + src/components/piMap/index.vue | 124 ++++ src/components/piPage/index.vue | 11 - src/components/piPlayer/index.vue | 91 +++ src/components/piSection/index.vue | 21 + src/components/piSelect/index.vue | 254 ++++++++ src/components/piTable/index.vue | 166 +++-- src/components/piTableForm/index.vue | 131 ++++ src/config/asset.ts | 122 ++++ src/config/select.ts | 42 ++ src/layout/components/userbar.vue | 13 +- src/pi.ts | 2 - src/utils/request.ts | 6 + src/utils/tools.ts | 32 +- src/views/monitor/online/index.vue | 80 +++ src/views/monitor/server/index.vue | 328 ++++++++++ src/views/system/menu/quick.vue | 13 +- src/views/system/post/index.vue | 2 +- src/views/system/user/index.vue | 4 - vite.config.ts | 18 +- 40 files changed, 3983 insertions(+), 114 deletions(-) create mode 100644 src/assets/icons/Wifi.vue create mode 100644 src/components/piAsset/index.vue create mode 100644 src/components/piAsset/move.vue create mode 100644 src/components/piAsset/picker.vue create mode 100644 src/components/piAsset/save.vue create mode 100644 src/components/piCharts/echarts-theme-T.ts create mode 100644 src/components/piCharts/index.vue create mode 100644 src/components/piDialogSelect/index.vue create mode 100644 src/components/piEditor/ext/FontSize.ts create mode 100644 src/components/piEditor/ext/Image/Action.vue create mode 100644 src/components/piEditor/ext/Image/index.ts create mode 100644 src/components/piEditor/ext/Indent.ts create mode 100644 src/components/piEditor/ext/Selection.ts create mode 100644 src/components/piEditor/ext/Table/Action.vue create mode 100644 src/components/piEditor/ext/Table/index.ts create mode 100644 src/components/piEditor/ext/Video/Action.vue create mode 100644 src/components/piEditor/ext/Video/index.ts create mode 100644 src/components/piEditor/index.vue create mode 100644 src/components/piMap/index.vue delete mode 100644 src/components/piPage/index.vue create mode 100644 src/components/piPlayer/index.vue create mode 100644 src/components/piSection/index.vue create mode 100644 src/components/piSelect/index.vue create mode 100644 src/components/piTableForm/index.vue create mode 100644 src/config/asset.ts create mode 100644 src/config/select.ts create mode 100644 src/views/monitor/online/index.vue create mode 100644 src/views/monitor/server/index.vue diff --git a/package.json b/package.json index 870d178..994eb57 100644 --- a/package.json +++ b/package.json @@ -10,27 +10,31 @@ "preview": "vite preview" }, "dependencies": { - "@element-plus/icons-vue": "^2.3.1", - "axios": "^1.9.0", - "cropperjs": "1.5.13", + "@element-plus/icons-vue": "^2.3.2", + "axios": "1.12.0", + "cropperjs": "^1.6.2", "crypto-js": "^4.2.0", - "element-plus": "^2.9.10", + "echarts": "^6.0.0", + "element-plus": "2.11.3", + "image-conversion": "^2.1.1", "nprogress": "^0.2.0", "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.3.0", "sortablejs": "^1.15.6", - "vue": "^3.5.14", - "vue-i18n": "^11.1.5", + "vue": "^3.5.22", + "vue-i18n": "^11.1.12", "vue-router": "^4.5.1", - "vuedraggable": "^2.24.3" + "vuedraggable": "^4.1.0", + "xgplayer": "^3.0.22", + "xgplayer-hls": "^3.0.22" }, "devDependencies": { - "@types/node": "^22.15.21", + "@types/node": "^24.9.1", "@vitejs/plugin-vue": "^5.2.4", "eslint": "^9.27.0", "sass": "^1.89.0", "typescript": "^5.8.3", - "vite": "^6.3.5" + "vite": "7.1.5" }, "license": "MIT" } diff --git a/src/api/model/system.ts b/src/api/model/system.ts index c636092..04f03f9 100644 --- a/src/api/model/system.ts +++ b/src/api/model/system.ts @@ -89,4 +89,17 @@ export default { upload: async function (data, config = {}) { return await http.post("upload", data, config); }, + monitor: { + server: async function (data, config = {}) { + return await http.get("monitor/server", data, config); + } + }, + online: { + list: async function (data, config = {}) { + return await http.get("online/list", data, config); + }, + quit: async function (data, config = {}) { + return await http.get("online/quit", data, config); + } + } } diff --git a/src/assets/icons/Wifi.vue b/src/assets/icons/Wifi.vue new file mode 100644 index 0000000..8d313de --- /dev/null +++ b/src/assets/icons/Wifi.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/piAsset/index.vue b/src/components/piAsset/index.vue new file mode 100644 index 0000000..e3ef373 --- /dev/null +++ b/src/components/piAsset/index.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/components/piAsset/move.vue b/src/components/piAsset/move.vue new file mode 100644 index 0000000..3560243 --- /dev/null +++ b/src/components/piAsset/move.vue @@ -0,0 +1,63 @@ + + + + diff --git a/src/components/piAsset/picker.vue b/src/components/piAsset/picker.vue new file mode 100644 index 0000000..fe86a42 --- /dev/null +++ b/src/components/piAsset/picker.vue @@ -0,0 +1,646 @@ + + + + + + diff --git a/src/components/piAsset/save.vue b/src/components/piAsset/save.vue new file mode 100644 index 0000000..899cd76 --- /dev/null +++ b/src/components/piAsset/save.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/components/piCharts/echarts-theme-T.ts b/src/components/piCharts/echarts-theme-T.ts new file mode 100644 index 0000000..4154161 --- /dev/null +++ b/src/components/piCharts/echarts-theme-T.ts @@ -0,0 +1,74 @@ +const T = { + "color": [ + "#409EFF", + "#36CE9E", + "#f56e6a", + "#626c91", + "#edb00d", + "#909399" + ], + 'grid': { + 'left': '3%', + 'right': '3%', + 'bottom': '10', + 'top': '40', + 'containLabel': true + }, + "legend": { + "textStyle": { + "color": "#999" + }, + "inactiveColor": "rgba(128,128,128,0.4)" + }, + "categoryAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "rgba(128,128,128,0.2)", + "width": 1 + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "color": "#999" + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": [ + "#eee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(255,255,255,0.01)", + "rgba(0,0,0,0.01)" + ] + } + } + }, + "valueAxis": { + "axisLine": { + "show": false, + "lineStyle": { + "color": "#999" + } + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": "rgba(128,128,128,0.2)" + } + } + } +} + +export default T diff --git a/src/components/piCharts/index.vue b/src/components/piCharts/index.vue new file mode 100644 index 0000000..bb2c7e1 --- /dev/null +++ b/src/components/piCharts/index.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/piDialogSelect/index.vue b/src/components/piDialogSelect/index.vue new file mode 100644 index 0000000..92a4ad6 --- /dev/null +++ b/src/components/piDialogSelect/index.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/src/components/piEditor/ext/FontSize.ts b/src/components/piEditor/ext/FontSize.ts new file mode 100644 index 0000000..38444ef --- /dev/null +++ b/src/components/piEditor/ext/FontSize.ts @@ -0,0 +1,18 @@ +import {TextStyle} from '@tiptap/extension-text-style' + +export default TextStyle.extend({ + addAttributes() { + return { + fontSize: { + default: null, + parseHTML: element => element.style.fontSize || null, + renderHTML: attributes => { + if (!attributes.fontSize) return {} + return { + style: `font-size: ${attributes.fontSize}`, + } + }, + }, + } + }, +}) diff --git a/src/components/piEditor/ext/Image/Action.vue b/src/components/piEditor/ext/Image/Action.vue new file mode 100644 index 0000000..a9de392 --- /dev/null +++ b/src/components/piEditor/ext/Image/Action.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/components/piEditor/ext/Image/index.ts b/src/components/piEditor/ext/Image/index.ts new file mode 100644 index 0000000..3804894 --- /dev/null +++ b/src/components/piEditor/ext/Image/index.ts @@ -0,0 +1,29 @@ +import Action from "./Action.vue" +import {VueNodeViewRenderer} from "@tiptap/vue-3" +import { Image } from '@tiptap/extension-image' + +export default Image.extend({ + addAttributes() { + return { + ...this.parent?.(), + class: { + default: null, + }, + style: { + default: null, + }, + width: { + default: null, + }, + height: { + default: null + }, + alt: { + default: null + } + } + }, + addNodeView() { + return VueNodeViewRenderer(Action) + }, +}) diff --git a/src/components/piEditor/ext/Indent.ts b/src/components/piEditor/ext/Indent.ts new file mode 100644 index 0000000..31d7a86 --- /dev/null +++ b/src/components/piEditor/ext/Indent.ts @@ -0,0 +1,109 @@ +import {Extension, Command} from '@tiptap/core' + +declare module '@tiptap/core' { + interface Commands { + indent: { + increaseIndent: () => ReturnType + decreaseIndent: () => ReturnType + } + } +} + +export default Extension.create({ + name: 'indent', + addOptions() { + return { + maxIndent: 50, + types: ['paragraph', 'heading', 'code_block', 'bullet_list', 'ordered_list'], + } + }, + addGlobalAttributes() { + return [ + { + types: this.options.types, + attributes: { + 'indent': { + default: 0, + parseHTML: element => parseInt(element.getAttribute('indent') || '0', 10), + renderHTML: attributes => { + const level = attributes['indent'] + return level > 0 && { + 'indent': level, + style: `text-indent: ${level * 2}em;` + } + }, + }, + }, + }, + ] + }, + addCommands() { + return { + increaseIndent: + (): Command => + ({tr, state, dispatch}) => { + const {from, to} = state.selection + let modified = false + state.doc.nodesBetween(from, to, (node, pos) => { + if (this.options.types.includes(node.type.name)) { + const indent = node.attrs['indent'] || 0 + if (indent + 1 > this.options.maxIndent) return + tr.setNodeMarkup(pos, undefined, { + ...node.attrs, + 'indent': indent + 1, + }) + modified = true + } + }) + if (modified) { + dispatch && dispatch(tr) + return false + } + return true + }, + + decreaseIndent: + (): Command => + ({tr, state, dispatch}) => { + const {from, to} = state.selection + let modified = false + state.doc.nodesBetween(from, to, (node, pos) => { + if (this.options.types.includes(node.type.name)) { + const indent = node.attrs['indent'] || 0 + if (indent - 1 < 0) return + const newIndent = Math.max(indent - 1, 0) + tr.setNodeMarkup(pos, undefined, { + ...node.attrs, + 'indent': newIndent, + }) + modified = true + } + }) + if (modified) { + dispatch && dispatch(tr) + return false + } + return true + }, + } + }, + addKeyboardShortcuts() { + return { + Tab: () => this.editor.commands.increaseIndent(), + 'Shift-Tab': () => this.editor.commands.decreaseIndent(), + Backspace: () => { + const {state} = this.editor + const {selection} = state + const {$from} = selection + if ($from.parentOffset !== 0) return false + const node = $from.node() + if (!this.options.types.includes(node.type.name)) return false + const indent = node.attrs['indent'] || 0 + if (indent > 0) { + return this.editor.commands.decreaseIndent() + } + return false + }, + } + }, +}) diff --git a/src/components/piEditor/ext/Selection.ts b/src/components/piEditor/ext/Selection.ts new file mode 100644 index 0000000..22a89a4 --- /dev/null +++ b/src/components/piEditor/ext/Selection.ts @@ -0,0 +1,79 @@ +import {Extension} from '@tiptap/core' +import {Plugin, PluginKey} from '@tiptap/pm/state' +import {Decoration, DecorationSet} from '@tiptap/pm/view' + +declare module '@tiptap/core' { + interface Commands { + selection: { + setSelection: () => ReturnType + clearSelection: () => ReturnType + } + } +} + +export default Extension.create({ + name: 'selection', + addOptions() { + return { + class: 'selection', + } + }, + addStorage() { + return { + fakeRange: null as { from: number, to: number } | null, + } + }, + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey('selection'), + props: { + decorations: (state) => { + const {fakeRange} = this.storage + if (!fakeRange) return null + const deco = Decoration.inline( + fakeRange.from, + fakeRange.to, + {class: this.options.class}, + ) + return DecorationSet.create(state.doc, [deco]) + }, + handleClick: (view, pos, event) => { + const { fakeRange } = this.storage + if (!fakeRange) return false + + const clickedInsideFake = + pos >= fakeRange.from && pos <= fakeRange.to + + if (!clickedInsideFake) { + // 点击了 fake selection 外部,清除它 + this.storage.fakeRange = null + view.dispatch(view.state.tr) + } + + return false + }, + }, + }), + ] + }, + addCommands() { + return { + setSelection: () => ({state, view}) => { + const {from, to, empty} = state.selection + if (!empty && from !== to) { + this.storage.fakeRange = {from, to} + view.dispatch(state.tr) + } + return true + }, + clearSelection: () => ({state, view}) => { + if (this.storage.fakeRange) { + this.storage.fakeRange = null + view.dispatch(state.tr) + } + return true + }, + } + }, +}) diff --git a/src/components/piEditor/ext/Table/Action.vue b/src/components/piEditor/ext/Table/Action.vue new file mode 100644 index 0000000..37de2a8 --- /dev/null +++ b/src/components/piEditor/ext/Table/Action.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/components/piEditor/ext/Table/index.ts b/src/components/piEditor/ext/Table/index.ts new file mode 100644 index 0000000..c92f2a0 --- /dev/null +++ b/src/components/piEditor/ext/Table/index.ts @@ -0,0 +1,137 @@ +import {VueNodeViewRenderer} from "@tiptap/vue-3"; +import {NodeSelection} from 'prosemirror-state' +import {Table} from '@tiptap/extension-table' +import Action from "./Action.vue" + +export default Table.extend({ + selectable: true, + atom: true, + // 添加属性配置 + addAttributes() { + return { + ...this.parent?.(), + width: { + default: '100%', + renderHTML: attributes => { + return { + width: attributes.width, + } + } + }, + class: { + default: null, + renderHTML: attributes => { + return { + class: attributes.class, + } + } + }, + style: { + default: 'table-layout: fixed;border-collapse: collapse;', + renderHTML: attributes => { + if (!attributes.style) return {} + return { + style: attributes.style, + } + } + } + } + }, + renderHTML({node, HTMLAttributes}) { + return ['table', HTMLAttributes, ['tbody', 0]] + }, + addKeyboardShortcuts() { + return { + Backspace: ({editor}) => { + const {state, view} = editor + const {selection} = state + + // 没有深度直接删除 + const {$from} = selection + if ($from.depth == 0) { + editor.commands.deleteSelection() + return true + } + + // 选中整个表格删除 + if (selection instanceof NodeSelection && selection.node.type.name === 'table') { + const tr = state.tr.delete(selection.from, selection.to) + view.dispatch(tr) + return true + } + const pos = $from.before($from.depth) // 当前段落的开始位置 + const index = $from.index($from.depth - 1) + + if (index == 0) return false + const parent = $from.node($from.depth - 1) + + // 查前一个节点是不是 table + const beforeNode = parent.child(index - 1) + + if (beforeNode?.type.name === 'table') { + const deletePos = pos - beforeNode.nodeSize + view.dispatch( + state.tr + .setSelection(NodeSelection.create(state.doc, deletePos)) + .deleteSelection() + ) + return true + } + + return false + }, + Delete: ({editor}) => { + const {state, view} = editor + const {selection} = state + + // 没有深度直接删除 + const {$from} = selection + if ($from.depth == 0) { + editor.commands.deleteSelection() + return true + } + + //选中整个表格删除 + if (selection instanceof NodeSelection && selection.node.type.name === 'table') { + const tr = state.tr.delete(selection.from, selection.to) + view.dispatch(tr) + return true + } + + // 在表格前删除 + const pos = $from.before($from.depth) // 当前段落的开始位置 + const index = $from.index($from.depth - 1) + const parent = $from.node($from.depth - 1) + + if (index < parent.childCount - 1) { + const afterNode = parent.child(index + 1) + if (afterNode?.type.name === 'table') { + const deletePos = pos + $from.node().nodeSize + view.dispatch( + state.tr + .setSelection(NodeSelection.create(state.doc, deletePos)) + .deleteSelection() + ) + return true + } + } else { + if (index == 0) return false + const beforeNode = parent.child(index - 1) + if (beforeNode?.type.name === 'table') { + const deletePos = pos - beforeNode.nodeSize + view.dispatch( + state.tr + .setSelection(NodeSelection.create(state.doc, deletePos)) + .deleteSelection() + ) + return true + } + } + return false + }, + } + }, + addNodeView() { + return VueNodeViewRenderer(Action) + } +}) diff --git a/src/components/piEditor/ext/Video/Action.vue b/src/components/piEditor/ext/Video/Action.vue new file mode 100644 index 0000000..e9f78e8 --- /dev/null +++ b/src/components/piEditor/ext/Video/Action.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/components/piEditor/ext/Video/index.ts b/src/components/piEditor/ext/Video/index.ts new file mode 100644 index 0000000..4b9b149 --- /dev/null +++ b/src/components/piEditor/ext/Video/index.ts @@ -0,0 +1,92 @@ +// extensions/Video.ts +import {Node, mergeAttributes, Command, CommandProps} from '@tiptap/core' +import {VueNodeViewRenderer} from "@tiptap/vue-3"; +import Action from "./Action.vue"; + +export interface VideoOptions { + HTMLAttributes: Record +} + +declare module '@tiptap/core' { + interface Commands { + video: { + setVideo: (options: { src: string }) => ReturnType + } + } +} + +export default Node.create({ + name: 'video', + group: 'block', + atom: true, + selectable: true, + addOptions() { + return { + HTMLAttributes: {}, + } + }, + addAttributes() { + return { + src: { + default: null, + }, + width: { + default: '100%', + renderHTML: attributes => { + return { + width: attributes.width, + } + } + }, + height: { + default: null + }, + class: { + default: null, + renderHTML: attributes => { + return { + class: attributes.class, + } + } + }, + style: { + default: '', + renderHTML: attributes => { + if (!attributes.style) return {} + return { + style: attributes.style, + } + } + } + } + }, + parseHTML() { + return [ + { + tag: 'video', + }, + ] + }, + renderHTML({HTMLAttributes}) { + return [ + 'video', + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + controls: true, + }), + ['source', {src: HTMLAttributes.src, type: 'video/mp4'}], + ] + }, + addCommands() { + return { + setVideo: (attrs: { src: string }): Command => ({commands}: CommandProps) => { + return commands.insertContent({ + type: this.name, + attrs, + }) + }, + } + }, + addNodeView() { + return VueNodeViewRenderer(Action) + }, +}) diff --git a/src/components/piEditor/index.vue b/src/components/piEditor/index.vue new file mode 100644 index 0000000..3807080 --- /dev/null +++ b/src/components/piEditor/index.vue @@ -0,0 +1,569 @@ + + + + + diff --git a/src/components/piIcon/index.vue b/src/components/piIcon/index.vue index 32641a9..aa09ef9 100644 --- a/src/components/piIcon/index.vue +++ b/src/components/piIcon/index.vue @@ -3,6 +3,7 @@
+ {{value}}
diff --git a/src/components/piMap/index.vue b/src/components/piMap/index.vue new file mode 100644 index 0000000..3c8c4d6 --- /dev/null +++ b/src/components/piMap/index.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/components/piPage/index.vue b/src/components/piPage/index.vue deleted file mode 100644 index 2dd905f..0000000 --- a/src/components/piPage/index.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/components/piPlayer/index.vue b/src/components/piPlayer/index.vue new file mode 100644 index 0000000..0f81c68 --- /dev/null +++ b/src/components/piPlayer/index.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/components/piSection/index.vue b/src/components/piSection/index.vue new file mode 100644 index 0000000..41caf11 --- /dev/null +++ b/src/components/piSection/index.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/src/components/piSelect/index.vue b/src/components/piSelect/index.vue new file mode 100644 index 0000000..3989cf3 --- /dev/null +++ b/src/components/piSelect/index.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/src/components/piTable/index.vue b/src/components/piTable/index.vue index 3bd7340..a74f6ef 100644 --- a/src/components/piTable/index.vue +++ b/src/components/piTable/index.vue @@ -1,11 +1,19 @@