mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2025-12-19 01:15:53 +08:00
IM
This commit is contained in:
506
im/src/components/editor/MeEditor.vue
Normal file
506
im/src/components/editor/MeEditor.vue
Normal file
@@ -0,0 +1,506 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-container class="editor-container">
|
||||
<el-header class="no-padding toolbar" height="35px">
|
||||
<ul>
|
||||
<li v-popover:popoverEmoticon>
|
||||
<i class="iconfont icon-icon_im_face" style="font-size: 15px" />
|
||||
<p class="tip-title">表情符号</p>
|
||||
</li>
|
||||
<!-- <li @click="codeBlock.isShow = true">
|
||||
<i class="iconfont icon-daima" />
|
||||
<p class="tip-title">代码片段</p>
|
||||
</li>
|
||||
<li @click="recorder = true">
|
||||
<i class="el-icon-headset" />
|
||||
<p class="tip-title">语音消息</p>
|
||||
</li> -->
|
||||
<!-- #TODO 发图片功能暂时隐藏 此处会涉及到token过期 -->
|
||||
<!-- <li @click="$refs.restFile.click()">
|
||||
<i class="el-icon-picture-outline-round" />
|
||||
<p class="tip-title">图片</p>
|
||||
</li> -->
|
||||
<!-- <li @click="$refs.restFile2.click()">
|
||||
<i class="el-icon-folder" />
|
||||
<p class="tip-title">附件</p>
|
||||
</li>
|
||||
<li @click="filesManager.isShow = true">
|
||||
<i class="el-icon-folder-opened" />
|
||||
<p class="tip-title">上传管理</p>
|
||||
</li>
|
||||
<li v-show="isGroupTalk" @click="vote.isShow = true">
|
||||
<i class="el-icon-s-data" />
|
||||
<p class="tip-title">发起投票</p>
|
||||
</li> -->
|
||||
|
||||
<!-- <p class="text-tips no-select">-->
|
||||
<!-- <span>按Enter发送 / Shift+Enter 换行</span>-->
|
||||
<!-- <el-popover placement="top-end" width="600" trigger="click">-->
|
||||
<!-- <div class="editor-books">-->
|
||||
<!-- <div class="books-title">编辑说明:</div>-->
|
||||
<!-- <p>-->
|
||||
<!-- 1.-->
|
||||
<!-- 支持上传QQ及微信截图,在QQ或微信中截图后使用Ctrl+v上传图片。-->
|
||||
<!-- </p>-->
|
||||
<!-- <p>-->
|
||||
<!-- 2.-->
|
||||
<!-- 支持浏览器及Word文档中的图片复制上传、复制后使用Ctrl+v上传图片。-->
|
||||
<!-- </p>-->
|
||||
<!-- <p>3. 支持图片拖拽上传。</p>-->
|
||||
<!-- <p>4. 支持文件上传 ( 文件小于100M ) 。</p>-->
|
||||
<!-- <p>5. 按Enter发送 / Shift+Enter 换行。</p>-->
|
||||
<!-- <p>-->
|
||||
<!-- 6.-->
|
||||
<!-- 注意:当文件正在上传时,请勿关闭网页或离开当前对话框,否则将导致文件停止上传或上传失败。-->
|
||||
<!-- </p>-->
|
||||
<!-- </div>-->
|
||||
<!-- <i class="el-icon-info" slot="reference" />-->
|
||||
<!-- </el-popover>-->
|
||||
<!-- </p>-->
|
||||
</ul>
|
||||
|
||||
<el-popover
|
||||
ref="popoverEmoticon"
|
||||
placement="top-start"
|
||||
trigger="click"
|
||||
width="300"
|
||||
popper-class="no-padding el-popover-em"
|
||||
>
|
||||
<MeEditorEmoticon ref="editorEmoticon" @selected="selecteEmoticon" />
|
||||
</el-popover>
|
||||
|
||||
<form
|
||||
enctype="multipart/form-data"
|
||||
style="display: none"
|
||||
ref="fileFrom"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
ref="restFile"
|
||||
accept="image/*"
|
||||
@change="uploadImageChange"
|
||||
/>
|
||||
<input type="file" ref="restFile2" @change="uploadFileChange" />
|
||||
</form>
|
||||
</el-header>
|
||||
<el-main class="no-padding textarea">
|
||||
<textarea
|
||||
ref="textarea"
|
||||
v-paste="pasteImage"
|
||||
v-drag="dragPasteImage"
|
||||
v-model.trim="editorText"
|
||||
rows="6"
|
||||
placeholder="你想要的聊点什么呢 ..."
|
||||
@keydown="keydownEvent($event)"
|
||||
@input="inputEvent($event)"
|
||||
/>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<!-- 图片查看器 -->
|
||||
<MeEditorImageView
|
||||
ref="imageViewer"
|
||||
v-model="imageViewer.isShow"
|
||||
:file="imageViewer.file"
|
||||
@confirm="confirmUploadImage"
|
||||
/>
|
||||
|
||||
<MeEditorRecorder v-if="recorder" @close="recorder = false" />
|
||||
|
||||
<!-- 代码块编辑器 -->
|
||||
<TalkCodeBlock
|
||||
v-if="codeBlock.isShow"
|
||||
:edit-mode="codeBlock.editMode"
|
||||
@close="codeBlock.isShow = false"
|
||||
@confirm="confirmCodeBlock"
|
||||
/>
|
||||
|
||||
<!-- 文件上传管理器 -->
|
||||
<MeEditorFileManage ref="filesManager" v-model="filesManager.isShow" />
|
||||
|
||||
<MeEditorVote
|
||||
v-if="vote.isShow"
|
||||
@close="
|
||||
() => {
|
||||
this.vote.isShow = false;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MeEditorEmoticon from "./MeEditorEmoticon";
|
||||
import MeEditorFileManage from "./MeEditorFileManage";
|
||||
import MeEditorImageView from "./MeEditorImageView";
|
||||
import MeEditorRecorder from "./MeEditorRecorder";
|
||||
import MeEditorVote from "./MeEditorVote";
|
||||
import TalkCodeBlock from "@/components/chat/TalkCodeBlock";
|
||||
import { getPasteImgs, getDragPasteImg } from "@/utils/editor";
|
||||
import { findTalk } from "@/utils/talk";
|
||||
|
||||
import {
|
||||
ServeSendTalkCodeBlock,
|
||||
ServeSendTalkImage,
|
||||
ServeSendEmoticon,
|
||||
} from "@/api/chat";
|
||||
|
||||
export default {
|
||||
name: "MeEditor",
|
||||
components: {
|
||||
MeEditorEmoticon,
|
||||
MeEditorFileManage,
|
||||
MeEditorImageView,
|
||||
TalkCodeBlock,
|
||||
MeEditorRecorder,
|
||||
MeEditorVote,
|
||||
},
|
||||
computed: {
|
||||
talkUser() {
|
||||
return this.$store.state.dialogue.index_name;
|
||||
},
|
||||
isGroupTalk() {
|
||||
return this.$store.state.dialogue.talk_type == 2;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
talkUser(n_index_name) {
|
||||
this.$refs.filesManager.clear();
|
||||
this.editorText = this.getDraftText(n_index_name);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前编辑的内容
|
||||
editorText: "",
|
||||
|
||||
// 图片查看器相关信息
|
||||
imageViewer: {
|
||||
isShow: false,
|
||||
file: null,
|
||||
},
|
||||
|
||||
codeBlock: {
|
||||
isShow: false,
|
||||
editMode: true,
|
||||
},
|
||||
|
||||
filesManager: {
|
||||
isShow: false,
|
||||
},
|
||||
|
||||
vote: {
|
||||
isShow: false,
|
||||
},
|
||||
|
||||
// 录音器
|
||||
recorder: false,
|
||||
|
||||
// 上次发送消息的时间
|
||||
sendtime: 0,
|
||||
|
||||
// 发送间隔时间(默认1秒)
|
||||
interval: 1000,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 读取对话编辑草稿信息 并赋值给当前富文本
|
||||
getDraftText(index_name) {
|
||||
console.log("findTalk(index_name)", findTalk(index_name));
|
||||
return findTalk(index_name)?.draft_text || "";
|
||||
},
|
||||
|
||||
//复制粘贴图片回调方法
|
||||
pasteImage(e) {
|
||||
let files = getPasteImgs(e);
|
||||
if (files.length == 0) return;
|
||||
|
||||
this.openImageViewer(files[0]);
|
||||
},
|
||||
|
||||
//拖拽上传图片回调方法
|
||||
dragPasteImage(e) {
|
||||
let files = getDragPasteImg(e);
|
||||
if (files.length == 0) return;
|
||||
|
||||
this.openImageViewer(files[0]);
|
||||
},
|
||||
|
||||
inputEvent(e) {
|
||||
this.$emit("keyboard-event", e.target.value);
|
||||
},
|
||||
|
||||
// 键盘按下监听事件
|
||||
keydownEvent(e) {
|
||||
if (e.keyCode == 13 && this.editorText == "") {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// 回车发送消息
|
||||
if (e.keyCode == 13 && e.shiftKey == false && this.editorText != "") {
|
||||
let currentTime = new Date().getTime();
|
||||
|
||||
if (this.sendtime > 0) {
|
||||
// 判断 1秒内只能发送一条消息
|
||||
if (currentTime - this.sendtime < this.interval) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit("send", this.editorText);
|
||||
this.editorText = "";
|
||||
this.sendtime = currentTime;
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
// 选择图片文件后回调方法
|
||||
uploadImageChange(e) {
|
||||
this.openImageViewer(e.target.files[0]);
|
||||
this.$refs.restFile.value = null;
|
||||
},
|
||||
|
||||
// 选择文件回调事件
|
||||
uploadFileChange(e) {
|
||||
let maxsize = 100 * 1024 * 1024;
|
||||
if (e.target.files.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let file = e.target.files[0];
|
||||
if (/\.(gif|jpg|jpeg|png|webp|GIF|JPG|PNG|WEBP)$/.test(file.name)) {
|
||||
this.openImageViewer(file);
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > maxsize) {
|
||||
this.$notify.info({
|
||||
title: "消息",
|
||||
message: "上传文件不能大于100M",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.filesManager.isShow = true;
|
||||
this.$refs.restFile2.value = null;
|
||||
this.$refs.filesManager.upload(file);
|
||||
},
|
||||
|
||||
// 打开图片查看器
|
||||
openImageViewer(file) {
|
||||
this.imageViewer.isShow = true;
|
||||
this.imageViewer.file = file;
|
||||
},
|
||||
|
||||
// 代码块编辑器确认完成回调事件
|
||||
confirmCodeBlock(data) {
|
||||
const { talk_type, receiver_id } = this.$store.state.dialogue;
|
||||
ServeSendTalkCodeBlock({
|
||||
talk_type,
|
||||
receiver_id,
|
||||
code: data.code,
|
||||
lang: data.language,
|
||||
}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
this.codeBlock.isShow = false;
|
||||
} else {
|
||||
this.$notify({
|
||||
title: "友情提示",
|
||||
message: res.message,
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 确认上传图片消息回调事件
|
||||
confirmUploadImage() {
|
||||
let fileData = new FormData();
|
||||
fileData.append("file", this.imageViewer.file);
|
||||
|
||||
let ref = this.$refs.imageViewer;
|
||||
|
||||
ServeSendTalkImage(fileData)
|
||||
.then((res) => {
|
||||
ref.loading = false;
|
||||
if (res.code == 200) {
|
||||
ref.closeBox();
|
||||
} else {
|
||||
this.$notify({
|
||||
title: "友情提示",
|
||||
message: res.message,
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
ref.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
// 选中表情包回调事件
|
||||
selecteEmoticon(data) {
|
||||
if (data.type == 1) {
|
||||
let value = this.editorText;
|
||||
let el = this.$refs.textarea;
|
||||
let startPos = el.selectionStart;
|
||||
let endPos = el.selectionEnd;
|
||||
let newValue =
|
||||
value.substring(0, startPos) +
|
||||
data.value +
|
||||
value.substring(endPos, value.length);
|
||||
|
||||
this.editorText = newValue;
|
||||
|
||||
if (el.setSelectionRange) {
|
||||
setTimeout(() => {
|
||||
let index = startPos + data.value.length;
|
||||
el.setSelectionRange(index, index);
|
||||
el.focus();
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
const { talk_type, receiver_id } = this.$store.state.dialogue;
|
||||
ServeSendEmoticon({
|
||||
talk_type,
|
||||
receiver_id,
|
||||
emoticon_id: data.value,
|
||||
});
|
||||
}
|
||||
|
||||
this.$refs.popoverEmoticon.doClose();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.editor-container {
|
||||
height: 160px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.editor-container .toolbar {
|
||||
line-height: 35px;
|
||||
border-bottom: 1px solid #f5f0f0;
|
||||
border-top: 1px solid #f5f0f0;
|
||||
}
|
||||
|
||||
.editor-container .toolbar li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
width: 35px;
|
||||
margin-left: 3px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 35px;
|
||||
position: relative;
|
||||
color: #8d8d8d;
|
||||
}
|
||||
|
||||
.editor-container .toolbar li .tip-title {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
background-color: rgba(31, 35, 41, 0.9);
|
||||
color: white;
|
||||
min-width: 30px;
|
||||
font-size: 10px;
|
||||
padding: 0 5px;
|
||||
border-radius: 2px;
|
||||
white-space: pre;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.editor-container .toolbar li:hover .tip-title {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.editor-container .toolbar li:hover {
|
||||
background-color: #f7f5f5;
|
||||
}
|
||||
|
||||
.editor-container .toolbar .text-tips {
|
||||
float: right;
|
||||
margin-right: 15px;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.editor-container .toolbar .text-tips i {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
color: rgb(255, 181, 111);
|
||||
}
|
||||
|
||||
.editor-container .textarea {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: calc(100% - 10px);
|
||||
width: -moz-calc(100% - 10px);
|
||||
width: -webkit-calc(100% - 10px);
|
||||
height: calc(100% - 10px);
|
||||
height: -moz-calc(100% - 10px);
|
||||
height: -webkit-calc(100% - 10px);
|
||||
border: 0 none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-size: 15px;
|
||||
overflow-y: auto;
|
||||
color: #464545;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar-thumb {
|
||||
background: #d5cfcf;
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar-track {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
textarea::-webkit-input-placeholder {
|
||||
color: #dccdcd;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 编辑器文档说明 --- start */
|
||||
.editor-books .books-title {
|
||||
font-size: 16px;
|
||||
height: 30px;
|
||||
line-height: 22px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #cbcbcb;
|
||||
color: #726f6f;
|
||||
font-weight: 400;
|
||||
margin-left: 11px;
|
||||
}
|
||||
|
||||
.editor-books p {
|
||||
text-indent: 10px;
|
||||
font-size: 12px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
color: #7f7c7c;
|
||||
}
|
||||
|
||||
/* 编辑器文档说明 --- end */
|
||||
</style>
|
||||
345
im/src/components/editor/MeEditorEmoticon.vue
Normal file
345
im/src/components/editor/MeEditorEmoticon.vue
Normal file
@@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-container class="container">
|
||||
<el-main class="no-padding main lum-scrollbar">
|
||||
<input
|
||||
type="file"
|
||||
ref="fileCustomEmoji"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="customUploadEmoji"
|
||||
/>
|
||||
|
||||
<div v-show="showEmoticonId == -1" class="emoticon">
|
||||
<div class="title">QQ表情</div>
|
||||
<div
|
||||
v-for="(elImg, text) in emoji.emojis"
|
||||
v-html="elImg"
|
||||
:key="text"
|
||||
class="emoticon-item"
|
||||
@click="clickEmoticon(text)"
|
||||
></div>
|
||||
<div class="clear"></div>
|
||||
<div class="title">符号表情</div>
|
||||
<div
|
||||
v-for="(item, i) in emoji.symbol"
|
||||
:key="i"
|
||||
class="emoticon-item symbol"
|
||||
@click="clickEmoticon(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="item in emojiItem.slice(1)"
|
||||
v-show="item.emoticon_id == showEmoticonId"
|
||||
:key="item.emoticon_id"
|
||||
class="emoji-box"
|
||||
>
|
||||
<div
|
||||
v-if="item.emoticon_id == 0"
|
||||
class="emoji-item custom-emoji"
|
||||
@click="$refs.fileCustomEmoji.click()"
|
||||
>
|
||||
<i class="el-icon-picture" />
|
||||
<span>自定义</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="subitem in item.list"
|
||||
:key="subitem.src"
|
||||
class="emoji-item"
|
||||
@click="clickImageEmoticon(subitem)"
|
||||
>
|
||||
<el-image :src="subitem.src" fit="cover" />
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</el-main>
|
||||
<!-- <el-footer height="40px" class="no-padding footer">
|
||||
<div class="toolbar-items">
|
||||
<div
|
||||
v-show="emojiItem.length > 13"
|
||||
class="toolbar-item prev-page"
|
||||
@click="turnPage(1)"
|
||||
>
|
||||
<i class="el-icon-caret-left" />
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in showItems"
|
||||
:key="index"
|
||||
class="toolbar-item"
|
||||
@click="triggerItem(item)"
|
||||
>
|
||||
<img :src="item.url" />
|
||||
<p class="title">{{ item.name }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-show="emojiItem.length > 13 && showItems.length == 13"
|
||||
class="toolbar-item next-page"
|
||||
@click="turnPage(2)"
|
||||
>
|
||||
<i class="el-icon-caret-right" />
|
||||
</div>
|
||||
</div>
|
||||
</el-footer> -->
|
||||
</el-container>
|
||||
|
||||
<MeEditorSystemEmoticon
|
||||
v-if="systemEmojiBox"
|
||||
@close="systemEmojiBox = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MeEditorSystemEmoticon from "@/components/editor/MeEditorSystemEmoticon";
|
||||
import { emojiList as emoji } from "@/utils/emojis";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "MeEditorEmoticon",
|
||||
components: {
|
||||
MeEditorSystemEmoticon,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
emojiItem: (state) => state.emoticon.items,
|
||||
}),
|
||||
showItems() {
|
||||
let start = (this.page - 1) * this.pageSize;
|
||||
let end = start + this.pageSize;
|
||||
return this.emojiItem.slice(start, end);
|
||||
},
|
||||
pageTotal() {
|
||||
return this.emojiItem.length / this.pageSize;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
emoji,
|
||||
|
||||
// 系统表情包套弹出窗
|
||||
systemEmojiBox: false,
|
||||
|
||||
showEmoticonId: -1,
|
||||
showTitle: "QQ表情/符号表情",
|
||||
|
||||
page: 1,
|
||||
pageSize: 13,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$store.commit("LOAD_USER_EMOTICON");
|
||||
},
|
||||
methods: {
|
||||
// 表情包导航翻页
|
||||
turnPage(type) {
|
||||
if (type == 1) {
|
||||
if (this.page == 1) return false;
|
||||
this.page--;
|
||||
} else {
|
||||
if (this.page >= this.pageTotal) return false;
|
||||
this.page++;
|
||||
}
|
||||
},
|
||||
|
||||
// 点击表情包导航
|
||||
triggerItem(item) {
|
||||
this.showEmoticonId = item.emoticon_id;
|
||||
this.showTitle = item.name;
|
||||
},
|
||||
|
||||
// 选中表情
|
||||
clickEmoticon(emoji) {
|
||||
this.callback({
|
||||
type: 1,
|
||||
value: emoji,
|
||||
});
|
||||
},
|
||||
|
||||
// 发送图片表情包
|
||||
clickImageEmoticon(item) {
|
||||
this.callback({
|
||||
type: 2,
|
||||
value: item.media_id,
|
||||
});
|
||||
},
|
||||
|
||||
callback(data) {
|
||||
this.$emit("selected", data);
|
||||
},
|
||||
|
||||
// 自定义上传表情
|
||||
customUploadEmoji(e) {
|
||||
if (e.target.files.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.$store.commit("UPLOAD_USER_EMOTICON", {
|
||||
file: e.target.files[0],
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
height: 300px;
|
||||
max-width: 500px;
|
||||
background-color: white;
|
||||
|
||||
.header {
|
||||
line-height: 30px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
padding-left: 5px;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #fbf5f5;
|
||||
|
||||
.addbtn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 1px;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #eff1f7;
|
||||
|
||||
.toolbar-items {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.toolbar-item {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin: 0 2px;
|
||||
background-color: #fff;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
left: 0px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
background: #353434;
|
||||
color: white;
|
||||
min-width: 30px;
|
||||
font-size: 10px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-radius: 2px;
|
||||
white-space: pre;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:hover .title {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container .footer .toolbar-items .prev-page:active i,
|
||||
.container .footer .toolbar-items .next-page:active i {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.emoji-box,
|
||||
.emoticon {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.emoticon {
|
||||
.title {
|
||||
width: 50%;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
color: #ccc;
|
||||
font-weight: 400;
|
||||
padding-left: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.emoticon-item {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 2px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
}
|
||||
|
||||
.symbol {
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-box {
|
||||
.emoji-item {
|
||||
width: 67px;
|
||||
height: 67px;
|
||||
margin: 2px;
|
||||
background-color: #eff1f7;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
transition: ease-in 0.3s;
|
||||
}
|
||||
|
||||
.custom-emoji {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-size: 10px;
|
||||
|
||||
i {
|
||||
font-size: 30px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: ease-in 0.3s;
|
||||
}
|
||||
|
||||
.emoji-box .emoji-item:hover .el-image,
|
||||
.emoji-box .emoji-item:hover {
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
440
im/src/components/editor/MeEditorFileManage.vue
Normal file
440
im/src/components/editor/MeEditorFileManage.vue
Normal file
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<el-container
|
||||
class="container animated bounceInUp"
|
||||
v-outside="closeBox"
|
||||
v-if="show"
|
||||
>
|
||||
<el-header class="no-padding header" height="50px">
|
||||
<p>
|
||||
上传管理 <span v-show="total">({{ successNum }}/{{ total }})</span>
|
||||
</p>
|
||||
<i class="close-btn el-icon-close" @click="closeBox" />
|
||||
</el-header>
|
||||
|
||||
<el-main class="no-padding mian lum-scrollbar">
|
||||
<div class="empty-data" v-show="total == 0">
|
||||
<SvgNotData />
|
||||
<p>暂无上传文件</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="file in items"
|
||||
v-show="!file.isDelete"
|
||||
:key="file.hashName"
|
||||
class="file-item"
|
||||
>
|
||||
<div class="file-header">
|
||||
<div class="type-icon">{{ file.ext }}</div>
|
||||
<el-tooltip :content="file.filename" placement="top-start">
|
||||
<div class="filename">{{ file.filename }}</div>
|
||||
</el-tooltip>
|
||||
|
||||
<div class="status">
|
||||
<span v-if="file.status == 0">等待上传</span>
|
||||
<span v-else-if="file.status == 1" style="color: #66b1ff">
|
||||
正在上传...
|
||||
</span>
|
||||
<span v-else-if="file.status == 2" style="color: #67c23a">
|
||||
已完成
|
||||
</span>
|
||||
<span v-else style="color: red">网络异常</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-mian">
|
||||
<div class="progress">
|
||||
<el-progress
|
||||
type="dashboard"
|
||||
:percentage="file.progress"
|
||||
:width="50"
|
||||
:color="colors"
|
||||
/>
|
||||
<span class="name">上传进度</span>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<p>
|
||||
文件类型:<span>{{ file.filetype }}</span>
|
||||
</p>
|
||||
<p>
|
||||
文件大小:<span>{{ file.filesize }}</span>
|
||||
</p>
|
||||
<p>
|
||||
上传时间:<span>{{ file.datetime }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="file.status == 2 || file.status == 3" class="file-means">
|
||||
<div class="btns" @click="removeFile(file.hashName)">删除</div>
|
||||
<div
|
||||
v-show="file.status == 3"
|
||||
class="btns"
|
||||
@click="triggerUpload(file.hashName)"
|
||||
>
|
||||
继续上传
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { SvgNotData } from '@/core/icons'
|
||||
import { Progress } from 'element-ui'
|
||||
Vue.use(Progress)
|
||||
|
||||
import { ServeFindFileSplitInfo, ServeFileSubareaUpload } from '@/api/upload'
|
||||
import { formatSize, getFileExt, parseTime } from '@/utils/functions'
|
||||
import { ServeSendTalkFile } from '@/api/chat'
|
||||
|
||||
export default {
|
||||
name: 'MeEditorFileManage',
|
||||
model: {
|
||||
prop: 'show',
|
||||
event: 'close',
|
||||
},
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
components: {
|
||||
SvgNotData,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
colors: [
|
||||
{
|
||||
color: '#f56c6c',
|
||||
percentage: 20,
|
||||
},
|
||||
{
|
||||
color: '#e6a23c',
|
||||
percentage: 40,
|
||||
},
|
||||
{
|
||||
color: '#5cb87a',
|
||||
percentage: 60,
|
||||
},
|
||||
{
|
||||
color: '#1989fa',
|
||||
percentage: 80,
|
||||
},
|
||||
{
|
||||
color: '#11ce65',
|
||||
percentage: 100,
|
||||
},
|
||||
],
|
||||
|
||||
items: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
total() {
|
||||
return this.items.filter(item => {
|
||||
return item.isDelete === false
|
||||
}).length
|
||||
},
|
||||
successNum() {
|
||||
return this.items.filter(item => {
|
||||
return item.isDelete === false && item.status == 2
|
||||
}).length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeBox() {
|
||||
this.$emit('close', false)
|
||||
},
|
||||
|
||||
upload(file) {
|
||||
ServeFindFileSplitInfo({
|
||||
file_name: file.name,
|
||||
file_size: file.size,
|
||||
}).then(res => {
|
||||
if (res.code == 200) {
|
||||
const { hash_name, split_size } = res.data
|
||||
|
||||
this.items.unshift({
|
||||
hashName: hash_name,
|
||||
originalFile: file,
|
||||
filename: file.name,
|
||||
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
|
||||
progress: 0,
|
||||
filesize: formatSize(file.size),
|
||||
filetype: file.type || '未知',
|
||||
datetime: parseTime(new Date(), '{m}-{d} {h}:{i}'),
|
||||
ext: getFileExt(file.name),
|
||||
forms: this.fileSlice(file, hash_name, split_size),
|
||||
successNum: 0,
|
||||
isDelete: false,
|
||||
})
|
||||
|
||||
this.triggerUpload(hash_name)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 处理拆分上传文件
|
||||
fileSlice(file, hash, eachSize) {
|
||||
const ext = getFileExt(file.name)
|
||||
const splitNum = Math.ceil(file.size / eachSize) // 分片总数
|
||||
const forms = []
|
||||
|
||||
// 处理每个分片的上传操作
|
||||
for (let i = 0; i < splitNum; i++) {
|
||||
let start = i * eachSize
|
||||
let end = Math.min(file.size, start + eachSize)
|
||||
|
||||
// 构建表单
|
||||
const form = new FormData()
|
||||
form.append('file', file.slice(start, end))
|
||||
form.append('name', file.name)
|
||||
form.append('hash', hash)
|
||||
form.append('ext', ext)
|
||||
form.append('size', file.size)
|
||||
form.append('split_index', i)
|
||||
form.append('split_num', splitNum)
|
||||
forms.push(form)
|
||||
}
|
||||
|
||||
return forms
|
||||
},
|
||||
|
||||
// 触发上传文件
|
||||
triggerUpload(hashName) {
|
||||
let $index = this.getFileIndex(hashName)
|
||||
if ($index < 0 || this.items[$index].isDelte) {
|
||||
return
|
||||
}
|
||||
|
||||
let i = this.items[$index].successNum
|
||||
let form = this.items[$index].forms[i]
|
||||
let length = this.items[$index].forms.length
|
||||
this.items[$index].status = 1
|
||||
ServeFileSubareaUpload(form)
|
||||
.then(res => {
|
||||
if (res.code == 200) {
|
||||
$index = this.getFileIndex(hashName)
|
||||
this.items[$index].successNum++
|
||||
this.items[$index].progress = Math.floor(
|
||||
(this.items[$index].successNum / length) * 100
|
||||
)
|
||||
if (this.items[$index].successNum == length) {
|
||||
this.items[$index].status = 2
|
||||
if (res.data.is_file_merge) {
|
||||
ServeSendTalkFile({
|
||||
hash_name: res.data.hash,
|
||||
receiver_id: this.$store.state.dialogue.receiver_id,
|
||||
talk_type: this.$store.state.dialogue.talk_type,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.triggerUpload(hashName)
|
||||
}
|
||||
} else {
|
||||
this.items[$index].status = 3
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
$index = this.getFileIndex(hashName)
|
||||
this.items[$index].status = 3
|
||||
})
|
||||
},
|
||||
|
||||
// 获取分片文件数组索引
|
||||
getFileIndex(hashName) {
|
||||
return this.items.findIndex(item => {
|
||||
return item.hashName === hashName
|
||||
})
|
||||
},
|
||||
|
||||
removeFile(hashName) {
|
||||
let index = this.getFileIndex(hashName)
|
||||
this.items[index].isDelete = true
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.items = []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 400px;
|
||||
height: 600px;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 5px #eae5e5;
|
||||
border: 1px solid #eae5e5;
|
||||
overflow: hidden;
|
||||
border-radius: 3px 3px 0 0;
|
||||
|
||||
.header {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
position: relative;
|
||||
text-indent: 20px;
|
||||
border-bottom: 1px solid #f5eeee;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 15px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mian {
|
||||
.empty-data {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 50%;
|
||||
|
||||
svg {
|
||||
font-size: 70px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 30px;
|
||||
color: #cccccc;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-item {
|
||||
width: 95%;
|
||||
min-height: 100px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 5px;
|
||||
margin: 15px auto;
|
||||
box-shadow: 0 0 5px #eae5e5;
|
||||
overflow: hidden;
|
||||
|
||||
.file-header {
|
||||
height: 45px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #f7f4f4;
|
||||
|
||||
.type-icon {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background-color: #66b1ff;
|
||||
border-radius: 50%;
|
||||
margin-left: 5px;
|
||||
font-size: 10px;
|
||||
font-weight: 200;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.filename {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
width: 65%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status {
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
top: 12px;
|
||||
font-size: 13px;
|
||||
color: #6b6868;
|
||||
font-weight: 200;
|
||||
}
|
||||
}
|
||||
|
||||
.file-mian {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.progress {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
flex-shrink: 0;
|
||||
background: #f9f6f6;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
|
||||
.name {
|
||||
font-size: 12px;
|
||||
color: #ada8a8;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
.detail {
|
||||
flex: auto;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 20px;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
font-size: 13px;
|
||||
|
||||
p {
|
||||
margin: 3px;
|
||||
color: #ada8a8;
|
||||
|
||||
span {
|
||||
color: #595a5a;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-means {
|
||||
width: 96.5%;
|
||||
height: 35px;
|
||||
border-top: 1px dashed rgb(234, 227, 227);
|
||||
margin: 3px auto;
|
||||
padding-top: 5px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
.btns {
|
||||
width: 80px;
|
||||
height: 25px;
|
||||
border: 1px solid #e6e1e1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 3px;
|
||||
border-radius: 15px;
|
||||
font-size: 12px;
|
||||
color: #635f5f;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 0 5px #eae5e5;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
133
im/src/components/editor/MeEditorImageView.vue
Normal file
133
im/src/components/editor/MeEditorImageView.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div v-if="show" class="lum-dialog-mask animated fadeIn">
|
||||
<el-container class="lum-dialog-box" v-outside="closeBox">
|
||||
<el-header class="no-padding header" height="50px">
|
||||
<p>发送图片</p>
|
||||
<p class="tools">
|
||||
<i class="el-icon-close" @click="closeBox" />
|
||||
</p>
|
||||
</el-header>
|
||||
|
||||
<el-main class="no-padding mian">
|
||||
<img v-show="src" :src="src" />
|
||||
<div v-show="src">
|
||||
<span class="filename">{{ fileName }}</span>
|
||||
<br />
|
||||
<span class="size">{{ fileSize }} KB</span>
|
||||
</div>
|
||||
</el-main>
|
||||
|
||||
<el-footer class="footer" height="50px">
|
||||
<el-button
|
||||
class="btn"
|
||||
type="primary"
|
||||
size="medium"
|
||||
:loading="loading"
|
||||
@click="uploadImage"
|
||||
>立即发送
|
||||
</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'MeEditorImageView',
|
||||
model: {
|
||||
prop: 'show',
|
||||
event: 'close',
|
||||
},
|
||||
props: {
|
||||
show: Boolean,
|
||||
file: File,
|
||||
},
|
||||
watch: {
|
||||
file(file) {
|
||||
this.loadFile(file)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
src: '',
|
||||
fileSize: '',
|
||||
fileName: '',
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeBox() {
|
||||
if (this.loading) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.$emit('close', false)
|
||||
},
|
||||
loadFile(file) {
|
||||
let reader = new FileReader()
|
||||
this.fileSize = Math.ceil(file.size / 1024)
|
||||
this.fileName = file.name
|
||||
reader.onload = () => {
|
||||
this.src = reader.result
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
},
|
||||
|
||||
// 确认按钮事件
|
||||
uploadImage() {
|
||||
this.loading = true
|
||||
this.$emit('confirm')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.lum-dialog-box {
|
||||
width: 500px;
|
||||
max-width: 500px;
|
||||
height: 450px;
|
||||
|
||||
.mian {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
img {
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 8px #e0dbdb;
|
||||
}
|
||||
|
||||
div {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
max-width: 80%;
|
||||
|
||||
.filename {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.size {
|
||||
color: rgb(148, 140, 140);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 50px;
|
||||
background: rgba(247, 245, 245, 0.66);
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
|
||||
.btn {
|
||||
width: 150px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
517
im/src/components/editor/MeEditorRecorder.vue
Normal file
517
im/src/components/editor/MeEditorRecorder.vue
Normal file
@@ -0,0 +1,517 @@
|
||||
<template>
|
||||
<div class="lum-dialog-mask animated fadeIn">
|
||||
<el-container class="lum-dialog-box">
|
||||
<el-header class="no-padding header no-select" height="50px">
|
||||
<p>语音消息</p>
|
||||
<p class="tools"><i class="el-icon-close" @click="closeBox" /></p>
|
||||
</el-header>
|
||||
|
||||
<el-main class="no-padding mian">
|
||||
<div class="music">
|
||||
<span class="line line1" :class="{ 'line-ani': animation }"></span>
|
||||
<span class="line line2" :class="{ 'line-ani': animation }"></span>
|
||||
<span class="line line3" :class="{ 'line-ani': animation }"></span>
|
||||
<span class="line line4" :class="{ 'line-ani': animation }"></span>
|
||||
<span class="line line5" :class="{ 'line-ani': animation }"></span>
|
||||
</div>
|
||||
<div style="margin-top: 35px; color: #676262; font-weight: 300">
|
||||
<template v-if="recorderStatus == 0">
|
||||
<p style="font-size: 13px; margin-top: 5px">
|
||||
<span>语音消息,让聊天更简单方便 ...</span>
|
||||
</p>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
recorderStatus == 1 || recorderStatus == 2 || recorderStatus == 3
|
||||
"
|
||||
>
|
||||
<p>{{ datetime }}</p>
|
||||
<p style="font-size: 13px; margin-top: 5px">
|
||||
<span v-if="recorderStatus == 1">正在录音</span>
|
||||
<span v-else-if="recorderStatus == 2">已暂停录音</span>
|
||||
<span v-else-if="recorderStatus == 3">录音时长</span>
|
||||
</p>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
recorderStatus == 4 || recorderStatus == 5 || recorderStatus == 6
|
||||
"
|
||||
>
|
||||
<p>{{ formatPlayTime }}</p>
|
||||
<p style="font-size: 13px; margin-top: 5px">
|
||||
<span v-if="recorderStatus == 4">正在播放</span>
|
||||
<span v-else-if="recorderStatus == 5">已暂停播放</span>
|
||||
<span v-else-if="recorderStatus == 6">播放已结束</span>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</el-main>
|
||||
|
||||
<el-footer class="footer" height="50px">
|
||||
<!-- 0:未开始录音 1:正在录音 2:暂停录音 3:结束录音 4:播放录音 5:停止播放 -->
|
||||
<el-button
|
||||
v-show="recorderStatus == 0"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-microphone"
|
||||
@click="startRecorder"
|
||||
>开始录音
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="recorderStatus == 1"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-video-pause"
|
||||
@click="pauseRecorder"
|
||||
>暂停录音
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="recorderStatus == 2"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-microphone"
|
||||
@click="resumeRecorder"
|
||||
>继续录音
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="recorderStatus == 2"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-microphone"
|
||||
@click="stopRecorder"
|
||||
>结束录音
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="recorderStatus == 3 || recorderStatus == 6"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-video-play"
|
||||
@click="playRecorder"
|
||||
>播放录音
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-show="
|
||||
recorderStatus == 3 || recorderStatus == 5 || recorderStatus == 6
|
||||
"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-video-play"
|
||||
@click="startRecorder"
|
||||
>重新录音
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-show="recorderStatus == 4"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-video-pause"
|
||||
@click="pausePlayRecorder"
|
||||
>暂停播放
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="recorderStatus == 5"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-video-play"
|
||||
@click="resumePlayRecorder"
|
||||
>继续播放
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-show="
|
||||
recorderStatus == 3 || recorderStatus == 5 || recorderStatus == 6
|
||||
"
|
||||
type="primary"
|
||||
size="mini"
|
||||
round
|
||||
@click="submit"
|
||||
>立即发送
|
||||
</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Recorder from 'js-audio-recorder'
|
||||
|
||||
export default {
|
||||
name: 'MeEditorRecorder',
|
||||
data() {
|
||||
return {
|
||||
// 录音实例
|
||||
recorder: null,
|
||||
|
||||
// 录音时长
|
||||
duration: 0,
|
||||
|
||||
// 播放时长
|
||||
playTime: 0,
|
||||
|
||||
animation: false,
|
||||
|
||||
// 当前状态
|
||||
recorderStatus: 0, //0:未开始录音 1:正在录音 2:暂停录音 3:结束录音 4:播放录音 5:停止播放 6:播放结束
|
||||
|
||||
playTimeout: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
datetime() {
|
||||
let hour = parseInt((this.duration / 60 / 60) % 24) //小时
|
||||
let minute = parseInt((this.duration / 60) % 60) //分钟
|
||||
let seconds = parseInt(this.duration % 60) //秒
|
||||
|
||||
if (hour < 10) hour = `0${hour}`
|
||||
if (minute < 10) minute = `0${minute}`
|
||||
if (seconds < 10) seconds = `0${seconds}`
|
||||
|
||||
return `${hour}:${minute}:${seconds}`
|
||||
},
|
||||
formatPlayTime() {
|
||||
let hour = parseInt((this.playTime / 60 / 60) % 24) //小时
|
||||
let minute = parseInt((this.playTime / 60) % 60) //分钟
|
||||
let seconds = parseInt(this.playTime % 60) //秒
|
||||
|
||||
if (hour < 10) hour = `0${hour}`
|
||||
if (minute < 10) minute = `0${minute}`
|
||||
if (seconds < 10) seconds = `0${seconds}`
|
||||
|
||||
return `${hour}:${minute}:${seconds}`
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
if (this.recorder) {
|
||||
this.destroyRecorder()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeBox() {
|
||||
if (this.recorder == null) {
|
||||
this.$emit('close', false)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.recorderStatus == 1) {
|
||||
this.stopRecorder()
|
||||
} else if (this.recorderStatus == 4) {
|
||||
this.pausePlayRecorder()
|
||||
}
|
||||
|
||||
// 销毁录音后关闭窗口
|
||||
this.destroyRecorder(() => {
|
||||
this.$emit('close', false)
|
||||
})
|
||||
},
|
||||
|
||||
// 开始录音
|
||||
startRecorder() {
|
||||
let _this = this
|
||||
// http://recorder.api.zhuyuntao.cn/Recorder/event.html
|
||||
// https://blog.csdn.net/qq_41619796/article/details/107865602
|
||||
this.recorder = new Recorder()
|
||||
this.recorder.onprocess = duration => {
|
||||
duration = parseInt(duration)
|
||||
_this.duration = duration
|
||||
}
|
||||
|
||||
this.recorder.start().then(
|
||||
() => {
|
||||
this.recorderStatus = 1
|
||||
this.animation = true
|
||||
},
|
||||
error => {
|
||||
console.log(`${error.name} : ${error.message}`)
|
||||
}
|
||||
)
|
||||
},
|
||||
// 暂停录音
|
||||
pauseRecorder() {
|
||||
this.recorder.pause()
|
||||
this.recorderStatus = 2
|
||||
this.animation = false
|
||||
},
|
||||
// 继续录音
|
||||
resumeRecorder() {
|
||||
this.recorderStatus = 1
|
||||
this.recorder.resume()
|
||||
this.animation = true
|
||||
},
|
||||
// 结束录音
|
||||
stopRecorder() {
|
||||
this.recorderStatus = 3
|
||||
this.recorder.stop()
|
||||
this.animation = false
|
||||
},
|
||||
// 录音播放
|
||||
playRecorder() {
|
||||
this.recorderStatus = 4
|
||||
this.recorder.play()
|
||||
this.playTimeouts()
|
||||
this.animation = true
|
||||
},
|
||||
// 暂停录音播放
|
||||
pausePlayRecorder() {
|
||||
this.recorderStatus = 5
|
||||
this.recorder.pausePlay()
|
||||
clearInterval(this.playTimeout)
|
||||
this.animation = false
|
||||
},
|
||||
// 恢复录音播放
|
||||
resumePlayRecorder() {
|
||||
this.recorderStatus = 4
|
||||
this.recorder.resumePlay()
|
||||
this.playTimeouts()
|
||||
this.animation = true
|
||||
},
|
||||
// 销毁录音
|
||||
destroyRecorder(callBack) {
|
||||
this.recorder.destroy().then(() => {
|
||||
this.recorder = null
|
||||
if (callBack) {
|
||||
callBack()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取录音文件大小(单位:字节)
|
||||
recorderSize() {
|
||||
return this.recorder.fileSize
|
||||
},
|
||||
|
||||
playTimeouts() {
|
||||
this.playTimeout = setInterval(() => {
|
||||
let time = parseInt(this.recorder.getPlayTime())
|
||||
this.playTime = time
|
||||
if (time == this.duration) {
|
||||
clearInterval(this.playTimeout)
|
||||
this.animation = false
|
||||
this.recorderStatus = 6
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
|
||||
submit() {
|
||||
alert('功能研发中,敬请期待...')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.lum-dialog-box {
|
||||
width: 500px;
|
||||
max-width: 500px;
|
||||
height: 450px;
|
||||
|
||||
.mian {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
border-top: 1px solid #f7f3f3;
|
||||
}
|
||||
}
|
||||
|
||||
.music {
|
||||
position: relative;
|
||||
width: 180px;
|
||||
height: 160px;
|
||||
border: 8px solid #bebebe;
|
||||
border-bottom: 0px;
|
||||
border-top-left-radius: 110px;
|
||||
border-top-right-radius: 110px;
|
||||
}
|
||||
|
||||
.music:before,
|
||||
.music:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
width: 40px;
|
||||
height: 82px;
|
||||
background-color: #bebebe;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.music:before {
|
||||
right: -25px;
|
||||
}
|
||||
|
||||
.music:after {
|
||||
left: -25px;
|
||||
}
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
min-height: 30px;
|
||||
transition: 0.5s;
|
||||
|
||||
vertical-align: middle;
|
||||
bottom: 0 !important;
|
||||
box-shadow: inset 0px 0px 16px -2px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.line-ani {
|
||||
animation: equalize 4s 0s infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
.line1 {
|
||||
left: 30%;
|
||||
bottom: 0px;
|
||||
animation-delay: -1.9s;
|
||||
background-color: #ff5e50;
|
||||
}
|
||||
|
||||
.line2 {
|
||||
left: 40%;
|
||||
height: 60px;
|
||||
bottom: -15px;
|
||||
animation-delay: -2.9s;
|
||||
background-color: #a64de6;
|
||||
}
|
||||
|
||||
.line3 {
|
||||
left: 50%;
|
||||
height: 30px;
|
||||
bottom: -1.5px;
|
||||
animation-delay: -3.9s;
|
||||
background-color: #5968dc;
|
||||
}
|
||||
|
||||
.line4 {
|
||||
left: 60%;
|
||||
height: 65px;
|
||||
bottom: -16px;
|
||||
animation-delay: -4.9s;
|
||||
background-color: #27c8f8;
|
||||
}
|
||||
|
||||
.line5 {
|
||||
left: 70%;
|
||||
height: 60px;
|
||||
bottom: -12px;
|
||||
animation-delay: -5.9s;
|
||||
background-color: #cc60b5;
|
||||
}
|
||||
|
||||
@keyframes equalize {
|
||||
0% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
4% {
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
8% {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
12% {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
16% {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
20% {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
24% {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
28% {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
32% {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
36% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
40% {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
44% {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
48% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
52% {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
56% {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
60% {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
64% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
68% {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
72% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
76% {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
80% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
84% {
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
88% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
92% {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
96% {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
100% {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
169
im/src/components/editor/MeEditorSystemEmoticon.vue
Normal file
169
im/src/components/editor/MeEditorSystemEmoticon.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="lum-dialog-mask">
|
||||
<el-container class="lum-dialog-box" v-outside="closeBox">
|
||||
<el-header class="no-padding header" height="50px">
|
||||
<p>系统表情</p>
|
||||
<p class="tools">
|
||||
<i class="el-icon-close" @click="closeBox" />
|
||||
</p>
|
||||
</el-header>
|
||||
|
||||
<el-main class="no-padding mian lum-scrollbar">
|
||||
<ul>
|
||||
<li v-for="(item, i) in items" :key="item.id" class="no-select">
|
||||
<div class="pkg-avatar">
|
||||
<el-image :src="item.icon" fit="cover" :lazy="true" />
|
||||
</div>
|
||||
<div class="pkg-info" v-text="item.name"></div>
|
||||
<div class="pkg-status">
|
||||
<button
|
||||
:class="{
|
||||
'add-emoji': item.status == 0,
|
||||
'remove-emoji': item.status != 0,
|
||||
}"
|
||||
@click="setEmoticon(i, item, item.status == 0 ? 1 : 2)"
|
||||
>
|
||||
{{ item.status == 0 ? '添加' : '移除' }}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</el-main>
|
||||
|
||||
<el-footer class="footer" height="50px">
|
||||
<el-button type="primary" size="medium" class="btn" @click="closeBox">
|
||||
关闭窗口
|
||||
</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ServeFindSysEmoticon, ServeSetUserEmoticon } from '@/api/emoticon'
|
||||
|
||||
export default {
|
||||
name: 'MeEditorSystemEmoticon',
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSysEmoticon()
|
||||
},
|
||||
methods: {
|
||||
closeBox() {
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
// 获取系统表情包列表
|
||||
loadSysEmoticon() {
|
||||
ServeFindSysEmoticon().then(res => {
|
||||
if (res.code == 200) {
|
||||
this.items = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
setEmoticon(index, item, type) {
|
||||
ServeSetUserEmoticon({
|
||||
emoticon_id: item.id,
|
||||
type: type,
|
||||
}).then(res => {
|
||||
if (res.code == 200) {
|
||||
if (type == 1) {
|
||||
this.items[index].status = 1
|
||||
this.$store.commit('APPEND_SYS_EMOTICON', res.data)
|
||||
} else {
|
||||
this.items[index].status = 0
|
||||
this.$store.commit('REMOVE_SYS_EMOTICON', {
|
||||
emoticon_id: item.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.lum-dialog-box {
|
||||
width: 350px;
|
||||
max-width: 350px;
|
||||
height: 500px;
|
||||
|
||||
.mian {
|
||||
height: 480px;
|
||||
overflow-y: auto;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
height: 68px;
|
||||
align-items: center;
|
||||
border-bottom: 3px solid #fbf2fb;
|
||||
padding-left: 5px;
|
||||
|
||||
.pkg-avatar {
|
||||
flex-shrink: 0;
|
||||
|
||||
.el-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.pkg-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 14px;
|
||||
margin-right: 14px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
width: 200px;
|
||||
color: #615d5d;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pkg-status {
|
||||
flex-shrink: 0;
|
||||
|
||||
button {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
line-height: 28px;
|
||||
border-radius: 20px;
|
||||
width: 50px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.add-emoji {
|
||||
background-color: #38adff;
|
||||
}
|
||||
|
||||
.remove-emoji {
|
||||
background-color: #ff5722;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 50px;
|
||||
background: rgba(247, 245, 245, 0.66);
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
|
||||
.btn {
|
||||
width: 150px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
226
im/src/components/editor/MeEditorVote.vue
Normal file
226
im/src/components/editor/MeEditorVote.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div class="lum-dialog-mask animated fadeIn">
|
||||
<el-container class="lum-dialog-box">
|
||||
<el-header class="header no-select" height="60px">
|
||||
<p>发起投票</p>
|
||||
<p class="tools">
|
||||
<i class="el-icon-close" @click="$emit('close')" />
|
||||
</p>
|
||||
</el-header>
|
||||
<el-main class="main no-padding vote-from">
|
||||
<div class="vote-title">投票方式</div>
|
||||
<div>
|
||||
<el-radio-group v-model="mode">
|
||||
<el-radio :label="0">单选</el-radio>
|
||||
<el-radio :label="1">多选</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="vote-title">投票主题</div>
|
||||
<div>
|
||||
<el-input
|
||||
size="medium"
|
||||
clear="vote-input"
|
||||
v-model.trim="title"
|
||||
placeholder="请输入投票主题,最多50字"
|
||||
:maxlength="50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="vote-title">投票选项</div>
|
||||
<div>
|
||||
<div class="vote-options" v-for="(option, index) in options">
|
||||
<div class="lbox">
|
||||
<el-input
|
||||
size="medium"
|
||||
clear="vote-input"
|
||||
v-model.trim="option.value"
|
||||
placeholder="请输入选项内容"
|
||||
:maxlength="120"
|
||||
>
|
||||
<span
|
||||
slot="prefix"
|
||||
style="margin-left:7px;"
|
||||
v-text="String.fromCharCode(65 + index)"
|
||||
/>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="rbox">
|
||||
<i class="el-icon-close" @click="removeOption(index)"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="pointer add-option" @click="addOption">
|
||||
<i class="el-icon-plus"></i> 添加选项
|
||||
</h6>
|
||||
</div>
|
||||
</el-main>
|
||||
<el-footer class="footer">
|
||||
<el-button plain size="small">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="isCheck"
|
||||
:loading="loading"
|
||||
@click="submit"
|
||||
>发起投票</el-button
|
||||
>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ServeSendVote } from '@/api/chat'
|
||||
|
||||
export default {
|
||||
name: 'MeEditorVote',
|
||||
props: {
|
||||
group_id: {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
mode: 0,
|
||||
title: '',
|
||||
options: [
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isCheck() {
|
||||
if (this.title == '') return true
|
||||
|
||||
return this.options.some(option => option.value == '')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
let items = []
|
||||
const { receiver_id } = this.$store.state.dialogue
|
||||
this.options.forEach(option => {
|
||||
items.push(option.value)
|
||||
})
|
||||
|
||||
this.loading = true
|
||||
|
||||
ServeSendVote({
|
||||
receiver_id,
|
||||
mode: this.mode,
|
||||
title: this.title,
|
||||
options: items,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code == 200) {
|
||||
this.$emit('close')
|
||||
this.$notify({
|
||||
title: '友情提示',
|
||||
message: '发起投票成功!',
|
||||
type: 'success',
|
||||
})
|
||||
} else {
|
||||
this.$notify({
|
||||
title: '友情提示',
|
||||
message: res.message,
|
||||
type: 'warning',
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
addOption() {
|
||||
if (this.options.length >= 6) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.options.push({
|
||||
value: '',
|
||||
})
|
||||
},
|
||||
removeOption(index) {
|
||||
if (this.options.length <= 2) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.$delete(this.options, index)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.lum-dialog-box {
|
||||
height: 600px;
|
||||
max-width: 450px;
|
||||
|
||||
.vote-from {
|
||||
box-sizing: border-box;
|
||||
padding: 15px 25px;
|
||||
overflow: hidden;
|
||||
|
||||
.vote-title {
|
||||
margin: 20px 0 10px;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.vote-options {
|
||||
display: flex;
|
||||
min-height: 30px;
|
||||
margin: 10px 0;
|
||||
|
||||
.lbox {
|
||||
width: 100%;
|
||||
|
||||
/deep/.el-input__prefix {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.rbox {
|
||||
flex-basis: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
i {
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-option {
|
||||
margin-top: 5px;
|
||||
font-weight: 400;
|
||||
color: #3370ff;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/.el-radio__input.is-checked + .el-radio__label {
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user