mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-12 11:19:56 +00:00
perf: 优化任务描述编辑器
This commit is contained in:
parent
ee708d1d1b
commit
a3c509da83
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="teditor-wrapper" @click="onClickWrap" @touchstart="onTouchstart">
|
<div class="teditor-wrapper">
|
||||||
<div class="teditor-box" :class="[!inline && spinShow ? 'teditor-loadstyle' : 'teditor-loadedstyle']">
|
<div class="teditor-box" :class="[!inline && spinShow ? 'teditor-loadstyle' : 'teditor-loadedstyle']">
|
||||||
<template v-if="inline">
|
<template v-if="inline">
|
||||||
<div ref="myTextarea" :id="id" v-html="spinShow ? '' : content"></div>
|
<div ref="myTextarea" :id="id" v-html="spinShow ? '' : content"></div>
|
||||||
@ -35,20 +35,6 @@
|
|||||||
:on-format-error="handleFormatError"
|
:on-format-error="handleFormatError"
|
||||||
:on-exceeded-size="handleMaxSize"
|
:on-exceeded-size="handleMaxSize"
|
||||||
:before-upload="handleBeforeUpload"/>
|
:before-upload="handleBeforeUpload"/>
|
||||||
<div class="teditor-operate" :style="operateStyles" v-show="operateVisible">
|
|
||||||
<Dropdown
|
|
||||||
trigger="custom"
|
|
||||||
:visible="operateVisible"
|
|
||||||
@on-clickoutside="operateVisible = false"
|
|
||||||
transfer>
|
|
||||||
<div :style="{userSelect:operateVisible ? 'none' : 'auto', height: operateStyles.height}"></div>
|
|
||||||
<DropdownMenu slot="list">
|
|
||||||
<DropdownItem @click.native="onFull">{{ editTitle || $L('编辑') }}</DropdownItem>
|
|
||||||
<DropdownItem v-if="operateLink" @click.native="onLinkPreview">{{ $L('打开链接') }}</DropdownItem>
|
|
||||||
<DropdownItem v-if="operateImg" @click.native="onImagePreview">{{ $L('查看图片') }}</DropdownItem>
|
|
||||||
</DropdownMenu>
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Spin fix v-if="uploadIng > 0">
|
<Spin fix v-if="uploadIng > 0">
|
||||||
<Icon type="ios-loading" class="icon-loading"></Icon>
|
<Icon type="ios-loading" class="icon-loading"></Icon>
|
||||||
@ -75,6 +61,8 @@
|
|||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import {languageType} from "../language";
|
import {languageType} from "../language";
|
||||||
|
|
||||||
|
const windowTouch = "ontouchend" in document
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TEditor',
|
name: 'TEditor',
|
||||||
components: {ImgUpload},
|
components: {ImgUpload},
|
||||||
@ -110,9 +98,25 @@
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
menubar: {
|
||||||
|
type: String,
|
||||||
|
default: () => {
|
||||||
|
if (windowTouch) {
|
||||||
|
return 'edit insert format tools';
|
||||||
|
} else {
|
||||||
|
return 'file edit view insert format tools table';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
toolbar: {
|
toolbar: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ' undo redo | styleselect | uploadImages | uploadFiles | bold italic underline forecolor backcolor | alignleft aligncenter alignright | bullist numlist outdent indent | link image emoticons media codesample | preview screenload',
|
default: () => {
|
||||||
|
if (windowTouch) {
|
||||||
|
return 'uploadImages | bold italic underline | forecolor backcolor | screenload';
|
||||||
|
} else {
|
||||||
|
return 'undo redo | styleselect | uploadImages | uploadFiles | bold italic underline forecolor backcolor | alignleft aligncenter alignright | bullist numlist outdent indent | link image emoticons media codesample | preview screenload';
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -132,7 +136,10 @@
|
|||||||
},
|
},
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false // windowTouch 为 true 时,非全屏模式下,readOnly 为 true
|
default: false
|
||||||
|
},
|
||||||
|
readOnlyFull: {
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
autoSize: {
|
autoSize: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -150,9 +157,6 @@
|
|||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
scrollHideOperateClassName: { // 触发隐藏操作菜单的滚动区域class
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -171,30 +175,14 @@
|
|||||||
actionUrl: $A.apiUrl('system/fileupload'),
|
actionUrl: $A.apiUrl('system/fileupload'),
|
||||||
maxSize: 10240,
|
maxSize: 10240,
|
||||||
|
|
||||||
operateStyles: {},
|
|
||||||
operateVisible: false,
|
|
||||||
operateLink: null,
|
|
||||||
operateImg: null,
|
operateImg: null,
|
||||||
|
|
||||||
timer: null,
|
timer: null,
|
||||||
listener: null,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.content = this.value;
|
this.content = this.value;
|
||||||
this.init();
|
this.init();
|
||||||
//
|
|
||||||
if (this.scrollHideOperateClassName) {
|
|
||||||
let parent = this.$parent.$el.parentNode;
|
|
||||||
while (parent) {
|
|
||||||
if (parent.classList.contains(this.scrollHideOperateClassName)) {
|
|
||||||
this.listener = parent;
|
|
||||||
parent.addEventListener("scroll", this.onTouchstart);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parent = parent.parentNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
this.content = this.value;
|
this.content = this.value;
|
||||||
@ -203,9 +191,6 @@
|
|||||||
deactivated() {
|
deactivated() {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
|
||||||
this.listener?.removeEventListener("scroll", this.onTouchstart);
|
|
||||||
},
|
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
@ -230,9 +215,6 @@
|
|||||||
},
|
},
|
||||||
readOnly(value) {
|
readOnly(value) {
|
||||||
if (this.editor !== null) {
|
if (this.editor !== null) {
|
||||||
if (this.windowTouch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value) {
|
if (value) {
|
||||||
this.editor.setMode('readonly');
|
this.editor.setMode('readonly');
|
||||||
} else {
|
} else {
|
||||||
@ -267,7 +249,6 @@
|
|||||||
this.editorT = null;
|
this.editorT = null;
|
||||||
}
|
}
|
||||||
this.spinShow = true;
|
this.spinShow = true;
|
||||||
this.operateVisible = false;
|
|
||||||
$A(this.$refs.myTextarea).show();
|
$A(this.$refs.myTextarea).show();
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
@ -301,8 +282,9 @@
|
|||||||
selector: (isFull ? '#T_' : '#') + this.id,
|
selector: (isFull ? '#T_' : '#') + this.id,
|
||||||
base_url: $A.originUrl('js/tinymce'),
|
base_url: $A.originUrl('js/tinymce'),
|
||||||
language: lang,
|
language: lang,
|
||||||
toolbar: this.toolbar,
|
|
||||||
plugins: this.plugin(isFull),
|
plugins: this.plugin(isFull),
|
||||||
|
menubar: this.menubar,
|
||||||
|
toolbar: this.toolbar,
|
||||||
placeholder: isFull && this.placeholderFull ? this.placeholderFull : this.placeholder,
|
placeholder: isFull && this.placeholderFull ? this.placeholderFull : this.placeholder,
|
||||||
save_onsavecallback: (e) => {
|
save_onsavecallback: (e) => {
|
||||||
this.$emit('editorSave', e);
|
this.$emit('editorSave', e);
|
||||||
@ -424,7 +406,8 @@
|
|||||||
editor.on('Init', (e) => {
|
editor.on('Init', (e) => {
|
||||||
this.editorT = editor;
|
this.editorT = editor;
|
||||||
this.editorT.setContent(this.content);
|
this.editorT.setContent(this.content);
|
||||||
if (this.readOnly) {
|
const readOnly = this.readOnlyFull === null ? this.readOnly : this.readOnlyFull;
|
||||||
|
if (readOnly) {
|
||||||
this.editorT.setMode('readonly');
|
this.editorT.setMode('readonly');
|
||||||
} else {
|
} else {
|
||||||
this.editorT.setMode('design');
|
this.editorT.setMode('design');
|
||||||
@ -448,13 +431,12 @@
|
|||||||
this.spinShow = false;
|
this.spinShow = false;
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
this.editor.setContent(this.content);
|
this.editor.setContent(this.content);
|
||||||
if (this.readOnly || this.windowTouch) {
|
if (this.readOnly) {
|
||||||
this.editor.setMode('readonly');
|
this.editor.setMode('readonly');
|
||||||
this.updateTouchContent();
|
|
||||||
} else {
|
} else {
|
||||||
this.editor.setMode('design');
|
this.editor.setMode('design');
|
||||||
}
|
}
|
||||||
this.$emit('editorInit', this.editor);
|
this.$emit('on-editor-init', this.editor);
|
||||||
});
|
});
|
||||||
editor.on('KeyUp', (e) => {
|
editor.on('KeyUp', (e) => {
|
||||||
if (this.editor !== null) {
|
if (this.editor !== null) {
|
||||||
@ -518,14 +500,8 @@
|
|||||||
this.$emit('input', this.content);
|
this.$emit('input', this.content);
|
||||||
this.editorT.destroy();
|
this.editorT.destroy();
|
||||||
this.editorT = null;
|
this.editorT = null;
|
||||||
//
|
|
||||||
if (this.windowTouch) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.updateTouchContent();
|
|
||||||
this.$emit('on-blur');
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.$emit('on-transfer-change', visible);
|
||||||
},
|
},
|
||||||
|
|
||||||
getEditor() {
|
getEditor() {
|
||||||
@ -627,12 +603,6 @@
|
|||||||
return imgs;
|
return imgs;
|
||||||
},
|
},
|
||||||
|
|
||||||
onLinkPreview() {
|
|
||||||
if (this.operateLink) {
|
|
||||||
window.open(this.operateLink);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onImagePreview() {
|
onImagePreview() {
|
||||||
const array = this.getValueImages();
|
const array = this.getValueImages();
|
||||||
if (array.length === 0) {
|
if (array.length === 0) {
|
||||||
@ -643,74 +613,6 @@
|
|||||||
this.$store.dispatch("previewImage", {index, list: array})
|
this.$store.dispatch("previewImage", {index, list: array})
|
||||||
},
|
},
|
||||||
|
|
||||||
onClickWrap(event) {
|
|
||||||
if (!this.windowTouch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
event.stopPropagation()
|
|
||||||
this.operateVisible = false;
|
|
||||||
this.operateLink = event.target.tagName === "A" ? event.target.href : null;
|
|
||||||
this.operateImg = event.target.tagName === "IMG" ? event.target.src : null;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
const rect = this.$el.getBoundingClientRect();
|
|
||||||
this.operateStyles = {
|
|
||||||
left: `${event.clientX - rect.left}px`,
|
|
||||||
top: `${event.clientY - rect.top}px`,
|
|
||||||
}
|
|
||||||
this.operateVisible = true;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
onTouchstart() {
|
|
||||||
if (!this.windowTouch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.operateVisible = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTouchContent() {
|
|
||||||
if (!this.windowTouch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$nextTick(_ => {
|
|
||||||
if (!this.editor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.placeholder || this.content) {
|
|
||||||
this.editor.bodyElement.removeAttribute("data-mce-placeholder");
|
|
||||||
this.editor.bodyElement.removeAttribute("aria-placeholder");
|
|
||||||
} else {
|
|
||||||
this.editor.bodyElement.setAttribute("data-mce-placeholder", this.placeholder);
|
|
||||||
this.editor.bodyElement.setAttribute("aria-placeholder", this.placeholder);
|
|
||||||
}
|
|
||||||
this.updateTouchLink(0);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTouchLink(timeout) {
|
|
||||||
if (!this.windowTouch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setTimeout(_ => {
|
|
||||||
if (!this.editor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.editor.bodyElement.querySelectorAll("a").forEach(item => {
|
|
||||||
if (item.__dataMceClick !== true) {
|
|
||||||
item.__dataMceClick = true;
|
|
||||||
item.addEventListener("click", event => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
this.onClickWrap(event);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (timeout < 300) {
|
|
||||||
this.updateTouchLink(timeout + 100);
|
|
||||||
}
|
|
||||||
}, timeout)
|
|
||||||
},
|
|
||||||
|
|
||||||
/********************文件上传部分************************/
|
/********************文件上传部分************************/
|
||||||
|
|
||||||
handleProgress(event, file) {
|
handleProgress(event, file) {
|
||||||
|
|||||||
251
resources/assets/js/components/TEditorTask.vue
Executable file
251
resources/assets/js/components/TEditorTask.vue
Executable file
@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<div class="task-editor" @click="onClickWrap" @touchstart="onTouchstart">
|
||||||
|
<TEditor
|
||||||
|
ref="desc"
|
||||||
|
v-model="content"
|
||||||
|
:plugins="plugins"
|
||||||
|
:options="options"
|
||||||
|
:option-full="optionFull"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:placeholderFull="placeholderFull"
|
||||||
|
:readOnly="windowTouch"
|
||||||
|
:readOnlyFull="false"
|
||||||
|
@on-blur="onBlur"
|
||||||
|
@on-editor-init="onEditorInit"
|
||||||
|
@on-transfer-change="onTransferChange"
|
||||||
|
inline/>
|
||||||
|
<div class="task-editor-operate" :style="operateStyles" v-show="operateVisible">
|
||||||
|
<Dropdown
|
||||||
|
trigger="custom"
|
||||||
|
:visible="operateVisible"
|
||||||
|
@on-clickoutside="operateVisible = false"
|
||||||
|
transfer>
|
||||||
|
<div :style="{userSelect:operateVisible ? 'none' : 'auto', height: operateStyles.height}"></div>
|
||||||
|
<DropdownMenu slot="list">
|
||||||
|
<DropdownItem @click.native="onEditing">{{ $L('编辑描述') }}</DropdownItem>
|
||||||
|
<DropdownItem v-if="operateLink" @click.native="onLinkPreview">{{ $L('打开链接') }}</DropdownItem>
|
||||||
|
<DropdownItem v-if="operateImg" @click.native="onImagePreview">{{ $L('查看图片') }}</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.task-editor {
|
||||||
|
position: relative;
|
||||||
|
.task-editor-operate {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 1px;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import TEditor from "./TEditor.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TEditorTask',
|
||||||
|
components: {TEditor},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
placeholderFull: {
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
content: this.value,
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
'advlist autolink lists link image charmap print preview hr anchor pagebreak',
|
||||||
|
'searchreplace visualblocks visualchars code',
|
||||||
|
'insertdatetime media nonbreaking save table directionality',
|
||||||
|
'emoticons paste codesample',
|
||||||
|
'autoresize'
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
statusbar: false,
|
||||||
|
menubar: false,
|
||||||
|
autoresize_bottom_margin: 2,
|
||||||
|
min_height: 200,
|
||||||
|
max_height: 380,
|
||||||
|
contextmenu: 'bold italic underline forecolor backcolor | link | codesample | uploadImages imagePreview | preview screenload',
|
||||||
|
valid_elements: 'a[href|title|target=_blank],em,strong/b,div[align],span[style],a,br,p,img[src|alt|witdh|height],pre[class],code',
|
||||||
|
extended_valid_elements: 'a[href|title|target=_blank]',
|
||||||
|
toolbar: false
|
||||||
|
},
|
||||||
|
optionFull: {
|
||||||
|
menubar: 'file edit view',
|
||||||
|
valid_elements: 'a[href|title|target=_blank],em,strong/b,div[align],span[style],a,br,p,img[src|alt|witdh|height],pre[class],code',
|
||||||
|
extended_valid_elements: 'a[href|title|target=_blank]',
|
||||||
|
toolbar: 'uploadImages | bold italic underline | forecolor backcolor'
|
||||||
|
},
|
||||||
|
|
||||||
|
operateStyles: {},
|
||||||
|
operateVisible: false,
|
||||||
|
operateLink: null,
|
||||||
|
operateImg: null,
|
||||||
|
|
||||||
|
listener: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
let parent = this.$parent.$el.parentNode;
|
||||||
|
while (parent) {
|
||||||
|
if (parent.classList.contains(".ivu-modal-wrap")) {
|
||||||
|
this.listener = parent;
|
||||||
|
parent.addEventListener("scroll", this.onTouchstart);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parent = parent.parentNode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.listener?.removeEventListener("scroll", this.onTouchstart);
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
editor() {
|
||||||
|
return this.$refs.desc.editor;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.content = val;
|
||||||
|
},
|
||||||
|
content(val) {
|
||||||
|
this.$emit('input', val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
getContent() {
|
||||||
|
return this.$refs.desc.getContent();
|
||||||
|
},
|
||||||
|
|
||||||
|
onEditing() {
|
||||||
|
this.$refs.desc.onFull()
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
this.$emit('on-blur');
|
||||||
|
},
|
||||||
|
|
||||||
|
onEditorInit(editor) {
|
||||||
|
this.updateTouchContent();
|
||||||
|
this.$emit('on-editor-init', editor);
|
||||||
|
},
|
||||||
|
|
||||||
|
onTransferChange(visible) {
|
||||||
|
if (visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.windowTouch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTimeout(_ => {
|
||||||
|
this.updateTouchContent();
|
||||||
|
this.onBlur();
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClickWrap(event) {
|
||||||
|
if (!this.windowTouch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event.stopPropagation()
|
||||||
|
this.operateVisible = false;
|
||||||
|
this.operateLink = event.target.tagName === "A" ? event.target.href : null;
|
||||||
|
this.operateImg = event.target.tagName === "IMG" ? event.target.src : null;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const rect = this.$el.getBoundingClientRect();
|
||||||
|
this.operateStyles = {
|
||||||
|
left: `${event.clientX - rect.left}px`,
|
||||||
|
top: `${event.clientY - rect.top}px`,
|
||||||
|
}
|
||||||
|
this.operateVisible = true;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onTouchstart() {
|
||||||
|
if (!this.windowTouch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.operateVisible = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTouchContent() {
|
||||||
|
if (!this.windowTouch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$nextTick(_ => {
|
||||||
|
if (!this.editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.content) {
|
||||||
|
this.editor.bodyElement.removeAttribute("data-mce-placeholder");
|
||||||
|
this.editor.bodyElement.removeAttribute("aria-placeholder");
|
||||||
|
} else {
|
||||||
|
this.editor.bodyElement.setAttribute("data-mce-placeholder", this.placeholder);
|
||||||
|
this.editor.bodyElement.setAttribute("aria-placeholder", this.placeholder);
|
||||||
|
}
|
||||||
|
this.updateTouchLink(0);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTouchLink(timeout) {
|
||||||
|
if (!this.windowTouch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTimeout(_ => {
|
||||||
|
if (!this.editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.editor.bodyElement.querySelectorAll("a").forEach(item => {
|
||||||
|
if (item.__dataMceClick !== true) {
|
||||||
|
item.__dataMceClick = true;
|
||||||
|
item.addEventListener("click", event => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.onClickWrap(event);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (timeout < 300) {
|
||||||
|
this.updateTouchLink(timeout + 100);
|
||||||
|
}
|
||||||
|
}, timeout)
|
||||||
|
},
|
||||||
|
|
||||||
|
onLinkPreview() {
|
||||||
|
if (this.operateLink) {
|
||||||
|
window.open(this.operateLink);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onImagePreview() {
|
||||||
|
const array = this.$refs.desc.getValueImages();
|
||||||
|
if (array.length === 0) {
|
||||||
|
$A.messageWarning("没有可预览的图片")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let index = Math.max(0, array.findIndex(item => item.src === this.operateImg));
|
||||||
|
this.$store.dispatch("previewImage", {index, list: array})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -25,16 +25,11 @@
|
|||||||
enterkeyhint="done"
|
enterkeyhint="done"
|
||||||
@on-keydown="onKeydown"/>
|
@on-keydown="onKeydown"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="desc">
|
<TEditorTask
|
||||||
<TEditor
|
class="desc"
|
||||||
v-model="addData.content"
|
v-model="addData.content"
|
||||||
:plugins="taskPlugins"
|
:placeholder="$L(windowLandscape ? '详细描述,选填...(点击右键使用工具栏)' : '详细描述,选填...')"
|
||||||
:options="taskOptions"
|
:placeholderFull="$L('详细描述...')"/>
|
||||||
:option-full="taskOptionFull"
|
|
||||||
:placeholder="$L(windowLandscape ? '详细描述,选填...(点击右键使用工具栏)' : '详细描述,选填...')"
|
|
||||||
:placeholderFull="$L('详细描述...')"
|
|
||||||
inline/>
|
|
||||||
</div>
|
|
||||||
<div class="advanced-option" :class="{'advanced-open': advanced}">
|
<div class="advanced-option" :class="{'advanced-open': advanced}">
|
||||||
<Button @click="advanced=!advanced">{{$L('高级选项')}}</Button>
|
<Button @click="advanced=!advanced">{{$L('高级选项')}}</Button>
|
||||||
<ul class="advanced-priority">
|
<ul class="advanced-priority">
|
||||||
@ -209,14 +204,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TEditor from "../../../components/TEditor";
|
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import UserSelect from "../../../components/UserSelect.vue";
|
import UserSelect from "../../../components/UserSelect.vue";
|
||||||
import TaskExistTips from "./TaskExistTips.vue";
|
import TaskExistTips from "./TaskExistTips.vue";
|
||||||
|
import TEditorTask from "../../../components/TEditorTask.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "TaskAdd",
|
name: "TaskAdd",
|
||||||
components: {UserSelect, TEditor, TaskExistTips},
|
components: {TEditorTask, UserSelect, TaskExistTips},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -252,29 +247,6 @@ export default {
|
|||||||
advanced: false,
|
advanced: false,
|
||||||
subName: '',
|
subName: '',
|
||||||
|
|
||||||
taskPlugins: [
|
|
||||||
'advlist autolink lists link image charmap print preview hr anchor pagebreak',
|
|
||||||
'searchreplace visualblocks visualchars code',
|
|
||||||
'insertdatetime media nonbreaking save table directionality',
|
|
||||||
'emoticons paste codesample',
|
|
||||||
'autoresize'
|
|
||||||
],
|
|
||||||
taskOptions: {
|
|
||||||
statusbar: false,
|
|
||||||
menubar: false,
|
|
||||||
autoresize_bottom_margin: 2,
|
|
||||||
min_height: 200,
|
|
||||||
max_height: 380,
|
|
||||||
contextmenu: 'bold italic underline forecolor backcolor | codesample | uploadImages imagePreview | preview screenload',
|
|
||||||
valid_elements : 'a[href|target=_blank],em,strong/b,div[align],span[style],a,br,p,img[src|alt|witdh|height],pre[class],code',
|
|
||||||
toolbar: false
|
|
||||||
},
|
|
||||||
taskOptionFull: {
|
|
||||||
menubar: 'file edit view',
|
|
||||||
valid_elements : 'a[href|target=_blank],em,strong/b,div[align],span[style],a,br,p,img[src|alt|witdh|height],pre[class],code',
|
|
||||||
toolbar: 'uploadImages | bold italic underline forecolor backcolor | codesample | preview screenload'
|
|
||||||
},
|
|
||||||
|
|
||||||
taskTimeOpen: false,
|
taskTimeOpen: false,
|
||||||
|
|
||||||
timeOptions: {shortcuts: $A.timeOptionShortcuts()},
|
timeOptions: {shortcuts: $A.timeOptionShortcuts()},
|
||||||
|
|||||||
@ -135,19 +135,12 @@
|
|||||||
@on-blur="updateBlur('name')"
|
@on-blur="updateBlur('name')"
|
||||||
@on-keydown="onNameKeydown"/>
|
@on-keydown="onNameKeydown"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="desc">
|
<TEditorTask
|
||||||
<TEditor
|
ref="desc"
|
||||||
ref="desc"
|
class="desc"
|
||||||
:value="taskContent"
|
:value="taskContent"
|
||||||
:plugins="taskPlugins"
|
:placeholder="$L('详细描述...')"
|
||||||
:options="taskOptions"
|
@on-blur="updateBlur('content')"/>
|
||||||
:option-full="taskOptionFull"
|
|
||||||
:edit-title="$L('编辑描述')"
|
|
||||||
:placeholder="$L('详细描述...')"
|
|
||||||
scroll-hide-operate-class-name="task-modal"
|
|
||||||
@on-blur="updateBlur('content')"
|
|
||||||
inline/>
|
|
||||||
</div>
|
|
||||||
<Form class="items" label-position="left" label-width="auto" @submit.native.prevent>
|
<Form class="items" label-position="left" label-width="auto" @submit.native.prevent>
|
||||||
<FormItem v-if="taskDetail.p_name">
|
<FormItem v-if="taskDetail.p_name">
|
||||||
<div class="item-label" slot="label">
|
<div class="item-label" slot="label">
|
||||||
@ -463,7 +456,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import TEditor from "../../../components/TEditor";
|
|
||||||
import TaskPriority from "./TaskPriority";
|
import TaskPriority from "./TaskPriority";
|
||||||
import TaskUpload from "./TaskUpload";
|
import TaskUpload from "./TaskUpload";
|
||||||
import DialogWrapper from "./DialogWrapper";
|
import DialogWrapper from "./DialogWrapper";
|
||||||
@ -473,10 +465,12 @@ import TaskMenu from "./TaskMenu";
|
|||||||
import ChatInput from "./ChatInput";
|
import ChatInput from "./ChatInput";
|
||||||
import UserSelect from "../../../components/UserSelect.vue";
|
import UserSelect from "../../../components/UserSelect.vue";
|
||||||
import TaskExistTips from "./TaskExistTips.vue";
|
import TaskExistTips from "./TaskExistTips.vue";
|
||||||
|
import TEditorTask from "../../../components/TEditorTask.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "TaskDetail",
|
name: "TaskDetail",
|
||||||
components: {
|
components: {
|
||||||
|
TEditorTask,
|
||||||
UserSelect,
|
UserSelect,
|
||||||
TaskExistTips,
|
TaskExistTips,
|
||||||
ChatInput,
|
ChatInput,
|
||||||
@ -485,7 +479,6 @@ export default {
|
|||||||
DialogWrapper,
|
DialogWrapper,
|
||||||
TaskUpload,
|
TaskUpload,
|
||||||
TaskPriority,
|
TaskPriority,
|
||||||
TEditor
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
taskId: {
|
taskId: {
|
||||||
@ -553,31 +546,6 @@ export default {
|
|||||||
sendLoad: 0,
|
sendLoad: 0,
|
||||||
openLoad: 0,
|
openLoad: 0,
|
||||||
|
|
||||||
taskPlugins: [
|
|
||||||
'advlist autolink lists link image charmap print preview hr anchor pagebreak',
|
|
||||||
'searchreplace visualblocks visualchars code',
|
|
||||||
'insertdatetime media nonbreaking save table directionality',
|
|
||||||
'emoticons paste codesample',
|
|
||||||
'autoresize'
|
|
||||||
],
|
|
||||||
taskOptions: {
|
|
||||||
statusbar: false,
|
|
||||||
menubar: false,
|
|
||||||
autoresize_bottom_margin: 2,
|
|
||||||
min_height: 200,
|
|
||||||
max_height: 380,
|
|
||||||
contextmenu: 'bold italic underline forecolor backcolor | link | codesample | uploadImages imagePreview | preview screenload',
|
|
||||||
valid_elements : 'a[href|title|target=_blank],em,strong/b,div[align],span[style],a,br,p,img[src|alt|witdh|height],pre[class],code',
|
|
||||||
extended_valid_elements : 'a[href|title|target=_blank]',
|
|
||||||
toolbar: false
|
|
||||||
},
|
|
||||||
taskOptionFull: {
|
|
||||||
menubar: 'file edit view',
|
|
||||||
valid_elements : 'a[href|title|target=_blank],em,strong/b,div[align],span[style],a,br,p,img[src|alt|witdh|height],pre[class],code',
|
|
||||||
extended_valid_elements : 'a[href|title|target=_blank]',
|
|
||||||
toolbar: 'uploadImages | bold italic underline | forecolor backcolor'
|
|
||||||
},
|
|
||||||
|
|
||||||
dialogDrag: false,
|
dialogDrag: false,
|
||||||
imageAttachment: true,
|
imageAttachment: true,
|
||||||
receiveTaskSubscribe: null,
|
receiveTaskSubscribe: null,
|
||||||
|
|||||||
28
resources/assets/sass/components/t-editor.scss
vendored
28
resources/assets/sass/components/t-editor.scss
vendored
@ -37,6 +37,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tox-tbtn__select-label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.tox-tbtn--bespoke {
|
.tox-tbtn--bespoke {
|
||||||
.tox-tbtn__select-label {
|
.tox-tbtn__select-label {
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -88,6 +94,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tox-tbtn__select-label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tox-tbtn--bespoke {
|
||||||
|
.tox-tbtn__select-label {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,16 +124,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.teditor-operate {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 1px;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-control {
|
.upload-control {
|
||||||
display: none;
|
display: none;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|||||||
18
resources/assets/sass/pages/common.scss
vendored
18
resources/assets/sass/pages/common.scss
vendored
@ -690,6 +690,24 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
&.lr,
|
||||||
|
&.auto {
|
||||||
|
> ul {
|
||||||
|
> li:not(.search-button) {
|
||||||
|
|
||||||
|
.search-content {
|
||||||
|
|
||||||
|
.ivu-input-wrapper,
|
||||||
|
.ivu-select {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-expand {
|
.search-expand {
|
||||||
|
|||||||
@ -69,7 +69,6 @@
|
|||||||
}
|
}
|
||||||
.desc {
|
.desc {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
overflow: auto;
|
|
||||||
div[contenteditable="true"] {
|
div[contenteditable="true"] {
|
||||||
outline: none
|
outline: none
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user