This commit is contained in:
kuaifan 2022-05-26 15:57:43 +08:00
parent 173f5c84db
commit ac45ba633b
21 changed files with 512 additions and 365 deletions

View File

@ -96,5 +96,8 @@
"url": "https://t.hitosea.com/desktop/publish" "url": "https://t.hitosea.com/desktop/publish"
} }
} }
] ],
"dependencies": {
"grapheme-splitter": "^1.0.4"
}
} }

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
98d215c910f5969e 6c5499276179e032

View File

@ -1,19 +1,24 @@
<template> <template>
<div class="chat-input-box">
<div class="chat-input-wrapper" :class="modeClass" @click.stop="focus"> <div class="chat-input-wrapper" :class="modeClass" @click.stop="focus">
<div ref="editor" class="no-dark-content" :style="editorStyle" @click.stop="" @paste="handlePaste"></div> <div ref="editor" class="no-dark-content" :style="editorStyle" @click.stop="" @paste="handlePaste"></div>
<div class="chat-input-toolbar" @click.stop=""> <div class="chat-input-toolbar" @click.stop="">
<slot name="toolbarBefore"/> <slot name="toolbarBefore"/>
<EPopover <EPopover
v-if="$isDesktop"
v-model="showEmoji" v-model="showEmoji"
:visibleArrow="false" :visibleArrow="false"
placement="top" placement="top"
popperClass="chat-input-emoji-popover"> popperClass="chat-input-emoji-popover">
<ETooltip slot="reference" ref="emojiTip" :disabled="!$isDesktop || showEmoji" placement="top" :content="$L('表情')"> <ETooltip slot="reference" ref="emojiTip" :disabled="!$isDesktop || showEmoji" placement="top" :content="$L('表情')">
<i class="taskfont" @click="onToolbar('emoji')">&#xe7ad;</i> <i class="taskfont">&#xe7ad;</i>
</ETooltip> </ETooltip>
<ChatEmoji @on-select="onSelectEmoji"/> <ChatEmoji @on-select="onSelectEmoji"/>
</EPopover> </EPopover>
<ETooltip v-else ref="emojiTip" :disabled="!$isDesktop || showEmoji" placement="top" :content="$L('表情')">
<i class="taskfont" @click="showEmoji=!showEmoji">&#xe7ad;</i>
</ETooltip>
<ETooltip placement="top" :disabled="!$isDesktop" :content="$L('选择会员')"> <ETooltip placement="top" :disabled="!$isDesktop" :content="$L('选择会员')">
<i class="taskfont" @click="onToolbar('user')">&#xe78f;</i> <i class="taskfont" @click="onToolbar('user')">&#xe78f;</i>
@ -28,7 +33,7 @@
placement="top" placement="top"
popperClass="chat-input-more-popover"> popperClass="chat-input-more-popover">
<ETooltip slot="reference" ref="moreTip" :disabled="!$isDesktop || showMore" placement="top" :content="$L('展开')"> <ETooltip slot="reference" ref="moreTip" :disabled="!$isDesktop || showMore" placement="top" :content="$L('展开')">
<i class="taskfont" @click="onToolbar('more')">&#xe790;</i> <i class="taskfont">&#xe790;</i>
</ETooltip> </ETooltip>
<div class="chat-input-popover-item" @click="onToolbar('image')"> <div class="chat-input-popover-item" @click="onToolbar('image')">
<i class="taskfont">&#xe64a;</i> <i class="taskfont">&#xe64a;</i>
@ -50,6 +55,10 @@
<slot name="toolbarAfter"/> <slot name="toolbarAfter"/>
</div> </div>
</div> </div>
<template v-if="!$isDesktop">
<ChatEmoji v-if="showEmoji" @on-select="onSelectEmoji"/>
</template>
</div>
</template> </template>
<script> <script>
@ -109,6 +118,7 @@ export default {
data() { data() {
return { return {
quill: null, quill: null,
rangeIndex: 0,
_content: '', _content: '',
_options: {}, _options: {},
@ -124,7 +134,6 @@ export default {
wrapperWidth: 0, wrapperWidth: 0,
editorHeight: 0, editorHeight: 0,
timerScroll: null,
isSpecVersion: this.checkIOSVersion(), isSpecVersion: this.checkIOSVersion(),
}; };
}, },
@ -144,7 +153,6 @@ export default {
this.observer.observe(this.$refs.editor); this.observer.observe(this.$refs.editor);
}, },
beforeDestroy() { beforeDestroy() {
this.inputCache(this.dialogId, this.value);
if (this.quill) { if (this.quill) {
this.quill = null this.quill = null
} }
@ -171,15 +179,16 @@ export default {
}, },
watch: { watch: {
// Watch content change // Watch content change
value(newVal) { value(val) {
if (this.quill) { if (this.quill) {
if (newVal && newVal !== this._content) { if (val && val !== this._content) {
this._content = newVal this._content = val
this.setContent(newVal) this.setContent(val)
} else if(!newVal) { } else if(!val) {
this.quill.setText('') this.quill.setText('')
} }
} }
this.setInputCache(val)
}, },
// Watch disabled change // Watch disabled change
@ -190,11 +199,10 @@ export default {
}, },
// Reset lists // Reset lists
dialogId(id1, id2) { dialogId() {
this.userList = null; this.userList = null;
this.taskList = null; this.taskList = null;
this.inputCache(id2, this.value) this.$emit('input', this.getInputCache())
this.$emit('input', this.inputCache(id1))
}, },
taskId() { taskId() {
this.userList = null; this.userList = null;
@ -202,19 +210,29 @@ export default {
}, },
showEmoji(val) { showEmoji(val) {
if (val) {
this.showMore = false;
if (this.quill) {
const range = this.quill.selection.savedRange;
this.rangeIndex = range ? range.index : 0
}
}
if (!val && this.$refs.emojiTip) { if (!val && this.$refs.emojiTip) {
this.$refs.emojiTip.updatePopper() this.$refs.emojiTip.updatePopper()
} }
}, },
showMore(val) { showMore(val) {
if (val) {
this.showEmoji = false;
}
if (!val && this.$refs.moreTip) { if (!val && this.$refs.moreTip) {
this.$refs.moreTip.updatePopper() this.$refs.moreTip.updatePopper()
} }
}, },
dialogInputCache() { dialogInputCache() {
this.$emit('input', this.inputCache(this.dialogId)) this.$emit('input', this.getInputCache())
} }
}, },
methods: { methods: {
@ -302,7 +320,7 @@ export default {
if (this.value) { if (this.value) {
this.setContent(this.value) this.setContent(this.value)
} else { } else {
this.$emit('input', this.inputCache(this.dialogId)) this.$emit('input', this.getInputCache())
} }
// Disabled editor // Disabled editor
@ -312,25 +330,17 @@ export default {
// Mark model as touched if editor lost focus // Mark model as touched if editor lost focus
this.quill.on('selection-change', range => { this.quill.on('selection-change', range => {
if (this.timerScroll) {
clearInterval(this.timerScroll);
}
if (!range) { if (!range) {
this.$emit('on-blur', this.quill) this.$emit('on-blur', this.quill)
this.inputCache(this.dialogId, this.value)
} else { } else {
this.$emit('on-focus', this.quill) this.$emit('on-focus', this.quill)
this.showEmoji = false this.hidePopover()
this.showMore = false
if (this.isSpecVersion) { if (this.isSpecVersion) {
// ios11.0-11.3 scrollTopscrolIntoViewbug // ios11.0-11.3 scrollTopscrolIntoViewbug
// //
} else { } else {
setTimeout(() => { setTimeout(() => {
$A.scrollToView(this.$refs.editor, true) $A.scrollToView(this.$refs.editor, true)
this.timerScroll = setInterval(() => {
$A.scrollToView(this.$refs.editor, true)
}, 300);
}, 300); }, 300);
} }
} }
@ -378,11 +388,14 @@ export default {
} }
}, },
inputCache(key, cache) { getInputCache() {
if (cache === undefined) { const key = this.dialogId;
const item = this.dialogInputCache.find(item => item.key == key); const item = this.dialogInputCache.find(item => item.key == key);
return item ? item.cache : ''; return item ? item.cache : '';
} },
setInputCache(cache) {
const key = this.dialogId;
const index = this.dialogInputCache.findIndex(item => item.key == key); const index = this.dialogInputCache.findIndex(item => item.key == key);
const data = {key, cache} const data = {key, cache}
if (index > -1) { if (index > -1) {
@ -390,9 +403,10 @@ export default {
} else { } else {
this.$store.state.dialogInputCache.push(data) this.$store.state.dialogInputCache.push(data)
} }
setTimeout(_ => { this.__setInputCache && clearTimeout(this.__setInputCache);
this.__setInputCache = setTimeout(_ => {
$A.setStorage("cacheDialogInput", this.$store.state.dialogInputCache); $A.setStorage("cacheDialogInput", this.$store.state.dialogInputCache);
}) }, 600)
}, },
focus() { focus() {
@ -412,7 +426,11 @@ export default {
return; return;
} }
this.$emit('on-send') this.$emit('on-send')
this.inputCache(this.dialogId, null) },
hidePopover() {
this.showEmoji = false;
this.showMore = false;
}, },
onSelectEmoji(item) { onSelectEmoji(item) {
@ -422,21 +440,21 @@ export default {
if (item.type === 'emoji') { if (item.type === 'emoji') {
let element = document.createElement('span'); let element = document.createElement('span');
element.innerHTML = item.html; element.innerHTML = item.html;
this.quill.insertText(this.quill.getSelection(true).index, element.innerHTML); this.quill.insertText(this.rangeIndex, element.innerHTML);
this.rangeIndex += element.innerHTML.length
element = null; element = null;
if (this.$isDesktop) {
this.showEmoji = false;
this.quill.setSelection(this.rangeIndex)
}
} else if (item.type === 'emoticon') { } else if (item.type === 'emoticon') {
this.$emit('on-send', `<img class="emoticon" data-asset="${item.asset}" data-name="${item.name}" src="${item.src}"/>`) this.$emit('on-send', `<img class="emoticon" data-asset="${item.asset}" data-name="${item.name}" src="${item.src}"/>`)
}
this.showEmoji = false; this.showEmoji = false;
}
}, },
onToolbar(action) { onToolbar(action) {
if (action !== 'emoji') { this.hidePopover();
this.showEmoji = false;
}
if (action !== 'more') {
this.showMore = false;
}
switch (action) { switch (action) {
case 'user': case 'user':
this.openMenu("@"); this.openMenu("@");

View File

@ -400,6 +400,7 @@
<div class="no-tip">{{$L('暂无消息')}}</div> <div class="no-tip">{{$L('暂无消息')}}</div>
<div class="no-input"> <div class="no-input">
<ChatInput <ChatInput
ref="chatInput"
:task-id="taskId" :task-id="taskId"
v-model="msgText" v-model="msgText"
:disabled="sendLoad > 0" :disabled="sendLoad > 0"
@ -408,7 +409,7 @@
:placeholder="$L('输入消息...')" :placeholder="$L('输入消息...')"
@on-more="onEventMore" @on-more="onEventMore"
@on-file="onSelectFile" @on-file="onSelectFile"
@on-send="msgDialog"> @on-send="onSend">
<Badge slot="toolbarAfter" :count="taskDetail.msg_num"/> <Badge slot="toolbarAfter" :count="taskDetail.msg_num"/>
</ChatInput> </ChatInput>
</div> </div>
@ -744,6 +745,7 @@ export default {
this.receiveShow = false; this.receiveShow = false;
this.$refs.owner && this.$refs.owner.handleClose(); this.$refs.owner && this.$refs.owner.handleClose();
this.$refs.assist && this.$refs.assist.handleClose(); this.$refs.assist && this.$refs.assist.handleClose();
this.$refs.chatInput && this.$refs.chatInput.hidePopover();
} }
}, },
immediate: true immediate: true
@ -1172,6 +1174,11 @@ export default {
this.msgDialog() this.msgDialog()
}, },
onSend() {
this.$refs.chatInput && this.$refs.chatInput.hidePopover();
this.msgDialog();
},
deleteFile(file) { deleteFile(file) {
this.$set(file, '_show_menu', false); this.$set(file, '_show_menu', false);
this.$store.dispatch("forgetTaskFile", file.id) this.$store.dispatch("forgetTaskFile", file.id)

View File

@ -410,6 +410,7 @@ body.dark-mode-reverse {
} }
} }
.chat-input-box {
.chat-input-wrapper { .chat-input-wrapper {
.ql-container { .ql-container {
.ql-editor { .ql-editor {
@ -426,3 +427,4 @@ body.dark-mode-reverse {
} }
} }
} }
}

View File

@ -1,6 +1,11 @@
@import "~quill/dist/quill.bubble.css"; @import "~quill/dist/quill.bubble.css";
@import "~quill-mention/dist/quill.mention.min.css"; @import "~quill-mention/dist/quill.mention.min.css";
.chat-input-box {
display: flex;
flex-direction: column;
width: 100%;
.chat-input-wrapper { .chat-input-wrapper {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
@ -308,6 +313,60 @@
} }
} }
} }
.chat-emoji-wrapper {
.chat-emoji-box {
width: auto;
padding: 8px 2px;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
&::after {
content: "";
flex: auto;
}
> li {
transition: none;
&:hover {
transform: none;
}
}
}
.chat-emoji-menu {
width: 100%;
padding: 3px 0;
border-radius: 8px;
box-sizing: content-box;
> li {
position: relative;
&:before {
display: none;
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 36px;
height: 36px;
border-radius: 8px;
transform: translate(-50%, -50%);
background-color: #ffffff;
z-index: 1;
}
> span,
> img {
position: static;
z-index: 2;
}
&.active {
background-color: transparent;
&:before {
display: block;
}
}
}
}
}
}
.chat-input-emoji-popover { .chat-input-emoji-popover {
padding: 0; padding: 0;
@ -414,3 +473,47 @@
} }
} }
} }
@media (max-width: 768px) {
.chat-input-box {
.chat-input-wrapper {
padding-left: 6px;
padding-right: 6px;
background-color: #ffffff;
}
.chat-emoji-wrapper {
margin-top: 8px;
margin-left: -10px;
margin-bottom: -8px;
width: calc(100% + 20px);
background-color: #ffffff;
.chat-emoji-box {
height: 246px;
> li {
width: 50px;
height: 50px;
line-height: 50px;
font-size: 28px;
}
&.emoticon > li {
width: 80px;
height: 80px;
padding: 8px;
}
}
.chat-emoji-menu {
border-radius: 0;
background-color: #f8f8f8;
padding: 4px;
width: calc(100% - 8px);
> li {
&.active {
&:before {
background-color: #e1e1e1;
}
}
}
}
}
}
}

View File

@ -687,14 +687,18 @@
display: flex; display: flex;
align-items: center; align-items: center;
.chat-input-wrapper { .chat-input-box {
flex: 1; flex: 1;
width: 0; width: 0;
display: flex;
flex-direction: column;
.chat-input-wrapper {
background-color: #F4F5F7; background-color: #F4F5F7;
padding: 10px 12px; padding: 10px 12px;
border-radius: 10px; border-radius: 10px;
} }
} }
}
.chat-upload { .chat-upload {
display: none; display: none;
@ -918,12 +922,14 @@
padding: 8px 10px; padding: 8px 10px;
margin-bottom: 0; margin-bottom: 0;
.dialog-input { .dialog-input {
.chat-input-box {
.chat-input-wrapper { .chat-input-wrapper {
padding-left: 6px; padding-left: 6px;
padding-right: 8px; padding-right: 6px;
background-color: #ffffff; background-color: #ffffff;
} }
} }
} }
} }
} }
}

View File

@ -570,15 +570,23 @@
align-items: center; align-items: center;
margin: 22px 0 0 36px; margin: 22px 0 0 36px;
background-color: #F4F5F7; background-color: #F4F5F7;
padding: 10px 12px; padding: 10px 11px;
border-radius: 10px; border-radius: 10px;
.chat-input-box {
.chat-input-wrapper {
padding: 0;
background-color: #F4F5F7;
}
}
.chat-input-toolbar { .chat-input-toolbar {
position: relative; position: relative;
.ivu-badge { .ivu-badge {
position: absolute; position: absolute;
transform: scale(0.6); transform: scale(0.6) translateX(100%);
top: -6px; transform-origin: right center;
right: 14px; top: -8px;
right: 10px;
z-index: 1;
} }
} }
} }