mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
refactor: dashboard rich text
This commit is contained in:
Vendored
+8
@@ -13,6 +13,8 @@ declare module '@vue/runtime-core' {
|
||||
ElAvatar: typeof import('element-plus-secondary/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus-secondary/es')['ElButton']
|
||||
ElCheckbox: typeof import('element-plus-secondary/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus-secondary/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus-secondary/es')['ElCol']
|
||||
ElContainer: typeof import('element-plus-secondary/es')['ElContainer']
|
||||
ElDialog: typeof import('element-plus-secondary/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus-secondary/es')['ElDivider']
|
||||
@@ -30,15 +32,21 @@ declare module '@vue/runtime-core' {
|
||||
ElMenuItem: typeof import('element-plus-secondary/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus-secondary/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus-secondary/es')['ElPagination']
|
||||
ElRadio: typeof import('element-plus-secondary/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus-secondary/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus-secondary/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus-secondary/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus-secondary/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus-secondary/es')['ElSlider']
|
||||
ElStep: typeof import('element-plus-secondary/es')['ElStep']
|
||||
ElSteps: typeof import('element-plus-secondary/es')['ElSteps']
|
||||
ElSwitch: typeof import('element-plus-secondary/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus-secondary/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus-secondary/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus-secondary/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus-secondary/es')['ElTabs']
|
||||
ElTooltip: typeof import('element-plus-secondary/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus-secondary/es')['ElUpload']
|
||||
Icon: typeof import('./src/components/icon-custom/src/Icon.vue')['default']
|
||||
Layout: typeof import('./src/components/layout/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
||||
@@ -7,6 +7,7 @@ export const dashboardStore = defineStore('dashboard', {
|
||||
tabCollisionActiveId: null,
|
||||
tabMoveInActiveId: null,
|
||||
curComponent: null,
|
||||
curComponentId: null,
|
||||
canvasStyle: {},
|
||||
componentData: [],
|
||||
dashboardInfo: {
|
||||
@@ -29,6 +30,7 @@ export const dashboardStore = defineStore('dashboard', {
|
||||
actions: {
|
||||
setCurComponent(value: any) {
|
||||
this.curComponent = value
|
||||
this.curComponentId = value && value.id ? value.id : null
|
||||
},
|
||||
setComponentData(value: any) {
|
||||
this.componentData = value
|
||||
|
||||
@@ -5,10 +5,12 @@ import {dashboardStoreWithOut} from '@/stores/dashboard/dashboard'
|
||||
import {type CanvasCoord, type CanvasItem} from '@/utils/canvas.ts'
|
||||
import CanvasShape from './CanvasShape.vue'
|
||||
import {findComponent} from "@/views/dashboard/components/component-list.ts";
|
||||
import {storeToRefs} from "pinia";
|
||||
|
||||
const dashboardStore = dashboardStoreWithOut()
|
||||
const canvasLocked = ref(false) // Is the canvas movement locked, Default false
|
||||
const emits = defineEmits(["parentAddItemBox"]);
|
||||
const {curComponentId, curComponent} = storeToRefs(dashboardStore)
|
||||
// @ts-ignore
|
||||
let currentInstance
|
||||
// Props
|
||||
@@ -411,7 +413,7 @@ function getNextDragId() {
|
||||
if (validIds.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
//@ts-ignore
|
||||
//@ts-ignore
|
||||
const maxDragId = Math.max(...validIds);
|
||||
return maxDragId + 1;
|
||||
}
|
||||
@@ -420,7 +422,7 @@ function addItem(item: CanvasItem, index: any) {
|
||||
if (index < 0) {
|
||||
index = canvasComponentData.value.length
|
||||
}
|
||||
item._dragId = getNextDragId()
|
||||
item._dragId = item.id || getNextDragId()
|
||||
checkItemPosition(item, {x: item.x, y: item.y})
|
||||
emptyTargetCell(item)
|
||||
addItemToPositionBox(item)
|
||||
@@ -702,15 +704,23 @@ function startResize(e: MouseEvent, point: string, item: CanvasItem, index: numb
|
||||
infoBox.value.point = point
|
||||
}
|
||||
|
||||
function containerClick() {
|
||||
// remove current component info
|
||||
dashboardStore.setCurComponent(null)
|
||||
}
|
||||
|
||||
function containerMouseDown(e: MouseEvent) {
|
||||
// e.preventDefault();
|
||||
if (!infoBox.value) {
|
||||
infoBox.value = {} // Reinitialize
|
||||
}
|
||||
infoBox.value.startX = e.pageX
|
||||
infoBox.value.startY = e.pageY
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
// @ts-ignore
|
||||
if (curComponent.value?.component !== 'SQText') {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getNowPosition(addSizeX: number, addSizeY: number, moveXSize: number, moveYSize: number) {
|
||||
@@ -959,6 +969,7 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
|
||||
window.removeEventListener('mouseup', itemMouseUp)
|
||||
}
|
||||
|
||||
// This will prevent click events from being passed to the parent level
|
||||
window.addEventListener('mouseup', itemMouseUp)
|
||||
}
|
||||
|
||||
@@ -1147,10 +1158,12 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dragAndResize" ref="containerRef" @mousedown="containerMouseDown($event)" @mouseup="endMove()"
|
||||
<div class="dragAndResize" ref="containerRef" @click="containerClick" @mousedown="containerMouseDown($event)"
|
||||
@mouseup="endMove()"
|
||||
@mousemove="moving()">
|
||||
<template v-if="renderOk">
|
||||
<CanvasShape v-for="(item, index) in canvasComponentData"
|
||||
:active="curComponentId === item.id"
|
||||
:config-item="item"
|
||||
:draggable="draggable"
|
||||
:item-index="index"
|
||||
|
||||
@@ -10,6 +10,10 @@ const props = defineProps({
|
||||
type: Object as PropType<CanvasItem>,
|
||||
required: true
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
itemIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
@@ -37,18 +41,23 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const { draggable } = toRefs(props)
|
||||
const shapeClick = (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{
|
||||
item: true,
|
||||
itemActive: active,
|
||||
itemCursorDefault: configItem.component === 'SQTab',
|
||||
moveAnimation: moveAnimate,
|
||||
movingItem: configItem.isPlayer,
|
||||
canNotDrag: !draggable
|
||||
}" @mousedown="startMove($event, configItem, itemIndex)" ref="shapeRef">
|
||||
}" @click="shapeClick" @mousedown="startMove($event, configItem, itemIndex)" ref="shapeRef">
|
||||
<slot></slot>
|
||||
<resize-handle
|
||||
<resize-handle v-if="active"
|
||||
:start-resize="(event : MouseEvent, point: string) => startResize(event, point, configItem, itemIndex)">
|
||||
</resize-handle>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ const COMPONENT_LIST = [
|
||||
icon: 'icon_graphical',
|
||||
innerType: 'bar',
|
||||
locked: false,
|
||||
editing: false,
|
||||
x: 1,
|
||||
y: 1,
|
||||
sizeX: 36,
|
||||
@@ -36,6 +37,7 @@ const COMPONENT_LIST = [
|
||||
}
|
||||
],
|
||||
canvasActive: false,
|
||||
editing: false,
|
||||
x: 1,
|
||||
y: 1,
|
||||
sizeX: 36,
|
||||
@@ -47,7 +49,8 @@ const COMPONENT_LIST = [
|
||||
component: 'SQText',
|
||||
name: 'new text',
|
||||
locked: false,
|
||||
propValue: ' ',
|
||||
propValue: 'Double click to edit text',
|
||||
editing: false,
|
||||
x: 1,
|
||||
y: 1,
|
||||
sizeX: 36,
|
||||
|
||||
@@ -220,6 +220,7 @@ defineExpose({
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 2px!important;// border size
|
||||
}
|
||||
|
||||
.tab-dashboard-editor-main{
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<editor v-model="myValue" :init="init" :id="tinymceId"></editor>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import tinymce from 'tinymce/tinymce'
|
||||
import Editor from '@tinymce/tinymce-vue'
|
||||
import 'tinymce/themes/silver'
|
||||
import 'tinymce/themes/silver/theme'
|
||||
import 'tinymce/icons/default'
|
||||
|
||||
import 'tinymce/icons/default/icons'
|
||||
import 'tinymce/plugins/table'
|
||||
import 'tinymce/plugins/lists'
|
||||
import 'tinymce/plugins/wordcount'
|
||||
import 'tinymce/plugins/code'
|
||||
import '@npkg/tinymce-plugins/letterspacing'
|
||||
|
||||
import {reactive, ref} from 'vue'
|
||||
import {onMounted} from 'vue'
|
||||
defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: () => {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
baseUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
plugins: {
|
||||
type: [String, Array],
|
||||
default: 'lists table'
|
||||
}
|
||||
})
|
||||
const myValue = ref('this is a rich text')
|
||||
const tinymceId = ref('vue-tinymce-1')
|
||||
const init = reactive({
|
||||
selector: '#vue-tinymce-1',
|
||||
language_url: '/tinymce-sqlbot-private/langs/zh_CN.js',
|
||||
language: 'zh_CN',
|
||||
skin_url: '/tinymce-sqlbot-private/skins/ui/oxide',
|
||||
content_css: '/tinymce-sqlbot-private/skins/content/default/content.css',
|
||||
toolbar: false,
|
||||
menubar: false,
|
||||
inline: true,
|
||||
base_url: '/node_modules/tinymce',
|
||||
plugins: ['link', 'table'],
|
||||
quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
|
||||
})
|
||||
onMounted(() => {
|
||||
tinymce.init({})
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="less"></style>
|
||||
@@ -1,11 +1,177 @@
|
||||
<script setup lang="ts">
|
||||
import SQRichText from "@/views/dashboard/components/sq-text/SQRichText.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SQRichText></SQRichText>
|
||||
<div
|
||||
class="rich-main-class"
|
||||
:class="{ 'edit-model': configItem.editing }"
|
||||
@keydown.stop
|
||||
@keyup.stop
|
||||
@dblclick="setEdit"
|
||||
>
|
||||
<editor :class="{ 'custom-text-content':true,'dragHandle':isDisabled}"
|
||||
v-model="configItem.propValue"
|
||||
:disabled="isDisabled" :init="init"
|
||||
:id="tinymceId"></editor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
<script setup lang="ts">
|
||||
import tinymce from 'tinymce/tinymce'
|
||||
import Editor from '@tinymce/tinymce-vue'
|
||||
import 'tinymce/themes/silver'
|
||||
import 'tinymce/themes/silver/theme'
|
||||
import 'tinymce/icons/default'
|
||||
|
||||
</style>
|
||||
import 'tinymce/plugins/advlist'
|
||||
import 'tinymce/plugins/autolink'
|
||||
import 'tinymce/plugins/link'
|
||||
import 'tinymce/plugins/image'
|
||||
import 'tinymce/plugins/lists'
|
||||
import 'tinymce/plugins/charmap'
|
||||
import 'tinymce/plugins/media'
|
||||
import 'tinymce/plugins/wordcount'
|
||||
import 'tinymce/plugins/contextmenu'
|
||||
import 'tinymce/plugins/directionality'
|
||||
import 'tinymce/plugins/nonbreaking'
|
||||
import 'tinymce/plugins/pagebreak'
|
||||
import '@npkg/tinymce-plugins/letterspacing'
|
||||
|
||||
import {computed, nextTick, type PropType, reactive, toRefs} from 'vue'
|
||||
import {onMounted} from 'vue'
|
||||
import type {CanvasItem} from "@/utils/canvas.ts";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
configItem: {
|
||||
type: Object as PropType<CanvasItem>,
|
||||
required: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const {configItem} = toRefs(props)
|
||||
const tinymceId = 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
const init = reactive({
|
||||
selector: tinymceId,
|
||||
language_url: '/tinymce-sqlbot-private/langs/zh_CN.js',
|
||||
language: 'zh_CN',
|
||||
skin_url: '/tinymce-sqlbot-private/skins/ui/oxide',
|
||||
content_css: '/tinymce-sqlbot-private/skins/content/default/content.css',
|
||||
plugins:
|
||||
'advlist autolink link image lists charmap media wordcount contextmenu directionality pagebreak letterspacing', // 插件
|
||||
// 工具栏
|
||||
toolbar:
|
||||
'undo redo | fontselect fontsizeselect |forecolor backcolor bold italic letterspacing |underline strikethrough link lineheight| formatselect |' +
|
||||
'top-align center-align bottom-align | alignleft aligncenter alignright | bullist numlist |' +
|
||||
' blockquote subscript superscript removeformat | image ',
|
||||
toolbar_location: '/',
|
||||
font_formats:
|
||||
'微软雅黑=Microsoft YaHei;宋体=SimSun;黑体=SimHei;仿宋=FangSong;华文黑体=STHeiti;华文楷体=STKaiti;华文宋体=STSong;华文仿宋=STFangsong;Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings',
|
||||
fontsize_formats:
|
||||
'12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 42px 48px 56px 72px 80px 90px 100px 110px 120px 140px 150px 170px 190px 210px', // 字体大小
|
||||
menubar: false,
|
||||
placeholder: '',
|
||||
inline: true,
|
||||
setup: function (editor : any) {
|
||||
editor.on('blur', function () {
|
||||
configItem.value.editing = false
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const isDisabled = computed(() => props.disabled || !configItem.value.editing)
|
||||
|
||||
const setEdit = () => {
|
||||
configItem.value.editing = true
|
||||
// @ts-ignore
|
||||
const ed = tinymce.editors[tinymceId]
|
||||
ed.setContent(configItem.value.propValue)
|
||||
nextTick(() => {
|
||||
editCursor()
|
||||
})
|
||||
}
|
||||
|
||||
const editCursor = () => {
|
||||
setTimeout(() => {
|
||||
const myDiv = document.getElementById(tinymceId)
|
||||
// Focus the cursor on the end of the text
|
||||
const range = document.createRange()
|
||||
const sel = window.getSelection()
|
||||
if (myDiv && myDiv.childNodes) {
|
||||
range.setStart(myDiv.childNodes[myDiv.childNodes.length - 1], 1)
|
||||
range.collapse(false)
|
||||
if (sel) {
|
||||
sel.removeAllRanges()
|
||||
sel.addRange(range)
|
||||
}
|
||||
}
|
||||
// For some browsers, it may be necessary to set the cursor to the end in another way
|
||||
if (myDiv && myDiv.focus) {
|
||||
myDiv.focus()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tinymce.init({})
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.edit-model {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.rich-main-class {
|
||||
display: flex;
|
||||
font-size: initial;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto !important;
|
||||
position: relative;
|
||||
|
||||
div::-webkit-scrollbar {
|
||||
width: 0px !important;
|
||||
height: 0px !important;
|
||||
}
|
||||
|
||||
:deep(.ol) {
|
||||
display: block !important;
|
||||
list-style-type: decimal;
|
||||
margin-block-start: 1em !important;
|
||||
margin-block-end: 1em !important;
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: 0px !important;
|
||||
padding-inline-start: 40px !important;
|
||||
}
|
||||
|
||||
:deep(ul) {
|
||||
display: block !important;
|
||||
list-style-type: disc;
|
||||
margin-block-start: 1em !important;
|
||||
margin-block-end: 1em !important;
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: 0px !important;
|
||||
padding-inline-start: 40px !important;
|
||||
}
|
||||
|
||||
:deep(li) {
|
||||
margin-left: 20px;
|
||||
display: list-item !important;
|
||||
text-align: -webkit-match-parent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-text-content {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
margin: 2px;
|
||||
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -10,19 +10,23 @@
|
||||
}
|
||||
|
||||
.itemCursorDefault {
|
||||
cursor: default!important;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.itemActive {
|
||||
outline: 1px solid #59c7f9;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid #59c7f9;
|
||||
background-color: #fff;
|
||||
cursor: move;
|
||||
.resizeHandle {
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
border: 1px solid #59c7f9;
|
||||
outline: 1px solid #59c7f9;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -114,7 +114,7 @@ const emits = defineEmits(["parentAddItemBox"]);
|
||||
}
|
||||
|
||||
.move-in-active {
|
||||
border: 2px dotted transparent;
|
||||
border-color: blueviolet;
|
||||
border: 2px dotted blueviolet;
|
||||
margin: -2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user