mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-17 11:13:26 +00:00
perf: 优化聊天输入框
This commit is contained in:
parent
8c05d8791d
commit
522ca02b36
@ -57,6 +57,8 @@
|
|||||||
"node-sass": "^6.0.1",
|
"node-sass": "^6.0.1",
|
||||||
"notification-koro1": "^1.1.1",
|
"notification-koro1": "^1.1.1",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
|
"quill": "^1.3.7",
|
||||||
|
"quill-mention": "^3.1.0",
|
||||||
"resolve-url-loader": "^4.0.0",
|
"resolve-url-loader": "^4.0.0",
|
||||||
"sass": "^1.45.1",
|
"sass": "^1.45.1",
|
||||||
"sass-loader": "^12.4.0",
|
"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-footer', msgNew > 0 && dialogMsgList.length > 0 ? 'newmsg' : '']" @click="onActive">
|
||||||
<div class="dialog-newmsg" @click="onToBottom">{{$L('有' + msgNew + '条新消息')}}</div>
|
<div class="dialog-newmsg" @click="onToBottom">{{$L('有' + msgNew + '条新消息')}}</div>
|
||||||
<slot name="inputBefore"/>
|
<slot name="inputBefore"/>
|
||||||
<DragInput
|
<ChatInput
|
||||||
ref="input"
|
ref="input"
|
||||||
v-model="msgText"
|
|
||||||
class="dialog-input"
|
class="dialog-input"
|
||||||
type="textarea"
|
v-model="msgText"
|
||||||
:rows="1"
|
|
||||||
:autosize="{ minRows: 1, maxRows: 3 }"
|
|
||||||
:maxlength="20000"
|
:maxlength="20000"
|
||||||
@on-focus="onEventFocus"
|
@on-focus="onEventFocus"
|
||||||
@on-blur="onEventblur"
|
@on-blur="onEventblur"
|
||||||
@on-keydown="chatKeydown"
|
@on-send="sendMsg"
|
||||||
@on-input-paste="pasteDrag"
|
:placeholder="$L('输入消息...')"/>
|
||||||
:placeholder="$L('输入消息...')" />
|
|
||||||
<div v-if="msgText != ''" class="dialog-send" @click="sendMsg">
|
<div v-if="msgText != ''" class="dialog-send" @click="sendMsg">
|
||||||
<Icon type="md-send" />
|
<Icon type="md-send" />
|
||||||
</div>
|
</div>
|
||||||
@ -157,7 +153,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import DragInput from "../../../components/DragInput";
|
|
||||||
import ScrollerY from "../../../components/ScrollerY";
|
import ScrollerY from "../../../components/ScrollerY";
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import DialogView from "./DialogView";
|
import DialogView from "./DialogView";
|
||||||
@ -166,10 +161,11 @@ import {Store} from "le5le-store";
|
|||||||
import UserInput from "../../../components/UserInput";
|
import UserInput from "../../../components/UserInput";
|
||||||
import DrawerOverlay from "../../../components/DrawerOverlay";
|
import DrawerOverlay from "../../../components/DrawerOverlay";
|
||||||
import DialogGroupInfo from "./DialogGroupInfo";
|
import DialogGroupInfo from "./DialogGroupInfo";
|
||||||
|
import ChatInput from "../../../components/ChatInput";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DialogWrapper",
|
name: "DialogWrapper",
|
||||||
components: {DialogGroupInfo, DrawerOverlay, UserInput, DialogUpload, DialogView, ScrollerY, DragInput},
|
components: {ChatInput, DialogGroupInfo, DrawerOverlay, UserInput, DialogUpload, DialogView, ScrollerY},
|
||||||
props: {
|
props: {
|
||||||
dialogId: {
|
dialogId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -382,17 +378,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
chatKeydown(e) {
|
chatPasteDrag(e, type) {
|
||||||
if (e.keyCode === 13) {
|
this.dialogDrag = false;
|
||||||
if (e.shiftKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
this.sendMsg();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
pasteDrag(e, type) {
|
|
||||||
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
||||||
const postFiles = Array.prototype.slice.call(files);
|
const postFiles = Array.prototype.slice.call(files);
|
||||||
if (postFiles.length > 0) {
|
if (postFiles.length > 0) {
|
||||||
@ -401,11 +388,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
chatPasteDrag(e, type) {
|
|
||||||
this.dialogDrag = false;
|
|
||||||
this.pasteDrag(e, type);
|
|
||||||
},
|
|
||||||
|
|
||||||
chatDragOver(show, e) {
|
chatDragOver(show, e) {
|
||||||
let random = (this.__dialogDrag = $A.randomString(8));
|
let random = (this.__dialogDrag = $A.randomString(8));
|
||||||
if (!show) {
|
if (!show) {
|
||||||
|
|||||||
@ -398,17 +398,13 @@
|
|||||||
@dragleave.prevent="taskDragOver(false, $event)">
|
@dragleave.prevent="taskDragOver(false, $event)">
|
||||||
<div class="no-tip">{{$L('暂无消息')}}</div>
|
<div class="no-tip">{{$L('暂无消息')}}</div>
|
||||||
<div class="no-input">
|
<div class="no-input">
|
||||||
<DragInput
|
<ChatInput
|
||||||
class="dialog-input"
|
class="dialog-input"
|
||||||
v-model="msgText"
|
v-model="msgText"
|
||||||
type="textarea"
|
|
||||||
:disabled="sendLoad > 0"
|
:disabled="sendLoad > 0"
|
||||||
:rows="1"
|
|
||||||
:autosize="{ minRows: 1, maxRows: 3 }"
|
|
||||||
:maxlength="20000"
|
:maxlength="20000"
|
||||||
:placeholder="$L('输入消息...')"
|
:placeholder="$L('输入消息...')"
|
||||||
@on-keydown="msgKeydown"
|
@on-send="msgDialog"/>
|
||||||
@on-input-paste="msgPasteDrag"/>
|
|
||||||
<div class="no-send" @click="msgDialog">
|
<div class="no-send" @click="msgDialog">
|
||||||
<Loading v-if="sendLoad > 0"/>
|
<Loading v-if="sendLoad > 0"/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -437,11 +433,11 @@ import DialogWrapper from "./DialogWrapper";
|
|||||||
import ProjectLog from "./ProjectLog";
|
import ProjectLog from "./ProjectLog";
|
||||||
import {Store} from "le5le-store";
|
import {Store} from "le5le-store";
|
||||||
import TaskMenu from "./TaskMenu";
|
import TaskMenu from "./TaskMenu";
|
||||||
import DragInput from "../../../components/DragInput";
|
import ChatInput from "../../../components/ChatInput";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "TaskDetail",
|
name: "TaskDetail",
|
||||||
components: {DragInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
|
components: {ChatInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
|
||||||
props: {
|
props: {
|
||||||
taskId: {
|
taskId: {
|
||||||
type: Number,
|
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() {
|
msgDialog() {
|
||||||
if (this.sendLoad > 0) {
|
if (this.sendLoad > 0) {
|
||||||
return;
|
return;
|
||||||
@ -1136,7 +1120,8 @@ export default {
|
|||||||
this.msgText = "";
|
this.msgText = "";
|
||||||
},
|
},
|
||||||
|
|
||||||
msgPasteDrag(e, type) {
|
taskPasteDrag(e, type) {
|
||||||
|
this.dialogDrag = false;
|
||||||
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
||||||
this.msgFile = Array.prototype.slice.call(files);
|
this.msgFile = Array.prototype.slice.call(files);
|
||||||
if (this.msgFile.length > 0) {
|
if (this.msgFile.length > 0) {
|
||||||
@ -1145,11 +1130,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
taskPasteDrag(e, type) {
|
|
||||||
this.dialogDrag = false;
|
|
||||||
this.msgPasteDrag(e, type);
|
|
||||||
},
|
|
||||||
|
|
||||||
taskDragOver(show, e) {
|
taskDragOver(show, e) {
|
||||||
let random = (this.__dialogDrag = $A.randomString(8));
|
let random = (this.__dialogDrag = $A.randomString(8));
|
||||||
if (!show) {
|
if (!show) {
|
||||||
|
|||||||
@ -142,6 +142,7 @@
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
color: #aaaaaa;
|
color: #aaaaaa;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
&.pointer {
|
&.pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -527,22 +528,12 @@
|
|||||||
background-color: #F4F5F7;
|
background-color: #F4F5F7;
|
||||||
padding: 10px 52px 10px 12px;
|
padding: 10px 52px 10px 12px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
.ivu-input {
|
|
||||||
border: 0;
|
|
||||||
resize: none;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-send {
|
.dialog-send {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 14px;
|
right: 28px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
width: 46px;
|
width: 46px;
|
||||||
|
|||||||
@ -106,6 +106,9 @@
|
|||||||
.dialog-input {
|
.dialog-input {
|
||||||
width: calc(100% - 44px);
|
width: calc(100% - 44px);
|
||||||
}
|
}
|
||||||
|
.dialog-send {
|
||||||
|
right: 22px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -384,10 +384,6 @@
|
|||||||
.dialog-nav {
|
.dialog-nav {
|
||||||
height: 54px;
|
height: 54px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
.dialog-title {
|
|
||||||
flex: 0;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -406,6 +402,9 @@
|
|||||||
.dialog-input {
|
.dialog-input {
|
||||||
width: calc(100% - 44px);
|
width: calc(100% - 44px);
|
||||||
}
|
}
|
||||||
|
.dialog-send {
|
||||||
|
right: 22px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user