Compare commits

...

4 Commits

1
.gitignore vendored

@ -20,6 +20,7 @@ coverage
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea
*.suo
*.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",
"version": "0.0.0",
"dependencies": {
"@iconify/vue": "^4.3.0",
"axios": "^1.8.4",
"element-plus": "^2.9.7",
"pinia": "^3.0.1",
@ -1337,6 +1338,25 @@
"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": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",

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

@ -1,15 +1,22 @@
<!-- eslint-disable @typescript-eslint/no-explicit-any -->
<script setup lang="ts">
import { inject, defineProps } from 'vue'
import type { IWidget } from '@/types/widgetsConfigInterface'
import { inject } from 'vue'
import { addTableRow, addTableCol, mergeRightCol, mergeRow, cancelMergeCol, } from "@/utils/table";
import { handleFindDomById } from "@/utils/index";
import type { IWidget, IWidgetJson } from '@/types/widgetsConfigInterface'
import RendererDom from '@/components/RendererDom.vue'
const props = defineProps<{
itemData: IWidget
itemData: IWidget,
rowIndex: number,
colIndex: number,
colLength: number
}>()
//
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const docForm = inject('docForm') as any
const widgetJson = inject('widgetJson') as IWidgetJson
//
if (props['itemData']['category'] == 'formItem') {
@ -19,24 +26,135 @@ if (props['itemData']['category'] == 'formItem') {
docForm[props['itemData']['options']['name']] = props['itemData']['options']['defaultValue'] || 0
}
}
//
const handleSelectDom = (activeId: any) => {
const fatherDom = handleFindDomById(widgetJson['subDomList'], activeId, false)
if (fatherDom) {
widgetJson['activeConfig']['activeId'] = fatherDom['id']
widgetJson['activeConfig']['activeDisplayName'] = fatherDom['displayName']
} else {
widgetJson['activeConfig']['activeId'] = ""
widgetJson['activeConfig']['activeDisplayName'] = ""
}
}
//
const handleFindLastDom = () => {
const fatherDom = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], true)
if (fatherDom) {
widgetJson['activeConfig']['activeId'] = fatherDom['id']
widgetJson['activeConfig']['activeDisplayName'] = fatherDom['displayName']
} else {
widgetJson['activeConfig']['activeId'] = ""
widgetJson['activeConfig']['activeDisplayName'] = ""
}
}
// ID
const handleDelDom = (node: any = widgetJson) => {
if (!node || !Array.isArray(node.subDomList)) return;
const deleteFromList = (list: any) => {
for (let i = list.length - 1; i >= 0; i--) {
const item = list[i];
if (Array.isArray(item)) {
//
deleteFromList(item);
//
if (item.length === 0) {
list.splice(i, 1);
}
} else if (item && item.id === widgetJson['activeConfig']['activeId']) {
//
list.splice(i, 1);
} else if (item && item.subDomList) {
//
handleDelDom(item);
}
}
};
deleteFromList(node.subDomList);
}
</script>
<template>
<!-- 针对表格容器 -->
<template v-if="itemData['type'] == 'table'">
<table border="1" cellpadding="10" cellspacing="0" class="w-full">
<template v-for="(row, rowIndex) in itemData['rows']" :key="rowIndex">
<tr>
<RendererDom v-for="subData in row['cols']" :key="subData['id']" :itemData="subData" />
</tr>
<div class="table-container" :class="{ activeDom: itemData['id'] == widgetJson['activeConfig']['activeId'] }"
:data-id="itemData['id']" @click.stop="handleSelectDom(itemData['id'])">
<table border="1" cellpadding="10" cellspacing="0" class="w-full">
<template v-for="(row, rowIndex) in itemData['subDomList']" :key="rowIndex">
<tr>
<RendererDom v-for="(subData, index) in row" :key="subData['id']" :itemData="subData" :rowIndex="rowIndex"
:colIndex="index" :colLength="Array.isArray(row) ? row['length'] : 1" />
</tr>
</template>
</table>
<template v-if="itemData['id'] == widgetJson['activeConfig']['activeId']">
<div class="activeDomWrapper">{{ widgetJson['activeConfig']['activeDisplayName'] }}</div>
<div class="fnArea">
<div @click.stop="handleFindLastDom" title="选中父组件">
<Icon icon="ion:return-up-back" />
</div>
<div @click.stop="addTableRow(widgetJson, rowIndex, true, false, 'table-cell')" title="插入新行">
<Icon icon="iconoir:arrow-separate-vertical" />
</div>
<div @click.stop="addTableCol(widgetJson, colIndex, true, false, 'table-cell')" title="插入新列">
<Icon icon="iconoir:arrow-separate" />
</div>
<div @click.stop="() => { debugger; handleDelDom() }" title="移除组件">
<Icon icon="mi:delete" />
</div>
</div>
</template>
</table>
</div>
</template>
<!-- 针对表格列 -->
<template v-if="itemData['type'] == 'table-cell'">
<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" />
<td v-if="!itemData['options']['merged']" :colspan="itemData['options']['colspan']"
:rowspan="itemData['options']['rowspan']" :data-id="itemData['id']"
:class="{ activeDom: widgetJson['activeConfig']['activeId'] == itemData['id'] }"
@click.stop="handleSelectDom(itemData['id'])">
<RendererDom v-for="(subData, index) in itemData['subDomList'] as IWidget[]" :key="subData['id']"
:itemData="subData" :rowIndex="rowIndex" :colIndex="index" :colLength="1" />
<template v-if="itemData['id'] == widgetJson['activeConfig']['activeId']">
<div class="activeDomWrapper">{{ widgetJson['activeConfig']['activeDisplayName'] }}</div>
<div class="fnArea">
<div @click.stop="handleFindLastDom" title="选中父组件">
<Icon icon="ion:return-up-back" />
</div>
<div class="moreBox">
<Icon icon="hugeicons:menu-square" />
<div class="actionBox">
<ul>
<li @click.stop="addTableCol(widgetJson, colIndex, false, true, 'table-cell')">插入左侧列</li>
<li @click.stop="addTableCol(widgetJson, colIndex, true, true, 'table-cell')">插入右侧列</li>
<li @click.stop="addTableRow(widgetJson, rowIndex, false, true, 'table-cell')">插入上方行</li>
<li @click.stop="addTableRow(widgetJson, rowIndex, true, true, 'table-cell')">插入下方行</li>
<el-divider />
<li :class="{ readOnly: colIndex == colLength - 1 || itemData['options']['colspan'] == colLength }"
@click="mergeRightCol(widgetJson, rowIndex, colIndex)">合并右侧单元格</li>
<li :class="{ readOnly: colLength == 1 || colIndex > 0 || itemData['options']['colspan'] == colLength }"
@click="mergeRow(widgetJson, rowIndex)">合并整行
</li>
<el-divider />
<li>合并下方单元格</li>
<li>合并整列</li>
<el-divider />
<li :class="{ readOnly: itemData['options']['colspan'] && itemData['options']['colspan'] == 1 }"
@click="cancelMergeCol(widgetJson, rowIndex, colIndex)">撤销行合并
</li>
<li>撤销列合并</li>
<el-divider />
<li>删除整列</li>
<li>删除整行</li>
</ul>
</div>
</div>
</div>
</template>
</td>
</template>

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

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

@ -1,16 +1,93 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import melonJson from "./melon.json";
import { containers, basicFields } from "@/utils/widgetsConfig";
import type { IWidget } from '@/types/widgetsConfigInterface'
/**
*
* @param widgetJson
*/
export const initJson = (widgetJson: any) => {
widgetJson['widgetList'] = []
// widgetJson['formConfig'] = {}
widgetJson['subDomList'] = []
widgetJson['activeConfig']['activeId'] = ""
widgetJson['activeConfig']['activeDisplayName'] = ""
}
export const addItem = (fatherId = "", widgetJson: any) => {
if (fatherId) { } else {
widgetJson['widgetList'] = [melonJson]
/**
*
* @param addItemDataOrigin
*/
export const formatAddItem = (addItemTypeName: string) => {
let addItemData = null;
let activeId = ""
let activeDisplayName = ""
const allWidgetList = [...containers, ...basicFields];
const targetItem = allWidgetList.filter(item => {
return item['type'] == addItemTypeName
})
if (targetItem['length']) {
addItemData = structuredClone(targetItem[0]);
addItemData['key'] = `${Date.now().toString().slice(-6)}${Math.floor(Math.random() * 900) + 100}`;
addItemData['id'] = `${addItemData['type']}-${addItemData['key']}`
addItemData['subDomList'] = [];
addItemData['options']['name'] = addItemData['id']
activeId = addItemData['id']
activeDisplayName = addItemData['displayName']
}
return {
addItemData,
activeId,
activeDisplayName
}
}
/**
*
* @param widgetJson JSON
* @param addItemTypeName
* @param fatherId id
*/
export const addItem = (widgetJson: any, addItemTypeName: any) => {
const { addItemData, activeId, activeDisplayName } = formatAddItem(addItemTypeName)
if (widgetJson['activeConfig']['activeId']) { } else {
widgetJson['subDomList'].push(addItemData)
}
widgetJson['activeConfig']['activeId'] = activeId
widgetJson['activeDisplayName'] = activeDisplayName
}
/**
* id
* @param subDomList widgetJson['subDomList']
* @param returnParent
*/
export const handleFindDomById = (subDomList: any, activeKey: string, returnParent = false) => {
let found: IWidget | null = null
function dfs(nodes: IWidget[], parent: IWidget | null): boolean {
for (const node of nodes) {
if (node['id'] === activeKey) {
found = returnParent ? parent?.category === 'container' ? parent : null : node
return true
}
// 针对 subDomList 为二维数组或一维数组都做兼容处理
const children = node.subDomList
if (Array.isArray(children)) {
if (Array.isArray(children[0])) {
// 二维数组情况
for (const row of children as IWidget[][]) {
if (dfs(row, node)) return true
}
} else {
// 一维数组情况
if (dfs(children as IWidget[], node)) return true
}
}
}
return false
}
dfs(subDomList, null)
return found
}

@ -5,166 +5,155 @@
"type": "table",
"displayName": "表格",
"icon": "Monitor",
"rows": [
{
"cols": [
{
"key": 47005,
"id": "table-cell-12332",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": false,
"widgetList": [
{
"key": 47016,
"id": "input-12355",
"category": "formItem",
"type": "el-input",
"displayName": "输入框",
"icon": "Folder",
"options": {
"name": "input-12355",
"keyNameEnabled": false,
"keyName": "",
"label": "名称",
"labelAlign": "",
"type": "text",
"defaultValue": "",
"hidden": false,
"customClass": "",
"placeholder": "请输入名称"
}
"subDomList": [
[
{
"key": 47005,
"id": "table-cell-12332",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": false,
"subDomList": [
{
"key": 47016,
"id": "input-12355",
"category": "formItem",
"type": "el-input",
"displayName": "输入框",
"icon": "Folder",
"options": {
"name": "input-12355",
"keyNameEnabled": false,
"keyName": "",
"label": "名称",
"labelAlign": "",
"defaultValue": "",
"hidden": false,
"customClass": "",
"placeholder": "请输入名称"
}
],
"rows": [],
"options": {
"name": "table-cell-12332",
"cellWidth": "",
"cellHeight": "",
"colspan": 1,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
},
{
"key": 47015,
"id": "table-cell-12344",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": false,
"widgetList": [
{
"key": 47016,
"id": "input-12366",
"category": "formItem",
"type": "el-input-number",
"displayName": "输入框",
"icon": "Folder",
"options": {
"name": "input-12366",
"keyNameEnabled": false,
"keyName": "",
"label": "年龄",
"labelAlign": "",
"type": "number",
"defaultValue": "",
"hidden": false,
"customClass": ""
}
],
"options": {
"name": "table-cell-12332",
"cellWidth": "",
"cellHeight": "",
"colspan": 1,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
},
{
"key": 47015,
"id": "table-cell-12344",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": false,
"subDomList": [
{
"key": 47016,
"id": "input-12366",
"category": "formItem",
"type": "el-input-number",
"displayName": "输入框",
"icon": "Folder",
"options": {
"name": "input-12366",
"keyNameEnabled": false,
"keyName": "",
"label": "年龄",
"labelAlign": "",
"defaultValue": "",
"hidden": false,
"customClass": ""
}
],
"rows": [],
"options": {
"name": "table-cell-12344",
"cellWidth": "",
"cellHeight": "",
"colspan": 1,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
],
"options": {
"name": "table-cell-12344",
"cellWidth": "",
"cellHeight": "",
"colspan": 1,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
]
},
{
"cols": [
{
"key": 47015,
"id": "table-cell-12344",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": false,
"widgetList": [
{
"key": 47016,
"id": "radio-12377",
"category": "formItem",
"type": "el-radio",
"displayName": "单选框",
"icon": "Folder",
"options": {
"name": "input-12377",
"keyNameEnabled": false,
"keyName": "",
"label": "性别",
"labelAlign": "",
"type": "text",
"defaultValue": "1",
"hidden": false,
"customClass": "",
"optionItems": [
{
"label": "男",
"value": "1"
},
{
"label": "女",
"value": 2
}
]
}
}
],
[
{
"key": 47015,
"id": "table-cell-12344",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": false,
"subDomList": [
{
"key": 47016,
"id": "radio-12377",
"category": "formItem",
"type": "el-radio",
"displayName": "单选框",
"icon": "Folder",
"options": {
"name": "input-12377",
"keyNameEnabled": false,
"keyName": "",
"label": "性别",
"labelAlign": "",
"defaultValue": "1",
"hidden": false,
"customClass": "",
"optionItems": [
{
"label": "男",
"value": "1"
},
{
"label": "女",
"value": 2
}
]
}
],
"rows": [],
"options": {
"name": "table-cell-12344",
"cellWidth": "",
"cellHeight": "",
"colspan": 2,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
},
{
"key": 47018,
"id": "table-cell-12399",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": true,
"widgetList": [],
"rows": [],
"options": {
"name": "table-cell-12399",
"cellWidth": "",
"cellHeight": "",
"colspan": 2,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
],
"options": {
"name": "table-cell-12344",
"cellWidth": "",
"cellHeight": "",
"colspan": 2,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
},
{
"key": 47018,
"id": "table-cell-12399",
"category": "container",
"type": "table-cell",
"displayName": "子单元格",
"icon": "Monitor",
"merged": true,
"widgetList": [],
"options": {
"name": "table-cell-12399",
"cellWidth": "",
"cellHeight": "",
"colspan": 2,
"rowspan": 1,
"wordBreak": false,
"customClass": ""
}
]
}
}
]
],
"options": {
"name": "",

@ -0,0 +1,180 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { formatAddItem, handleFindDomById } from "@/utils/index";
import type { IWidget, IWidgetJson } from '@/types/widgetsConfigInterface'
/**
*
* @param widgetJson
* @param addItemTypeName
* @param fatherId
*/
export const addTable = (widgetJson: IWidgetJson, addItemTypeName: any) => {
const tableCol = formatAddItem('table-cell');
const { addItemData, activeId, activeDisplayName } = formatAddItem(addItemTypeName);
if (addItemData && tableCol['addItemData']) {
addItemData['subDomList'] = [[tableCol['addItemData']]];
}
if (widgetJson['activeConfig']['activeId']) {
if (widgetJson['activeConfig']['activeId'].startsWith('table-cell')) {
// 单元格里面直接加
const tableJson = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], false);
if (tableJson && addItemData) {
(tableJson['subDomList'] as IWidget[]).push(addItemData)
}
} else if (widgetJson['activeConfig']['activeId'].startsWith('table')) {
// 表格后面加
if (addItemData) {
widgetJson['subDomList'].push(addItemData)
}
} else {
// 寻找上一层单元格加
const tableJson = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], true);
if (tableJson && addItemData) {
(tableJson['subDomList'] as IWidget[]).push(addItemData)
}
}
} else {
if (addItemData) {
widgetJson['subDomList'].push(addItemData)
}
}
widgetJson['activeConfig']['activeId'] = tableCol['activeId']
widgetJson['activeConfig']['activeDisplayName'] = tableCol['activeDisplayName']
// widgetJson['activeConfig']['activeId'] = activeId
// widgetJson['activeConfig']['activeDisplayName'] = activeDisplayName
}
/**
*
* @param widgetJson
* @param addItemTypeName
* @param fatherId
*/
export const addTableRow = (widgetJson: IWidgetJson, rowIndex: number, isPush: boolean, returnParent: boolean, addItemTypeName = 'table-cell') => {
const tableJson = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], returnParent);
if (tableJson) {
const newRow: IWidget[] = []
for (let index = 0; index < tableJson['subDomList'][0]['length']; index++) {
const { addItemData } = formatAddItem(addItemTypeName);
if (addItemData) {
newRow.push(addItemData);
}
}
if (isPush) {
(tableJson['subDomList'] as IWidget[][]).splice(rowIndex + 1, 0, newRow)
} else {
(tableJson['subDomList'] as IWidget[][]).splice(rowIndex, 0, newRow)
}
}
}
/**
*
* @param widgetJson
* @param colIndex
* @param isPush
* @param returnParent
* @param addItemTypeName
*/
export const addTableCol = (widgetJson: IWidgetJson, colIndex: number, isPush: boolean, returnParent: boolean, addItemTypeName = 'table-cell') => {
const tableJson = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], returnParent);
if (tableJson && tableJson['subDomList']) {
for (let index = 0; index < tableJson['subDomList']['length']; index++) {
const { addItemData } = formatAddItem(addItemTypeName);
if (addItemData) {
const row: IWidget[] = tableJson['subDomList'][index];
if (isPush) {
row.splice(colIndex + 1, 0, addItemData);
} else {
row.splice(colIndex, 0, addItemData);
}
}
}
}
}
/**
*
* @param widgetJson
* @param rowIndex
* @param colIndex
*/
export const mergeRightCol = (widgetJson: IWidgetJson, rowIndex: number, colIndex: number) => {
const tableJson = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], true);
if (tableJson && tableJson['subDomList']) {
let isStartmerge = false;
let mergeIndexArr: number[] = [];
let targetItem = { colspan: 0 };
const rowData = tableJson['subDomList'][rowIndex];
for (let index = 0; index < rowData['length']; index++) {
if (isStartmerge) {
if (mergeIndexArr.includes(index)) {
const nextItem = rowData[index]['options'] as any;
if (nextItem['merged']) {
targetItem['colspan'] = targetItem['colspan'] + nextItem['colspan']
}
nextItem['colspan'] = 1;
nextItem['merged'] = true;
}
}
if (colIndex == index) {
isStartmerge = true;
targetItem = rowData[index]['options'];
rowData[index]['options']['colspan']++;
mergeIndexArr = Array.from({ length: rowData[index]['options']['colspan'] }, (_, i) => index + i);
}
}
}
}
/**
*
* @param widgetJson
* @param rowIndex
*/
export const mergeRow = (widgetJson: IWidgetJson, rowIndex: number) => {
const tableJson = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], true);
if (tableJson && tableJson['subDomList']) {
const rowData = tableJson['subDomList'][rowIndex];
for (let index = 0; index < rowData['length']; index++) {
if (index == 0) {
rowData[index]['options']['colspan'] = rowData['length'];
} else {
const nextItem = rowData[index]['options'] as any;
nextItem['colspan'] = 1;
nextItem['merged'] = true;
}
}
}
}
/**
*
* @param widgetJson
* @param rowIndex
* @param colIndex
*/
export const cancelMergeCol = (widgetJson: IWidgetJson, rowIndex: number, colIndex: number) => {
const tableJson = handleFindDomById(widgetJson['subDomList'], widgetJson['activeConfig']['activeId'], true);
if (tableJson && tableJson['subDomList']) {
let isStartmerge = false;
let mergeIndexArr: number[] = [];
const rowData = tableJson['subDomList'][rowIndex];
for (let index = 0; index < rowData['length']; index++) {
if (isStartmerge) {
if (mergeIndexArr.includes(index)) {
(rowData[index]['options'] as any)['merged'] = false;
(rowData[index]['options'] as any)['colspan'] = 1;
}
}
if (colIndex == index) {
isStartmerge = true;
mergeIndexArr = Array.from({ length: rowData[index]['options']['colspan'] }, (_, i) => index + i);
(rowData[index]['options'] as any)['colspan'] = 1;
}
}
}
}

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

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

@ -1,7 +1,8 @@
<script setup lang="ts">
import { ref, inject } from 'vue'
import { containers, basicFields } from '@/utils/widgetsConfig'
import { addItem } from '@/utils'
import { addItem } from '@/utils/index'
import { addTable } from '@/utils/table'
import UseIcon from '@/components/UseIcon.vue'
//
@ -23,6 +24,15 @@ const collapseList = ref([
])
//
const widgetJson = inject('widgetJson')
//
const onAddItem = (typeName: string) => {
if (typeName == 'table') {
addTable(widgetJson, typeName)
} else {
addItem(widgetJson, typeName)
}
}
</script>
<template>
@ -35,9 +45,7 @@ const widgetJson = inject('widgetJson')
<el-collapse-item :title="group['display']" :name="group['activeName']">
<div class="flex flex-wrap justify-between content-center px-2">
<template v-for="item in group['subItem']" :key="item['displayName']">
<div
class="basis-2/5 flex justify-between items-center border border-solid border-[#c1c1c1] px-2 mb-4"
@click="addItem('', widgetJson)">
<div class="itemBox" @click="onAddItem(item['type'])">
<UseIcon :iconName="item['icon']"></UseIcon>
<p>{{ item['displayName'] }}</p>
</div>
@ -60,5 +68,26 @@ section {
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>

@ -13,12 +13,118 @@ provide('docForm', docForm)
</script>
<template>
<section>
<section class="editWrapper">
<h1 class="text-sky-300">{{ widgetJson }}</h1>
<el-form :model="docForm" label-width="80px">
<template v-for="item in widgetJson['widgetList']" :key="item['id']">
<RendererDom :itemData="item" />
<template v-for="(item, index) in widgetJson['subDomList']" :key="item['id']">
<RendererDom :itemData="item" :rowIndex="index" :colIndex="index" :colLength="1" />
</template>
</el-form>
</section>
</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);
position: relative;
.moreBox {
position: relative;
&:hover {
.actionBox {
display: block;
}
}
.actionBox {
// display: none;
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding-top: 12px;
z-index: 10;
ul {
list-style: none;
padding-left: 0;
background-color: #fff;
border: 1px solid #c1c1c1;
padding-bottom: 2px;
li {
list-style: none;
white-space: nowrap;
color: #666;
font-size: 12px;
padding: 4px 8px;
&:not(.readOnly):hover {
background-color: #c9effd;
color: var(--el-color-primary);
}
&.readOnly {
color: #c0c4cc;
cursor: not-allowed;
}
}
}
}
}
}
}
&.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