wip: 添加表格到编辑器 正在完成编辑表格功能

master
shengwen.chen 2 months ago
parent a6647dcefe
commit ad14e6d7ee

1
.gitignore vendored

@ -20,6 +20,7 @@ coverage
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
!.vscode/settings.json
.idea .idea
*.suo *.suo
*.ntvs* *.ntvs*

@ -0,0 +1,35 @@
{
//
"editor.formatOnType": false,
"editor.formatOnSave": true,
"editor.formatOnPaste": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
//
"editor.tabSize": 2,
"editor.snippetSuggestions": "top",
"editor.guides.bracketPairs": "active",
"editor.suggestSelection": "first",
"editor.acceptSuggestionOnCommitCharacter": false,
"editor.quickSuggestions": {
"other": true,
"comments": true,
"strings": true
},
//
"editor.wordWrap": "on",
"editor.wordWrapColumn": 180,
"editor.rulers": [180],
//
"files.autoSave": "off",
// Git
"git.confirmSync": false,
//
"workbench.startupEditor": "newUntitledFile"
}

20
package-lock.json generated

@ -8,6 +8,7 @@
"name": "smdoceditor", "name": "smdoceditor",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@iconify/vue": "^4.3.0",
"axios": "^1.8.4", "axios": "^1.8.4",
"element-plus": "^2.9.7", "element-plus": "^2.9.7",
"pinia": "^3.0.1", "pinia": "^3.0.1",
@ -1337,6 +1338,25 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@iconify/types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="
},
"node_modules/@iconify/vue": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-4.3.0.tgz",
"integrity": "sha512-Xq0h6zMrHBbrW8jXJ9fISi+x8oDQllg5hTDkDuxnWiskJ63rpJu9CvJshj8VniHVTbsxCg9fVoPAaNp3RQI5OQ==",
"dependencies": {
"@iconify/types": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/cyberalien"
},
"peerDependencies": {
"vue": ">=3"
}
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",

@ -14,6 +14,7 @@
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@iconify/vue": "^4.3.0",
"axios": "^1.8.4", "axios": "^1.8.4",
"element-plus": "^2.9.7", "element-plus": "^2.9.7",
"pinia": "^3.0.1", "pinia": "^3.0.1",

@ -1,6 +1,7 @@
<!-- eslint-disable @typescript-eslint/no-explicit-any -->
<script setup lang="ts"> <script setup lang="ts">
import { inject, defineProps } from 'vue' import { inject } from 'vue'
import type { IWidget } from '@/types/widgetsConfigInterface' import type { IWidget, IWidgetJson } from '@/types/widgetsConfigInterface'
import RendererDom from '@/components/RendererDom.vue' import RendererDom from '@/components/RendererDom.vue'
const props = defineProps<{ const props = defineProps<{
@ -10,6 +11,7 @@ const props = defineProps<{
// //
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const docForm = inject('docForm') as any const docForm = inject('docForm') as any
const widgetJson = inject('widgetJson') as IWidgetJson
// //
if (props['itemData']['category'] == 'formItem') { if (props['itemData']['category'] == 'formItem') {
@ -19,24 +21,97 @@ if (props['itemData']['category'] == 'formItem') {
docForm[props['itemData']['options']['name']] = props['itemData']['options']['defaultValue'] || 0 docForm[props['itemData']['options']['name']] = props['itemData']['options']['defaultValue'] || 0
} }
} }
// id
const handleFindDomById = (data: any, returnParent = false, parent: any) => {
if (Array.isArray(data)) {
for (const item of data) {
const found: any = handleFindDomById(item, returnParent, data);
if (found) return found;
}
} else {
if (data.id === widgetJson['activeId']) {
console.log(parent);
console.log(data);
return returnParent ? parent : data;
}
//
if (data.subDomList) {
const result: any = handleFindDomById(data.subDomList, returnParent, data);
if (result) return result;
}
}
return null;
}
//
const handleSelectDom = (activeId: any) => {
widgetJson['activeId'] = activeId
}
//
const handleFindLastDom = () => {
}
//
const handleAddRow = () => {
// widgetJson['activeId'] = 'input-12366'
const res = handleFindDomById(widgetJson['subDomList'], true, null);
console.log(res);
}
//
const handleAddCol = () => {
}
//
const handleDelDom = () => {
}
</script> </script>
<template> <template>
<!-- 针对表格容器 --> <!-- 针对表格容器 -->
<template v-if="itemData['type'] == 'table'"> <template v-if="itemData['type'] == 'table'">
<div class="table-container" :class="{ activeDom: itemData['id'] == widgetJson['activeId'] }"
:data-id="itemData['id']" @click.stop="handleSelectDom(itemData['id'])">
<table border="1" cellpadding="10" cellspacing="0" class="w-full"> <table border="1" cellpadding="10" cellspacing="0" class="w-full">
<template v-for="(row, rowIndex) in itemData['rows']" :key="rowIndex"> <template v-for="(row, rowIndex) in itemData['subDomList']" :key="rowIndex">
<tr> <tr>
<RendererDom v-for="subData in row['cols']" :key="subData['id']" :itemData="subData" /> <RendererDom v-for="subData in row" :key="subData['id']" :itemData="subData" />
</tr> </tr>
</template> </template>
</table> </table>
<template v-if="itemData['id'] == widgetJson['activeId']">
<div class="activeDomWrapper">{{ widgetJson['activeDomName'] }}</div>
<div class="fnArea">
<div @click="handleFindLastDom" title="选中父组件">
<Icon icon="ion:return-up-back" />
</div>
<div @click="handleAddRow" title="插入新行">
<Icon icon="iconoir:arrow-separate-vertical" />
</div>
<div @click="handleAddCol" title="插入新列">
<Icon icon="iconoir:arrow-separate" />
</div>
<div @click="handleDelDom" title="移除组件">
<Icon icon="mi:delete" />
</div>
</div>
</template>
</div>
</template> </template>
<!-- 针对表格列 --> <!-- 针对表格列 -->
<template v-if="itemData['type'] == 'table-cell'"> <template v-if="itemData['type'] == 'table-cell'">
<td v-if="!itemData['merged']" :colspan="itemData['options']['colspan']" :rowspan="itemData['options']['rowspan']"> <td v-if="!itemData['merged']" :colspan="itemData['options']['colspan']" :rowspan="itemData['options']['rowspan']"
<RendererDom v-for="subData in itemData['widgetList']" :key="subData['id']" :itemData="subData" /> :data-id="itemData['id']" :class="{ activeDom: widgetJson['activeId'] == itemData['id'] }"
@click.stop="handleSelectDom(itemData['id'])">
<RendererDom v-for="subData in itemData['subDomList']" :key="subData['id']" :itemData="subData" />
</td> </td>
</template> </template>

@ -1,5 +1,7 @@
/* eslint-disable vue/multi-word-component-names */
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import { Icon } from '@iconify/vue'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import './assets/css/main.css' import './assets/css/main.css'
@ -8,6 +10,7 @@ import router from './router'
const app = createApp(App) const app = createApp(App)
app.use(ElementPlus) app.use(ElementPlus)
app.component('Icon', Icon)
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)

@ -2,7 +2,7 @@ export interface IOption {
// 元素唯一标识符 // 元素唯一标识符
name: string name: string
// 隐藏状态 // 隐藏状态
hidden: boolean hidden?: boolean
// 自定义类名 // 自定义类名
customClass?: string customClass?: string
// 标签名称 // 标签名称
@ -23,7 +23,7 @@ export type ICategory = 'container' | 'formItem'
export interface IWidget { export interface IWidget {
// 元素唯一标识符 // 元素唯一标识符
key?: number key?: string
// 元素id // 元素id
id?: string id?: string
// 类别 // 类别
@ -34,16 +34,23 @@ export interface IWidget {
displayName: string displayName: string
// 图标 // 图标
icon: string icon: string
// 子类
rows?: [{ cols: [IWidget] }]
// 属性 // 属性
options: IOption options: IOption
// 子结构 // 子结构
widgetList?: [] subDomList?: [IWidget] | [[IWidget]]
// 单元格是否被合并 // 单元格是否被合并
merged?: boolean merged?: boolean
// X轴坐标
axisX?: number
// Y轴坐标
axisY?: number
} }
export interface IWidgetJson { export interface IWidgetJson {
widgetList: IWidget[] // 页面结构
subDomList: IWidget[]
// 当前选中元素ID
activeId: string
// 当前选中元素名称
activeDomName: string
} }

@ -1,16 +1,76 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import melonJson from "./melon.json"; // import melonjson from "./melon.json";
/** /**
* *
* @param widgetJson * @param widgetJson
*/ */
export const initJson = (widgetJson: any) => { export const initJson = (widgetJson: any) => {
widgetJson['widgetList'] = [] widgetJson['subDomList'] = []
widgetJson['activeId'] = ""
widgetJson['activeDomName'] = ""
// widgetJson['formConfig'] = {} // widgetJson['formConfig'] = {}
} }
export const addItem = (fatherId = "", widgetJson: any) => { /**
*
* @param addItemDataOrigin
* @param fatherId id
* @param widgetJson wJSON
*/
export const addItem = (addItemDataOrigin: any, fatherId = "", widgetJson: any) => {
const { addItemData, activeId, activeDomName } = formatAddItem(addItemDataOrigin)
if (fatherId) { } else { if (fatherId) { } else {
widgetJson['widgetList'] = [melonJson] widgetJson['subDomList'] = [addItemData]
}
widgetJson['activeId'] = activeId
widgetJson['activeDomName'] = activeDomName
}
/**
*
* @param addItemDataOrigin
*/
const formatAddItem = (addItemDataOrigin: any) => {
const addItemData = JSON.parse(JSON.stringify(addItemDataOrigin));
let activeId = '';
let activeDomName = '';
addItemData['key'] = Date.now().toString().slice(-6);
addItemData['id'] = `${addItemData['type']}-${addItemData['key']}`;
activeId = addItemData['id']
activeDomName = addItemData['displayName']
addItemData['options'] = {
name: `table-${addItemData['key']}`,
hidden: false,
customClass: ''
};
// 针对表格 需要初始化子单元格
if (addItemData['type'] == 'table') {
const key = addItemData['key'].slice(-1) + addItemData['key'].slice(0, -1);
// 子单元格
const colItem = {
key: key,
id: `table-cell-${key}`,
category: "container",
type: "table-cell",
displayName: "子单元格",
icon: "Monitor",
merged: false,
subDomList: [],
options: {
name: `table-cell-${key}`,
hidden: false,
colspan: 1,
rowspan: 1,
customClass: ''
}
};
addItemData['subDomList'] = [[colItem]]
// activeId = colItem['id']
// activeDomName = colItem['displayName']
}
return {
addItemData,
activeId,
activeDomName
} }
} }

@ -5,9 +5,8 @@
"type": "table", "type": "table",
"displayName": "表格", "displayName": "表格",
"icon": "Monitor", "icon": "Monitor",
"rows": [ "subDomList": [
{ [
"cols": [
{ {
"key": 47005, "key": 47005,
"id": "table-cell-12332", "id": "table-cell-12332",
@ -16,7 +15,7 @@
"displayName": "子单元格", "displayName": "子单元格",
"icon": "Monitor", "icon": "Monitor",
"merged": false, "merged": false,
"widgetList": [ "subDomList": [
{ {
"key": 47016, "key": 47016,
"id": "input-12355", "id": "input-12355",
@ -30,7 +29,6 @@
"keyName": "", "keyName": "",
"label": "名称", "label": "名称",
"labelAlign": "", "labelAlign": "",
"type": "text",
"defaultValue": "", "defaultValue": "",
"hidden": false, "hidden": false,
"customClass": "", "customClass": "",
@ -38,7 +36,6 @@
} }
} }
], ],
"rows": [],
"options": { "options": {
"name": "table-cell-12332", "name": "table-cell-12332",
"cellWidth": "", "cellWidth": "",
@ -57,7 +54,7 @@
"displayName": "子单元格", "displayName": "子单元格",
"icon": "Monitor", "icon": "Monitor",
"merged": false, "merged": false,
"widgetList": [ "subDomList": [
{ {
"key": 47016, "key": 47016,
"id": "input-12366", "id": "input-12366",
@ -71,14 +68,12 @@
"keyName": "", "keyName": "",
"label": "年龄", "label": "年龄",
"labelAlign": "", "labelAlign": "",
"type": "number",
"defaultValue": "", "defaultValue": "",
"hidden": false, "hidden": false,
"customClass": "" "customClass": ""
} }
} }
], ],
"rows": [],
"options": { "options": {
"name": "table-cell-12344", "name": "table-cell-12344",
"cellWidth": "", "cellWidth": "",
@ -89,10 +84,8 @@
"customClass": "" "customClass": ""
} }
} }
] ],
}, [
{
"cols": [
{ {
"key": 47015, "key": 47015,
"id": "table-cell-12344", "id": "table-cell-12344",
@ -101,7 +94,7 @@
"displayName": "子单元格", "displayName": "子单元格",
"icon": "Monitor", "icon": "Monitor",
"merged": false, "merged": false,
"widgetList": [ "subDomList": [
{ {
"key": 47016, "key": 47016,
"id": "radio-12377", "id": "radio-12377",
@ -115,7 +108,6 @@
"keyName": "", "keyName": "",
"label": "性别", "label": "性别",
"labelAlign": "", "labelAlign": "",
"type": "text",
"defaultValue": "1", "defaultValue": "1",
"hidden": false, "hidden": false,
"customClass": "", "customClass": "",
@ -132,7 +124,6 @@
} }
} }
], ],
"rows": [],
"options": { "options": {
"name": "table-cell-12344", "name": "table-cell-12344",
"cellWidth": "", "cellWidth": "",
@ -152,7 +143,6 @@
"icon": "Monitor", "icon": "Monitor",
"merged": true, "merged": true,
"widgetList": [], "widgetList": [],
"rows": [],
"options": { "options": {
"name": "table-cell-12399", "name": "table-cell-12399",
"cellWidth": "", "cellWidth": "",
@ -164,7 +154,6 @@
} }
} }
] ]
}
], ],
"options": { "options": {
"name": "", "name": "",

@ -3,23 +3,17 @@ import type { IWidget } from "@/types/widgetsConfigInterface"
export const containers: IWidget[] = [ export const containers: IWidget[] = [
{ {
category: 'container', category: 'container',
type: 'el-table', type: 'table',
displayName: '表格', displayName: '表格',
icon: 'Monitor', icon: 'Monitor',
options: { options: { name: '' }
name: '',
hidden: false,
}
}, },
{ {
category: 'container', category: 'container',
type: 'el-table-column', type: 'table-cell',
displayName: '子单元格', displayName: '子单元格',
icon: 'Monitor', icon: 'Monitor',
options: { options: { name: '' }
name: '',
hidden: false,
}
} }
] ]
@ -30,29 +24,20 @@ export const basicFields: IWidget[] = [
type: 'el-input', type: 'el-input',
displayName: '输入框', displayName: '输入框',
icon: 'Folder', icon: 'Folder',
options: { options: { name: '' }
name: '',
hidden: false,
},
}, },
{ {
category: 'formItem', category: 'formItem',
type: 'el-input-number', type: 'el-input-number',
displayName: '数值输入框', displayName: '数值输入框',
icon: 'Folder', icon: 'Folder',
options: { options: { name: '' }
name: '',
hidden: false,
},
}, },
{ {
category: 'formItem', category: 'formItem',
type: 'el-redio', type: 'el-redio',
displayName: ' 单选项', displayName: ' 单选项',
icon: 'Folder', icon: 'Folder',
options: { options: { name: '' }
name: '',
hidden: false,
},
}, },
] ]

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { provide, ref } from 'vue' import { provide, reactive } from 'vue'
import { initJson } from '@/utils' import { initJson } from '@/utils'
import LeftSide from './subCom/LeftSide.vue' import LeftSide from './subCom/LeftSide.vue'
import NavBar from './subCom/NavBar.vue' import NavBar from './subCom/NavBar.vue'
import MainWrapper from './subCom/MainWrapper.vue' import MainWrapper from './subCom/MainWrapper.vue'
import RightSide from './subCom/RightSide.vue' import RightSide from './subCom/RightSide.vue'
const widgetJson = ref({}) const widgetJson = reactive({})
initJson(widgetJson['value']) initJson(widgetJson)
provide('widgetJson', widgetJson) provide('widgetJson', widgetJson)
</script> </script>

@ -35,9 +35,7 @@ const widgetJson = inject('widgetJson')
<el-collapse-item :title="group['display']" :name="group['activeName']"> <el-collapse-item :title="group['display']" :name="group['activeName']">
<div class="flex flex-wrap justify-between content-center px-2"> <div class="flex flex-wrap justify-between content-center px-2">
<template v-for="item in group['subItem']" :key="item['displayName']"> <template v-for="item in group['subItem']" :key="item['displayName']">
<div <div class="itemBox" @click="addItem(item, '', widgetJson)">
class="basis-2/5 flex justify-between items-center border border-solid border-[#c1c1c1] px-2 mb-4"
@click="addItem('', widgetJson)">
<UseIcon :iconName="item['icon']"></UseIcon> <UseIcon :iconName="item['icon']"></UseIcon>
<p>{{ item['displayName'] }}</p> <p>{{ item['displayName'] }}</p>
</div> </div>
@ -60,5 +58,26 @@ section {
padding-left: 8px; padding-left: 8px;
} }
} }
.itemBox {
flex-basis: 40%;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #c1c1c1;
padding: 0 8px;
margin-bottom: 16px;
cursor: pointer;
&:hover {
border-color: var(--el-color-primary);
background-color: #F1F2F3;
:deep(.el-icon) {
color: var(--el-color-primary);
}
}
}
} }
</style> </style>

@ -13,12 +13,69 @@ provide('docForm', docForm)
</script> </script>
<template> <template>
<section> <section class="editWrapper">
<h1 class="text-sky-300">{{ widgetJson }}</h1> <h1 class="text-sky-300">{{ widgetJson }}</h1>
<el-form :model="docForm" label-width="80px"> <el-form :model="docForm" label-width="80px">
<template v-for="item in widgetJson['widgetList']" :key="item['id']"> <template v-for="item in widgetJson['subDomList']" :key="item['id']">
<RendererDom :itemData="item" /> <RendererDom :itemData="item" />
</template> </template>
</el-form> </el-form>
</section> </section>
</template> </template>
<style lang="scss" scoped>
.editWrapper {
--active-color: #409EFF;
:deep(.table-container) {
box-sizing: border-box;
padding: 8px;
border: 1px dashed var(--el-color-primary);
table {
border: none;
td {
border: 1px dashed var(--el-color-primary);
}
}
&.activeDom,
.activeDom {
outline: 2px solid var(--active-color);
padding: 16px;
position: relative;
}
&.activeDomWrapper,
.activeDomWrapper {
position: absolute;
top: 0;
left: 0;
padding: 0px 12px;
background-color: var(--active-color);
color: #fff;
font-size: 13px;
}
&.fnArea,
.fnArea {
position: absolute;
bottom: 0;
right: 0;
padding: 2px 4px;
background-color: var(--active-color);
color: #fff;
font-size: 18px;
display: flex;
align-items: center;
div {
margin: 0 4px;
cursor: pointer;
}
}
}
}
</style>

Loading…
Cancel
Save