mirror of
https://github.com/kuaifan/dootask.git
synced 2026-02-28 04:40:37 +00:00
perf: 优化聊天输入框
This commit is contained in:
parent
8c05d8791d
commit
522ca02b36
@ -57,6 +57,8 @@
|
||||
"node-sass": "^6.0.1",
|
||||
"notification-koro1": "^1.1.1",
|
||||
"postcss": "^8.4.5",
|
||||
"quill": "^1.3.7",
|
||||
"quill-mention": "^3.1.0",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"sass": "^1.45.1",
|
||||
"sass-loader": "^12.4.0",
|
||||
|
||||
219
resources/assets/js/components/ChatInput.vue
Executable file
219
resources/assets/js/components/ChatInput.vue
Executable file
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="chat-input-wrapper">
|
||||
<div ref="editor"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.chat-input-wrapper {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
.ql-editor {
|
||||
padding: 4px 7px;
|
||||
font-size: 14px;
|
||||
max-height: 100px;
|
||||
&.ql-blank {
|
||||
&::before {
|
||||
left: 7px;
|
||||
right: 7px;
|
||||
color: #ccc;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Quill from 'quill';
|
||||
import "quill/dist/quill.snow.css";
|
||||
|
||||
import "quill-mention";
|
||||
import "quill-mention/dist/quill.mention.min.css";
|
||||
|
||||
export default {
|
||||
name: 'ChatInput',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enterSend: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
maxlength: {
|
||||
type: Number
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
quill: null,
|
||||
_content: '',
|
||||
_options: {},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.quill = null
|
||||
delete this.quill
|
||||
},
|
||||
watch: {
|
||||
// Watch content change
|
||||
value(newVal) {
|
||||
if (this.quill) {
|
||||
if (newVal && newVal !== this._content) {
|
||||
this._content = newVal
|
||||
this.quill.pasteHTML(newVal)
|
||||
} else if(!newVal) {
|
||||
this.quill.setText('')
|
||||
}
|
||||
}
|
||||
},
|
||||
// Watch disabled change
|
||||
disabled(newVal) {
|
||||
if (this.quill) {
|
||||
this.quill.enable(!newVal)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const atValues = [
|
||||
{ id: 1, value: "Fredrik Sundqvist" },
|
||||
{ id: 2, value: "Patrik Sjölin" }
|
||||
];
|
||||
const hashValues = [
|
||||
{ id: 3, value: "Fredrik Sundqvist 2" },
|
||||
{ id: 4, value: "Patrik Sjölin 2" }
|
||||
];
|
||||
|
||||
// Options
|
||||
this._options = Object.assign({
|
||||
theme: null,
|
||||
readOnly: false,
|
||||
placeholder: this.placeholder,
|
||||
modules: {
|
||||
keyboard: {
|
||||
bindings: {
|
||||
'short enter': {
|
||||
key: 13,
|
||||
shortKey: true,
|
||||
handler: _ => {
|
||||
if (!this.enterSend) {
|
||||
this.$emit('on-send', this.quill)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
'enter': {
|
||||
key: 13,
|
||||
shiftKey: false,
|
||||
handler: _ => {
|
||||
if (this.enterSend) {
|
||||
this.$emit('on-send', this.quill)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mention: {
|
||||
mentionDenotationChars: ["@", "#"],
|
||||
source: function(searchTerm, renderList, mentionChar) {
|
||||
let values;
|
||||
|
||||
if (mentionChar === "@") {
|
||||
values = atValues;
|
||||
} else {
|
||||
values = hashValues;
|
||||
}
|
||||
|
||||
if (searchTerm.length === 0) {
|
||||
renderList(values, searchTerm);
|
||||
} else {
|
||||
const matches = [];
|
||||
for (let i = 0; i < values.length; i++)
|
||||
if (
|
||||
~values[i].value.toLowerCase().indexOf(searchTerm.toLowerCase())
|
||||
)
|
||||
matches.push(values[i]);
|
||||
renderList(matches, searchTerm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this.options)
|
||||
|
||||
// Instance
|
||||
this.quill = new Quill(this.$refs.editor, this._options)
|
||||
this.quill.enable(false)
|
||||
|
||||
// Set editor content
|
||||
if (this.value) {
|
||||
this.quill.pasteHTML(this.value)
|
||||
}
|
||||
|
||||
// Disabled editor
|
||||
if (!this.disabled) {
|
||||
this.quill.enable(true)
|
||||
}
|
||||
|
||||
// Mark model as touched if editor lost focus
|
||||
this.quill.on('selection-change', range => {
|
||||
if (!range) {
|
||||
this.$emit('on-blur', this.quill)
|
||||
} else {
|
||||
this.$emit('on-focus', this.quill)
|
||||
}
|
||||
})
|
||||
|
||||
// Update model if text changes
|
||||
this.quill.on('text-change', _ => {
|
||||
if (this.maxlength > 0 && this.quill.getLength() > this.maxlength) {
|
||||
this.quill.deleteText(this.maxlength, this.quill.getLength());
|
||||
}
|
||||
let html = this.$refs.editor.children[0].innerHTML
|
||||
const quill = this.quill
|
||||
const text = this.quill.getText()
|
||||
if (/^(\<p\>\<br\>\<\/p\>)+$/.test(html)) html = ''
|
||||
this._content = html
|
||||
this.$emit('input', this._content)
|
||||
this.$emit('on-change', { html, text, quill })
|
||||
})
|
||||
|
||||
// Emit ready event
|
||||
this.$emit('on-ready', this.quill)
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$nextTick(() => {
|
||||
this.quill && this.quill.focus()
|
||||
})
|
||||
},
|
||||
|
||||
blur() {
|
||||
this.$nextTick(() => {
|
||||
this.quill && this.quill.blur()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,396 +0,0 @@
|
||||
<template>
|
||||
<div :class="wrapClasses">
|
||||
<template v-if="type !== 'textarea'">
|
||||
<div :class="[prefixCls + '-group-prepend']" v-if="prepend" v-show="slotReady"><slot name="prepend"></slot></div>
|
||||
<i class="ivu-icon" :class="['ivu-icon-ios-close-circle', prefixCls + '-icon', prefixCls + '-icon-clear' , prefixCls + '-icon-normal']" v-if="clearable && currentValue && !itemDisabled" @click="handleClear"></i>
|
||||
<i class="ivu-icon" :class="['ivu-icon-' + icon, prefixCls + '-icon', prefixCls + '-icon-normal']" v-else-if="icon" @click="handleIconClick"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-search" :class="[prefixCls + '-icon', prefixCls + '-icon-normal', prefixCls + '-search-icon']" v-else-if="search && enterButton === false" @click="handleSearch"></i>
|
||||
<span class="ivu-input-suffix" v-else-if="showSuffix"><slot name="suffix"><i class="ivu-icon" :class="['ivu-icon-' + suffix]" v-if="suffix"></i></slot></span>
|
||||
<span class="ivu-input-word-count" v-else-if="showWordLimit">{{ textLength }}/{{ upperLimit }}</span>
|
||||
<span class="ivu-input-suffix" v-else-if="password" @click="handleToggleShowPassword">
|
||||
<i class="ivu-icon ivu-icon-ios-eye-off-outline" v-if="showPassword"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-eye-outline" v-else></i>
|
||||
</span>
|
||||
<transition name="fade">
|
||||
<i class="ivu-icon ivu-icon-ios-loading ivu-load-loop" :class="[prefixCls + '-icon', prefixCls + '-icon-validate']" v-if="!icon"></i>
|
||||
</transition>
|
||||
<input
|
||||
:id="elementId"
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
ref="input"
|
||||
:type="currentType"
|
||||
:class="inputClasses"
|
||||
:placeholder="placeholder"
|
||||
:disabled="itemDisabled"
|
||||
:maxlength="maxlength"
|
||||
:readonly="readonly"
|
||||
:name="name"
|
||||
:value="currentValue"
|
||||
:number="number"
|
||||
:autofocus="autofocus"
|
||||
@keyup.enter="handleEnter"
|
||||
@keyup="handleKeyup"
|
||||
@keypress="handleKeypress"
|
||||
@keydown="handleKeydown"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@compositionstart="handleComposition"
|
||||
@compositionupdate="handleComposition"
|
||||
@compositionend="handleComposition"
|
||||
@input="handleInput"
|
||||
@change="handleChange"
|
||||
@paste="handlePaste">
|
||||
<div :class="[prefixCls + '-group-append']" v-if="append" v-show="slotReady"><slot name="append"></slot></div>
|
||||
<div :class="[prefixCls + '-group-append', prefixCls + '-search']" v-else-if="search && enterButton" @click="handleSearch">
|
||||
<i class="ivu-icon ivu-icon-ios-search" v-if="enterButton === true"></i>
|
||||
<template v-else>{{ enterButton }}</template>
|
||||
</div>
|
||||
<span class="ivu-input-prefix" v-else-if="showPrefix"><slot name="prefix"><i class="ivu-icon" :class="['ivu-icon-' + prefix]" v-if="prefix"></i></slot></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<textarea
|
||||
:id="elementId"
|
||||
:wrap="wrap"
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
ref="textarea"
|
||||
:class="textareaClasses"
|
||||
:style="textareaStyles"
|
||||
:placeholder="placeholder"
|
||||
:disabled="itemDisabled"
|
||||
:rows="rows"
|
||||
:maxlength="maxlength"
|
||||
:readonly="readonly"
|
||||
:name="name"
|
||||
:value="currentValue"
|
||||
:autofocus="autofocus"
|
||||
@keyup.enter="handleEnter"
|
||||
@keyup="handleKeyup"
|
||||
@keypress="handleKeypress"
|
||||
@keydown="handleKeydown"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@compositionstart="handleComposition"
|
||||
@compositionupdate="handleComposition"
|
||||
@compositionend="handleComposition"
|
||||
@input="handleInput"
|
||||
@paste="handlePaste">
|
||||
</textarea>
|
||||
<span class="ivu-input-word-count" v-if="showWordLimit">{{ textLength }}/{{ upperLimit }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { oneOf, findComponentUpward } from 'view-design-hi/src/utils/assist';
|
||||
import calcTextareaHeight from 'view-design-hi/src/utils/calcTextareaHeight';
|
||||
import Emitter from 'view-design-hi/src/mixins/emitter';
|
||||
import mixinsForm from 'view-design-hi/src/mixins/form';
|
||||
|
||||
const prefixCls = 'ivu-input';
|
||||
|
||||
export default {
|
||||
name: 'DragInput',
|
||||
mixins: [ Emitter, mixinsForm ],
|
||||
props: {
|
||||
type: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['text', 'textarea', 'password', 'url', 'email', 'date', 'number', 'tel']);
|
||||
},
|
||||
default: 'text'
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxlength: {
|
||||
type: [String, Number]
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: String,
|
||||
autosize: {
|
||||
type: [Boolean, Object],
|
||||
default: false
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
number: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
spellcheck: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: 'off'
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
elementId: {
|
||||
type: String
|
||||
},
|
||||
wrap: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['hard', 'soft']);
|
||||
},
|
||||
default: 'soft'
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
search: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enterButton: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
// 4.0.0
|
||||
showWordLimit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 4.0.0
|
||||
password: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
currentValue: this.value,
|
||||
prefixCls: prefixCls,
|
||||
slotReady: false,
|
||||
textareaStyles: {},
|
||||
isOnComposition: false,
|
||||
showPassword: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentType () {
|
||||
let type = this.type;
|
||||
if (type === 'password' && this.password && this.showPassword) type = 'text';
|
||||
return type;
|
||||
},
|
||||
prepend () {
|
||||
let state = false;
|
||||
if (this.type !== 'textarea') state = this.$slots.prepend !== undefined;
|
||||
return state;
|
||||
},
|
||||
append () {
|
||||
let state = false;
|
||||
if (this.type !== 'textarea') state = this.$slots.append !== undefined;
|
||||
return state;
|
||||
},
|
||||
showPrefix () {
|
||||
let state = false;
|
||||
if (this.type !== 'textarea') state = this.prefix !== '' || this.$slots.prefix !== undefined;
|
||||
return state;
|
||||
},
|
||||
showSuffix () {
|
||||
let state = false;
|
||||
if (this.type !== 'textarea') state = this.suffix !== '' || this.$slots.suffix !== undefined;
|
||||
return state;
|
||||
},
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}-wrapper`,
|
||||
{
|
||||
[`${prefixCls}-wrapper-${this.size}`]: !!this.size,
|
||||
[`${prefixCls}-type-${this.type}`]: this.type,
|
||||
[`${prefixCls}-group`]: this.prepend || this.append || (this.search && this.enterButton),
|
||||
[`${prefixCls}-group-${this.size}`]: (this.prepend || this.append || (this.search && this.enterButton)) && !!this.size,
|
||||
[`${prefixCls}-group-with-prepend`]: this.prepend,
|
||||
[`${prefixCls}-group-with-append`]: this.append || (this.search && this.enterButton),
|
||||
[`${prefixCls}-hide-icon`]: this.append, // #554
|
||||
[`${prefixCls}-with-search`]: (this.search && this.enterButton)
|
||||
}
|
||||
];
|
||||
},
|
||||
inputClasses () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-${this.size}`]: !!this.size,
|
||||
[`${prefixCls}-disabled`]: this.itemDisabled,
|
||||
[`${prefixCls}-with-prefix`]: this.showPrefix,
|
||||
[`${prefixCls}-with-suffix`]: this.showSuffix || (this.search && this.enterButton === false)
|
||||
}
|
||||
];
|
||||
},
|
||||
textareaClasses () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-disabled`]: this.itemDisabled
|
||||
}
|
||||
];
|
||||
},
|
||||
upperLimit () {
|
||||
return this.maxlength;
|
||||
},
|
||||
textLength () {
|
||||
if (typeof this.value === 'number') {
|
||||
return String(this.value).length;
|
||||
}
|
||||
|
||||
return (this.value || '').length;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEnter (event) {
|
||||
this.$emit('on-enter', event);
|
||||
if (this.search) this.$emit('on-search', this.currentValue);
|
||||
},
|
||||
handleKeydown (event) {
|
||||
this.$emit('on-keydown', event);
|
||||
},
|
||||
handleKeypress(event) {
|
||||
this.$emit('on-keypress', event);
|
||||
},
|
||||
handleKeyup (event) {
|
||||
this.$emit('on-keyup', event);
|
||||
},
|
||||
handleIconClick (event) {
|
||||
this.$emit('on-click', event);
|
||||
},
|
||||
handleFocus (event) {
|
||||
this.$emit('on-focus', event);
|
||||
},
|
||||
handleBlur (event) {
|
||||
this.$emit('on-blur', event);
|
||||
if (!findComponentUpward(this, ['DatePicker', 'TimePicker', 'Cascader', 'Search'])) {
|
||||
this.dispatch('FormItem', 'on-form-blur', this.currentValue);
|
||||
}
|
||||
},
|
||||
handleComposition(event) {
|
||||
if (event.type === 'compositionstart') {
|
||||
this.isOnComposition = true;
|
||||
}
|
||||
if (event.type === 'compositionend') {
|
||||
this.isOnComposition = false;
|
||||
this.handleInput(event);
|
||||
}
|
||||
},
|
||||
handleInput (event) {
|
||||
if (this.isOnComposition) return;
|
||||
|
||||
let value = event.target.value;
|
||||
if (this.number && value !== '') value = Number.isNaN(Number(value)) ? value : Number(value);
|
||||
this.$emit('input', value);
|
||||
this.setCurrentValue(value);
|
||||
this.$emit('on-change', event);
|
||||
},
|
||||
handleChange (event) {
|
||||
this.$emit('on-input-change', event);
|
||||
},
|
||||
handlePaste (event) {
|
||||
this.$emit('on-input-paste', event);
|
||||
},
|
||||
setCurrentValue (value) {
|
||||
if (value === this.currentValue) return;
|
||||
this.$nextTick(() => {
|
||||
this.resizeTextarea();
|
||||
});
|
||||
this.currentValue = value;
|
||||
if (!findComponentUpward(this, ['DatePicker', 'TimePicker', 'Cascader', 'Search'])) {
|
||||
this.dispatch('FormItem', 'on-form-change', value);
|
||||
}
|
||||
},
|
||||
resizeTextarea () {
|
||||
const autosize = this.autosize;
|
||||
if (!autosize || this.type !== 'textarea') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const minRows = autosize.minRows;
|
||||
const maxRows = autosize.maxRows;
|
||||
|
||||
this.textareaStyles = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
|
||||
},
|
||||
focus () {
|
||||
if (this.type === 'textarea') {
|
||||
this.$refs.textarea.focus();
|
||||
} else {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
blur () {
|
||||
if (this.type === 'textarea') {
|
||||
this.$refs.textarea.blur();
|
||||
} else {
|
||||
this.$refs.input.blur();
|
||||
}
|
||||
},
|
||||
handleClear () {
|
||||
const e = { target: { value: '' } };
|
||||
this.$emit('input', '');
|
||||
this.setCurrentValue('');
|
||||
this.$emit('on-change', e);
|
||||
this.$emit('on-clear');
|
||||
},
|
||||
handleSearch () {
|
||||
if (this.itemDisabled) return false;
|
||||
this.$refs.input.focus();
|
||||
this.$emit('on-search', this.currentValue);
|
||||
},
|
||||
handleToggleShowPassword () {
|
||||
if (this.itemDisabled) return false;
|
||||
this.showPassword = !this.showPassword;
|
||||
this.focus();
|
||||
const len = this.currentValue.length;
|
||||
setTimeout(() => {
|
||||
this.$refs.input.setSelectionRange(len, len);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.setCurrentValue(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.slotReady = true;
|
||||
this.resizeTextarea();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -83,19 +83,15 @@
|
||||
<div :class="['dialog-footer', msgNew > 0 && dialogMsgList.length > 0 ? 'newmsg' : '']" @click="onActive">
|
||||
<div class="dialog-newmsg" @click="onToBottom">{{$L('有' + msgNew + '条新消息')}}</div>
|
||||
<slot name="inputBefore"/>
|
||||
<DragInput
|
||||
<ChatInput
|
||||
ref="input"
|
||||
v-model="msgText"
|
||||
class="dialog-input"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
:autosize="{ minRows: 1, maxRows: 3 }"
|
||||
v-model="msgText"
|
||||
:maxlength="20000"
|
||||
@on-focus="onEventFocus"
|
||||
@on-blur="onEventblur"
|
||||
@on-keydown="chatKeydown"
|
||||
@on-input-paste="pasteDrag"
|
||||
:placeholder="$L('输入消息...')" />
|
||||
@on-send="sendMsg"
|
||||
:placeholder="$L('输入消息...')"/>
|
||||
<div v-if="msgText != ''" class="dialog-send" @click="sendMsg">
|
||||
<Icon type="md-send" />
|
||||
</div>
|
||||
@ -157,7 +153,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DragInput from "../../../components/DragInput";
|
||||
import ScrollerY from "../../../components/ScrollerY";
|
||||
import {mapState} from "vuex";
|
||||
import DialogView from "./DialogView";
|
||||
@ -166,10 +161,11 @@ import {Store} from "le5le-store";
|
||||
import UserInput from "../../../components/UserInput";
|
||||
import DrawerOverlay from "../../../components/DrawerOverlay";
|
||||
import DialogGroupInfo from "./DialogGroupInfo";
|
||||
import ChatInput from "../../../components/ChatInput";
|
||||
|
||||
export default {
|
||||
name: "DialogWrapper",
|
||||
components: {DialogGroupInfo, DrawerOverlay, UserInput, DialogUpload, DialogView, ScrollerY, DragInput},
|
||||
components: {ChatInput, DialogGroupInfo, DrawerOverlay, UserInput, DialogUpload, DialogView, ScrollerY},
|
||||
props: {
|
||||
dialogId: {
|
||||
type: Number,
|
||||
@ -382,17 +378,8 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
chatKeydown(e) {
|
||||
if (e.keyCode === 13) {
|
||||
if (e.shiftKey) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.sendMsg();
|
||||
}
|
||||
},
|
||||
|
||||
pasteDrag(e, type) {
|
||||
chatPasteDrag(e, type) {
|
||||
this.dialogDrag = false;
|
||||
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
||||
const postFiles = Array.prototype.slice.call(files);
|
||||
if (postFiles.length > 0) {
|
||||
@ -401,11 +388,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
chatPasteDrag(e, type) {
|
||||
this.dialogDrag = false;
|
||||
this.pasteDrag(e, type);
|
||||
},
|
||||
|
||||
chatDragOver(show, e) {
|
||||
let random = (this.__dialogDrag = $A.randomString(8));
|
||||
if (!show) {
|
||||
|
||||
@ -398,17 +398,13 @@
|
||||
@dragleave.prevent="taskDragOver(false, $event)">
|
||||
<div class="no-tip">{{$L('暂无消息')}}</div>
|
||||
<div class="no-input">
|
||||
<DragInput
|
||||
<ChatInput
|
||||
class="dialog-input"
|
||||
v-model="msgText"
|
||||
type="textarea"
|
||||
:disabled="sendLoad > 0"
|
||||
:rows="1"
|
||||
:autosize="{ minRows: 1, maxRows: 3 }"
|
||||
:maxlength="20000"
|
||||
:placeholder="$L('输入消息...')"
|
||||
@on-keydown="msgKeydown"
|
||||
@on-input-paste="msgPasteDrag"/>
|
||||
@on-send="msgDialog"/>
|
||||
<div class="no-send" @click="msgDialog">
|
||||
<Loading v-if="sendLoad > 0"/>
|
||||
<template v-else>
|
||||
@ -437,11 +433,11 @@ import DialogWrapper from "./DialogWrapper";
|
||||
import ProjectLog from "./ProjectLog";
|
||||
import {Store} from "le5le-store";
|
||||
import TaskMenu from "./TaskMenu";
|
||||
import DragInput from "../../../components/DragInput";
|
||||
import ChatInput from "../../../components/ChatInput";
|
||||
|
||||
export default {
|
||||
name: "TaskDetail",
|
||||
components: {DragInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
|
||||
components: {ChatInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
@ -1064,18 +1060,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
msgKeydown(e) {
|
||||
if (e.keyCode === 13) {
|
||||
if (e.shiftKey) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
if (this.msgText) {
|
||||
this.msgDialog();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
msgDialog() {
|
||||
if (this.sendLoad > 0) {
|
||||
return;
|
||||
@ -1136,7 +1120,8 @@ export default {
|
||||
this.msgText = "";
|
||||
},
|
||||
|
||||
msgPasteDrag(e, type) {
|
||||
taskPasteDrag(e, type) {
|
||||
this.dialogDrag = false;
|
||||
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
||||
this.msgFile = Array.prototype.slice.call(files);
|
||||
if (this.msgFile.length > 0) {
|
||||
@ -1145,11 +1130,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
taskPasteDrag(e, type) {
|
||||
this.dialogDrag = false;
|
||||
this.msgPasteDrag(e, type);
|
||||
},
|
||||
|
||||
taskDragOver(show, e) {
|
||||
let random = (this.__dialogDrag = $A.randomString(8));
|
||||
if (!show) {
|
||||
|
||||
@ -142,6 +142,7 @@
|
||||
line-height: 20px;
|
||||
padding-top: 2px;
|
||||
color: #aaaaaa;
|
||||
white-space: normal;
|
||||
|
||||
&.pointer {
|
||||
cursor: pointer;
|
||||
@ -527,22 +528,12 @@
|
||||
background-color: #F4F5F7;
|
||||
padding: 10px 52px 10px 12px;
|
||||
border-radius: 10px;
|
||||
|
||||
.ivu-input {
|
||||
border: 0;
|
||||
resize: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-send {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 14px;
|
||||
right: 28px;
|
||||
bottom: 0;
|
||||
font-size: 18px;
|
||||
width: 46px;
|
||||
|
||||
@ -106,6 +106,9 @@
|
||||
.dialog-input {
|
||||
width: calc(100% - 44px);
|
||||
}
|
||||
.dialog-send {
|
||||
right: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,10 +384,6 @@
|
||||
.dialog-nav {
|
||||
height: 54px;
|
||||
justify-content: center;
|
||||
.dialog-title {
|
||||
flex: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
.dialog-footer {
|
||||
position: relative;
|
||||
@ -406,6 +402,9 @@
|
||||
.dialog-input {
|
||||
width: calc(100% - 44px);
|
||||
}
|
||||
.dialog-send {
|
||||
right: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user