jeecgboot3.4.2版本发布,新功能(表单右侧评论功能)
parent
78d182ba0c
commit
df0441c8f5
|
@ -0,0 +1,164 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-alert type="info" class="jeecg-comment-files">
|
||||
<template #message>
|
||||
<span class="j-icon">
|
||||
<a-upload multiple v-model:file-list="selectFileList" :showUploadList="false" :before-upload="beforeUpload">
|
||||
<span class="inner-button"><upload-outlined />上传</span>
|
||||
</a-upload>
|
||||
</span>
|
||||
<span class="j-icon">
|
||||
<span class="inner-button"><folder-outlined />从文件库选择?</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<!-- 正在上传的文件 -->
|
||||
<div class="selected-file-warp" v-if="selectFileList && selectFileList.length > 0">
|
||||
<div class="selected-file-list">
|
||||
<div class="item" v-for="item in selectFileList">
|
||||
<div class="complex">
|
||||
<div class="content" >
|
||||
<!-- 图片 -->
|
||||
<div v-if="isImage(item)" class="content-top" style="height: 100%">
|
||||
<div class="content-image" :style="getImageAsBackground(item)">
|
||||
<!-- <img style="height: 100%;" :src="getImageSrc(item)">-->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文件 -->
|
||||
<template v-else>
|
||||
<div class="content-top">
|
||||
<div class="content-icon" :style="{ background: 'url(' + getBackground(item) + ') no-repeat' }"></div>
|
||||
</div>
|
||||
<div class="content-bottom" :title="item.name">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="layer" :class="{'layer-image':isImage(item)}">
|
||||
<div class="next" @click="viewImage(item)"><div class="text">{{ item.name }} </div></div>
|
||||
<div class="buttons">
|
||||
<div class="opt-icon">
|
||||
<Tooltip title="删除">
|
||||
<delete-outlined @click="handleRemove(item)" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item empty"></div><div class="item empty"></div><div class="item empty"></div> <div class="item empty"></div><div class="item empty"></div><div class="item empty"></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 24px; margin-top: 18px; text-align: right">
|
||||
<a-button @click="quxiao">取消</a-button>
|
||||
<a-button type="primary" style="margin-left: 10px" @click="queding" :loading="buttonLoading">确定</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 历史文件 -->
|
||||
<history-file-list :dataList="dataList"></history-file-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UploadOutlined, FolderOutlined, DownloadOutlined, PaperClipOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
|
||||
import { uploadFileUrl } from './useComment';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { computed, watchEffect, unref, ref } from 'vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { fileList } from './useComment';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { saveOne, useCommentWithFile, useFileList } from './useComment';
|
||||
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import HistoryFileList from './HistoryFileList.vue';
|
||||
|
||||
export default {
|
||||
name: 'CommentFiles',
|
||||
components: {
|
||||
UploadOutlined,
|
||||
FolderOutlined,
|
||||
JUpload,
|
||||
DownloadOutlined,
|
||||
PaperClipOutlined,
|
||||
DeleteOutlined,
|
||||
Tooltip,
|
||||
HistoryFileList,
|
||||
},
|
||||
props: {
|
||||
tableName: propTypes.string.def(''),
|
||||
dataId: propTypes.string.def(''),
|
||||
datetime: propTypes.number.def(1)
|
||||
},
|
||||
setup(props) {
|
||||
// const { createMessage } = useMessage();
|
||||
const { userInfo } = useUserStore();
|
||||
const dataList = ref([]);
|
||||
const commentId = ref('');
|
||||
|
||||
async function loadFileList() {
|
||||
const params = {
|
||||
tableName: props.tableName,
|
||||
tableDataId: props.dataId,
|
||||
};
|
||||
const data = await fileList(params);
|
||||
console.log('1111', data)
|
||||
if (!data || !data.records || data.records.length == 0) {
|
||||
dataList.value = [];
|
||||
} else {
|
||||
let array = data.records;
|
||||
console.log(123, array);
|
||||
dataList.value = array;
|
||||
}
|
||||
commentId.value = '';
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
// 每次切换tab都会刷新文件列表--- VUEN-1884 评论里上传的图片未在文件中显示
|
||||
if(props.datetime){
|
||||
if (props.tableName && props.dataId) {
|
||||
loadFileList();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { saveCommentAndFiles, buttonLoading } = useCommentWithFile(props);
|
||||
const { selectFileList, beforeUpload, handleRemove, getBackground, isImage, getImageAsBackground, viewImage } = useFileList();
|
||||
|
||||
function quxiao() {
|
||||
selectFileList.value = [];
|
||||
}
|
||||
async function queding() {
|
||||
let obj = {
|
||||
fromUserId: userInfo.id,
|
||||
commentContent: '上传了附件'
|
||||
}
|
||||
await saveCommentAndFiles(obj, selectFileList.value)
|
||||
selectFileList.value = [];
|
||||
await loadFileList();
|
||||
}
|
||||
|
||||
return {
|
||||
selectFileList,
|
||||
beforeUpload,
|
||||
handleRemove,
|
||||
getBackground,
|
||||
isImage,
|
||||
dataList,
|
||||
uploadFileUrl,
|
||||
quxiao,
|
||||
queding,
|
||||
buttonLoading,
|
||||
getImageAsBackground,
|
||||
viewImage
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'comment.less';
|
||||
</style>
|
|
@ -0,0 +1,328 @@
|
|||
<template>
|
||||
<div :style="{ position: 'relative', height: allHeight + 'px' }">
|
||||
<a-list class="jeecg-comment-list" header="" item-layout="horizontal" :data-source="dataList" :style="{ height: commentHeight + 'px' }">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item style="padding-left: 10px; flex-direction: column" @click="handleClickItem">
|
||||
<a-comment>
|
||||
<template #avatar>
|
||||
<a-avatar class="tx" :src="getAvatar(item)" :alt="getAvatarText(item)">{{ getAvatarText(item) }}</a-avatar>
|
||||
</template>
|
||||
|
||||
<template #author>
|
||||
<div class="comment-author">
|
||||
<span>{{ item.fromUserId_dictText }}</span>
|
||||
|
||||
<template v-if="item.toUserId">
|
||||
<span>回复</span>
|
||||
<span>{{ item.toUserId_dictText }}</span>
|
||||
<Tooltip class="comment-last-content" @visibleChange="(v)=>visibleChange(v, item)">
|
||||
<template #title>
|
||||
<div v-html="getHtml(item.commentId_dictText)"></div>
|
||||
</template>
|
||||
<message-outlined />
|
||||
</Tooltip>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #datetime>
|
||||
<div>
|
||||
<Tooltip :title="item.createTime">
|
||||
<span>{{ getDateDiff(item) }}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<span @click="showReply(item)">回复</span>
|
||||
|
||||
<Popconfirm title="确定删除吗?" @confirm="deleteComment(item)">
|
||||
<span>删除</span>
|
||||
</Popconfirm>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div v-html="getHtml(item.commentContent)" style="font-size: 15px">
|
||||
</div>
|
||||
|
||||
<div v-if="item.fileList && item.fileList.length > 0">
|
||||
<!-- 历史文件 -->
|
||||
<history-file-list :dataList="item.fileList" isComment></history-file-list>
|
||||
</div>
|
||||
</template>
|
||||
</a-comment>
|
||||
<div v-if="item.commentStatus" class="inner-comment">
|
||||
<my-comment inner @cancel="item.commentStatus = false" @comment="(content, fileList) => replyComment(item, content, fileList)" :inputFocus="focusStatus"></my-comment>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
|
||||
<div style="position: absolute; bottom: 0; left: 0; width: 100%; background: #fff; border-top: 1px solid #eee">
|
||||
<a-comment style="margin: 0 10px">
|
||||
<template #avatar>
|
||||
<a-avatar class="tx" :src="getMyAvatar()" :alt="getMyname()">{{ getMyname() }}</a-avatar>
|
||||
</template>
|
||||
<template #content>
|
||||
<my-comment ref="bottomCommentRef" @comment="sendComment" :inputFocus="focusStatus"></my-comment>
|
||||
</template>
|
||||
</a-comment>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 评论列表
|
||||
*/
|
||||
import { defineComponent, ref, onMounted, watch, watchEffect } from 'vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh.js';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
dayjs.locale('zh');
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(customParseFormat);
|
||||
import { MessageOutlined } from '@ant-design/icons-vue';
|
||||
import { Comment, Tooltip } from 'ant-design-vue';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import MyComment from './MyComment.vue';
|
||||
import { list, saveOne, deleteOne, useCommentWithFile, useEmojiHtml, queryById } from './useComment';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import HistoryFileList from './HistoryFileList.vue';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CommentList',
|
||||
components: {
|
||||
MessageOutlined,
|
||||
AComment: Comment,
|
||||
Tooltip,
|
||||
MyComment,
|
||||
Popconfirm,
|
||||
HistoryFileList,
|
||||
},
|
||||
props: {
|
||||
tableName: propTypes.string.def(''),
|
||||
dataId: propTypes.string.def(''),
|
||||
datetime: propTypes.number.def(1)
|
||||
},
|
||||
setup(props) {
|
||||
const { createMessage } = useMessage();
|
||||
const dataList = ref([]);
|
||||
const { userInfo } = useUserStore();
|
||||
/**
|
||||
* 获取当前用户名称
|
||||
*/
|
||||
function getMyname() {
|
||||
if (userInfo.realname) {
|
||||
return userInfo.realname.substr(0, 2);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getMyAvatar(){
|
||||
return userInfo.avatar;
|
||||
}
|
||||
|
||||
// 获取头像
|
||||
function getAvatar(item) {
|
||||
if (item.fromUserAvatar) {
|
||||
return getFileAccessHttpUrl(item.fromUserAvatar)
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// 头像没有获取 用户名前两位
|
||||
function getAvatarText(item){
|
||||
if (item.fromUserId_dictText) {
|
||||
return item.fromUserId_dictText.substr(0, 2);
|
||||
}
|
||||
return '未知';
|
||||
}
|
||||
|
||||
function getAuthor(item) {
|
||||
if (item.toUser) {
|
||||
return item.fromUserId_dictText + ' 回复 ' + item.fromUserId_dictText;
|
||||
} else {
|
||||
return item.fromUserId_dictText;
|
||||
}
|
||||
}
|
||||
|
||||
function getDateDiff(item) {
|
||||
if (item.createTime) {
|
||||
const temp = dayjs(item.createTime, 'YYYY-MM-DD hh:mm:ss');
|
||||
return temp.fromNow();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
const commentHeight = ref(300);
|
||||
const allHeight = ref(300);
|
||||
onMounted(() => {
|
||||
commentHeight.value = window.innerHeight - 57 - 46 - 70 - 160;
|
||||
allHeight.value = window.innerHeight - 57 - 46 - 53 -20;
|
||||
});
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function loadData() {
|
||||
const params = {
|
||||
tableName: props.tableName,
|
||||
tableDataId: props.dataId,
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
};
|
||||
const data = await list(params);
|
||||
if (!data || !data.records || data.records.length == 0) {
|
||||
dataList.value = [];
|
||||
} else {
|
||||
let array = data.records;
|
||||
console.log(123, array);
|
||||
dataList.value = array;
|
||||
}
|
||||
}
|
||||
|
||||
const { saveCommentAndFiles } = useCommentWithFile(props);
|
||||
// 回复
|
||||
async function replyComment(item, content, fileList) {
|
||||
console.log(content, item);
|
||||
let obj = {
|
||||
fromUserId: userInfo.id,
|
||||
toUserId: item.fromUserId,
|
||||
commentId: item.id,
|
||||
commentContent: content
|
||||
}
|
||||
await saveCommentAndFiles(obj, fileList)
|
||||
await loadData();
|
||||
}
|
||||
|
||||
//评论
|
||||
async function sendComment(content, fileList) {
|
||||
let obj = {
|
||||
fromUserId: userInfo.id,
|
||||
commentContent: content
|
||||
}
|
||||
await saveCommentAndFiles(obj, fileList)
|
||||
await loadData();
|
||||
focusStatus.value = false;
|
||||
setTimeout(()=>{
|
||||
focusStatus.value = true;
|
||||
},100)
|
||||
}
|
||||
|
||||
//删除
|
||||
async function deleteComment(item) {
|
||||
const params = { id: item.id };
|
||||
await deleteOne(params);
|
||||
await loadData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开回复时触发
|
||||
* @type {Ref<UnwrapRef<boolean>>}
|
||||
*/
|
||||
const focusStatus = ref(false);
|
||||
function showReply(item) {
|
||||
let arr = dataList.value;
|
||||
for (let temp of arr) {
|
||||
temp.commentStatus = false;
|
||||
}
|
||||
item.commentStatus = true;
|
||||
focusStatus.value = false;
|
||||
focusStatus.value = true;
|
||||
}
|
||||
|
||||
// 表单改变 -重新加载评论列表
|
||||
watchEffect(() => {
|
||||
if(props.datetime){
|
||||
if (props.tableName && props.dataId) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { getHtml } = useEmojiHtml();
|
||||
const bottomCommentRef = ref()
|
||||
function handleClickItem(){
|
||||
bottomCommentRef.value.changeActive()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据id查询评论信息
|
||||
*/
|
||||
async function visibleChange(v, item){
|
||||
if(v==true){
|
||||
if(!item.commentId_dictText){
|
||||
const data = await queryById(item.commentId);
|
||||
if(data.success == true){
|
||||
item.commentId_dictText = data.result.commentContent
|
||||
}else{
|
||||
console.error(data.message)
|
||||
item.commentId_dictText='该评论已被删除';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dataList,
|
||||
getAvatar,
|
||||
getAvatarText,
|
||||
getAuthor,
|
||||
getDateDiff,
|
||||
commentHeight,
|
||||
allHeight,
|
||||
replyComment,
|
||||
sendComment,
|
||||
getMyname,
|
||||
getMyAvatar,
|
||||
|
||||
focusStatus,
|
||||
showReply,
|
||||
deleteComment,
|
||||
getHtml,
|
||||
handleClickItem,
|
||||
bottomCommentRef,
|
||||
visibleChange
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.jeecg-comment-list {
|
||||
overflow: auto;
|
||||
/* border-bottom: 1px solid #eee;*/
|
||||
.inner-comment {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.ant-comment {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.comment-author {
|
||||
span {
|
||||
margin: 3px;
|
||||
}
|
||||
.comment-last-content {
|
||||
margin-left: 5px;
|
||||
&:hover{
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-list-items{
|
||||
.ant-list-item:last-child{
|
||||
margin-bottom: 46px;
|
||||
}
|
||||
}
|
||||
.tx{
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div class="comment-tabs-warp" v-if="showStatus">
|
||||
<a-tabs @change="handleChange" :animated="false">
|
||||
<a-tab-pane tab="评论" key="comment" class="comment-list-tab">
|
||||
<comment-list :tableName="tableName" :dataId="dataId" :datetime="datetime1"></comment-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="文件" key="file">
|
||||
<comment-files :tableName="tableName" :dataId="dataId" :datetime="datetime2"></comment-files>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="日志" key="log">
|
||||
<data-log-list :tableName="tableName" :dataId="dataId" :datetime="datetime3"></data-log-list>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-empty v-else description="新增页面不支持评论" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 评论区域
|
||||
*/
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { computed, ref } from 'vue';
|
||||
import CommentList from './CommentList.vue';
|
||||
import CommentFiles from './CommentFiles.vue';
|
||||
import DataLogList from './DataLogList.vue';
|
||||
|
||||
export default {
|
||||
name: 'CommentPanel',
|
||||
components: {
|
||||
CommentList,
|
||||
CommentFiles,
|
||||
DataLogList,
|
||||
},
|
||||
props: {
|
||||
tableName: propTypes.string.def(''),
|
||||
dataId: propTypes.string.def(''),
|
||||
},
|
||||
setup(props) {
|
||||
const showStatus = computed(() => {
|
||||
if (props.dataId && props.tableName) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const datetime1 = ref(1);
|
||||
const datetime2 = ref(1);
|
||||
const datetime3 = ref(1);
|
||||
function handleChange(e) {
|
||||
let temp = new Date().getTime();
|
||||
if (e == 'comment') {
|
||||
datetime1.value = temp;
|
||||
} else if (e == 'file') {
|
||||
datetime2.value = temp;
|
||||
} else {
|
||||
datetime3.value = temp;
|
||||
}
|
||||
}
|
||||
|
||||
// VUEN-1978【bug】online关联记录和他表字段存在问题 20 修改完数据,再次打开不切换tab的时候,修改日志没有变化
|
||||
function reload() {
|
||||
let temp = new Date().getTime();
|
||||
datetime1.value = temp;
|
||||
datetime2.value = temp;
|
||||
datetime3.value = temp;
|
||||
}
|
||||
|
||||
return {
|
||||
showStatus,
|
||||
handleChange,
|
||||
datetime1,
|
||||
datetime2,
|
||||
datetime3,
|
||||
reload
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.comment-tabs-warp {
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
> .ant-tabs {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
//antd3升级后,表单右侧讨论样式调整
|
||||
::v-deep(.ant-tabs-top .ant-tabs-nav, .ant-tabs-bottom .ant-tabs-nav, .ant-tabs-top div .ant-tabs-nav, .ant-tabs-bottom div .ant-tabs-nav) {
|
||||
margin: 0 16px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<div class="data-log-scroll" :style="{'height': height+'px'}">
|
||||
<div class="data-log-content">
|
||||
<div class="logbox">
|
||||
|
||||
<div class="log-item" v-for="(item, index) in dataList">
|
||||
<span class="log-item-icon">
|
||||
<plus-outlined v-if="lastIndex == index" style="margin-top:3px"/>
|
||||
<edit-outlined v-else/>
|
||||
</span>
|
||||
<span class="log-item-content">
|
||||
<a @click="handleClickPerson">@{{item.createBy}}</a>
|
||||
{{ item.dataContent }}
|
||||
</span>
|
||||
<div class="log-item-date">
|
||||
<Tooltip :title="item.createTime">
|
||||
<span>{{ getDateDiff(item) }}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { PlusOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import { getModalHeight, getLogList } from './useComment'
|
||||
import {ref, watchEffect} from 'vue'
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh.js';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
dayjs.locale('zh');
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
export default {
|
||||
name: "DataLogList",
|
||||
components:{
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
Tooltip
|
||||
},
|
||||
props: {
|
||||
tableName: propTypes.string.def(''),
|
||||
dataId: propTypes.string.def(''),
|
||||
datetime: propTypes.number.def(1),
|
||||
},
|
||||
setup(props){
|
||||
const winHeight = getModalHeight();
|
||||
const height = ref(300);
|
||||
height.value = winHeight - 46 - 57 -53 - 30;
|
||||
|
||||
const dataList = ref([]);
|
||||
const lastIndex = ref(0);
|
||||
/**
|
||||
* 加载数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function loadData() {
|
||||
const params = {
|
||||
dataTable: props.tableName,
|
||||
dataId: props.dataId,
|
||||
type: 'comment'
|
||||
};
|
||||
const res = await getLogList(params);
|
||||
if (!res || !res.result || res.result.length == 0) {
|
||||
dataList.value = [];
|
||||
lastIndex.value = -1;
|
||||
} else {
|
||||
let arr = res.result;
|
||||
lastIndex.value = arr.length-1;
|
||||
console.log('log-list', arr);
|
||||
dataList.value = arr;
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if(props.datetime){
|
||||
if (props.tableName && props.dataId) {
|
||||
console.log(props.tableName, props.dataId)
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
function getDateDiff(item) {
|
||||
if (item.createTime) {
|
||||
const temp = dayjs(item.createTime, 'YYYY-MM-DD hh:mm:ss');
|
||||
return temp.fromNow();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function handleClickPerson() {
|
||||
console.log('此功能未开放')
|
||||
}
|
||||
|
||||
return {
|
||||
height,
|
||||
lastIndex,
|
||||
dataList,
|
||||
getDateDiff,
|
||||
handleClickPerson
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.data-log-scroll{
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
padding-bottom: 16px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
.data-log-content{
|
||||
/* right: -10px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
position: absolute;
|
||||
top: 0;*/
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
.logbox{
|
||||
box-sizing: border-box;
|
||||
padding-left: 16px;
|
||||
.log-item{
|
||||
box-sizing: border-box;
|
||||
color: #9e9e9e;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 20px;
|
||||
padding-right: 25px;
|
||||
position: relative;
|
||||
.log-item-icon{
|
||||
left: 0;
|
||||
line-height: 16px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.log-item-content{
|
||||
word-wrap: break-word;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.log-item-date{
|
||||
word-wrap: break-word;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
box-sizing: border-box;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<div class="comment-file-his-list" :class="isComment === true ? 'in-comment' : ''">
|
||||
<div class="selected-file-list">
|
||||
<div class="item" v-for="item in dataList">
|
||||
<div class="complex">
|
||||
<div class="content">
|
||||
<!-- 图片 -->
|
||||
<div v-if="isImage(item)" class="content-top" style="height: 100%">
|
||||
<div class="content-image" :style="getImageAsBackground(item)">
|
||||
<!--<img style="height: 100%;" :src="getImageSrc(item)"/>-->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文件 -->
|
||||
<template v-else>
|
||||
<div class="content-top">
|
||||
<div class="content-icon" :style="{ background: 'url(' + getBackground(item) + ') no-repeat' }"></div>
|
||||
</div>
|
||||
<div class="content-bottom" :title="item.name">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="layer" :class="{'layer-image':isImage(item)}">
|
||||
<div class="next" @click="viewImage(item)">
|
||||
<div class="text">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="text">
|
||||
{{ getFileSize(item) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="opt-icon">
|
||||
<Tooltip title="下载">
|
||||
<download-outlined @click="downLoad(item)" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item empty"></div><div class="item empty"></div><div class="item empty"></div> <div class="item empty"></div><div class="item empty"></div><div class="item empty"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { UploadOutlined, FolderOutlined, DownloadOutlined, PaperClipOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { useFileList } from './useComment';
|
||||
|
||||
export default {
|
||||
name: 'HistoryFileList',
|
||||
props: {
|
||||
dataList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isComment: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
UploadOutlined,
|
||||
FolderOutlined,
|
||||
DownloadOutlined,
|
||||
PaperClipOutlined,
|
||||
DeleteOutlined,
|
||||
Tooltip,
|
||||
},
|
||||
setup() {
|
||||
const { getBackground, getFileSize, downLoad, isImage, getImageAsBackground, viewImage } = useFileList();
|
||||
return {
|
||||
getBackground,
|
||||
downLoad,
|
||||
getFileSize,
|
||||
isImage,
|
||||
getImageAsBackground,
|
||||
viewImage
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'comment.less';
|
||||
</style>
|
|
@ -0,0 +1,383 @@
|
|||
<template>
|
||||
<div :class="{'comment-active': commentActive}" style="border: 1px solid #eee; margin: 0; position: relative" @click="handleClickBlank">
|
||||
<textarea ref="commentRef" v-model="myComment" @input="handleCommentChange" @blur="handleBlur" class="comment-content" :rows="3" placeholder="请输入你的评论,可以@成员" />
|
||||
<div class="comment-content comment-html-shower" :class="{'no-content':noConent, 'top-div': showHtml, 'bottom-div': showHtml == false }" v-html="commentHtml" @click="handleClickHtmlShower"></div>
|
||||
<div class="comment-buttons" v-if="commentActive">
|
||||
<div style="cursor: pointer">
|
||||
<Tooltip title="选择@用户">
|
||||
<user-add-outlined @click="openSelectUser" />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="上传附件">
|
||||
<PaperClipOutlined @click="uploadVisible = !uploadVisible" />
|
||||
</Tooltip>
|
||||
|
||||
<span title="表情" style="display: inline-block">
|
||||
<SmileOutlined ref="emojiButton" @click="handleShowEmoji" />
|
||||
<div style="position: relative" v-show=""> </div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="commentActive">
|
||||
<a-button v-if="inner" @click="noComment" style="margin-right: 10px">取消</a-button>
|
||||
<a-button type="primary" @click="sendComment" :loading="buttonLoading" :disabled="disabledButton">发 送</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<upload-chunk ref="uploadRef" :visible="uploadVisible" @select="selectFirstFile"></upload-chunk>
|
||||
</div>
|
||||
<UserSelectModal labelKey="realname" rowKey="username" @register="registerModal" @getSelectResult="setValue" isRadioSelection></UserSelectModal>
|
||||
<a-modal v-model:visible="visibleEmoji" :footer="null" wrapClassName="emoji-modal" :closable="false" :width="490">
|
||||
<template #title>
|
||||
<span></span>
|
||||
</template>
|
||||
<Picker
|
||||
:pickerStyles="pickerStyles"
|
||||
:i18n="optionsName"
|
||||
:data="emojiIndex"
|
||||
emoji="grinning"
|
||||
:showPreview="false"
|
||||
:infiniteScroll="false"
|
||||
:showSearch="false"
|
||||
:showSkinTones="false"
|
||||
set="apple"
|
||||
@select="showEmoji">
|
||||
</Picker>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { UserAddOutlined, PaperClipOutlined, SmileOutlined } from '@ant-design/icons-vue';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import UploadChunk from './UploadChunk.vue';
|
||||
import { Picker } from 'emoji-mart-vue-fast/src';
|
||||
import 'emoji-mart-vue-fast/css/emoji-mart.css';
|
||||
import { useEmojiHtml } from './useComment';
|
||||
|
||||
const optionsName = {
|
||||
categories: {
|
||||
recent: '最常用的',
|
||||
smileys: '表情选择',
|
||||
people: '人物&身体',
|
||||
nature: '动物&自然',
|
||||
foods: '食物&饮料',
|
||||
activity: '活动',
|
||||
places: '旅行&地点',
|
||||
objects: '物品',
|
||||
symbols: '符号',
|
||||
flags: '旗帜',
|
||||
},
|
||||
};
|
||||
export default {
|
||||
name: 'MyComment',
|
||||
components: {
|
||||
UserAddOutlined,
|
||||
Tooltip,
|
||||
UserSelectModal,
|
||||
PaperClipOutlined,
|
||||
UploadChunk,
|
||||
SmileOutlined,
|
||||
Picker,
|
||||
},
|
||||
props: {
|
||||
inner: propTypes.bool.def(false),
|
||||
inputFocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['cancel', 'comment'],
|
||||
setup(props, { emit }) {
|
||||
const uploadVisible = ref(false);
|
||||
const uploadRef = ref();
|
||||
//注册model
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const buttonLoading = ref(false);
|
||||
const myComment = ref<string>('');
|
||||
function sendComment() {
|
||||
console.log(myComment.value);
|
||||
let content = myComment.value;
|
||||
if (!content && content !== '0') {
|
||||
disabledButton.value = true;
|
||||
} else {
|
||||
buttonLoading.value = true;
|
||||
let fileList = [];
|
||||
if (uploadVisible.value == true) {
|
||||
fileList = uploadRef.value.getUploadFileList();
|
||||
}
|
||||
emit('comment', content, fileList);
|
||||
setTimeout(() => {
|
||||
buttonLoading.value = false;
|
||||
}, 350);
|
||||
}
|
||||
}
|
||||
const disabledButton = ref(false);
|
||||
watch(myComment, () => {
|
||||
let content = myComment.value;
|
||||
if (!content && content !== '0') {
|
||||
disabledButton.value = true;
|
||||
} else {
|
||||
disabledButton.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
function noComment() {
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
const commentRef = ref();
|
||||
watch(
|
||||
() => props.inputFocus,
|
||||
(val) => {
|
||||
if (val == true) {
|
||||
// commentRef.value.focus()
|
||||
myComment.value = '';
|
||||
if (uploadVisible.value == true) {
|
||||
uploadRef.value.clear();
|
||||
uploadVisible.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
function openSelectUser() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
function setValue(options) {
|
||||
console.log('setValue', options);
|
||||
if (options && options.length > 0) {
|
||||
const { label, value } = options[0];
|
||||
if (label && value) {
|
||||
let str = `${label}[${value}]`;
|
||||
let temp = myComment.value;
|
||||
if (!temp) {
|
||||
myComment.value = '@' + str;
|
||||
} else {
|
||||
if (temp.endsWith('@')) {
|
||||
myComment.value = temp + str;
|
||||
} else {
|
||||
myComment.value = '@' + str + ' ' + temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleCommentChange() {
|
||||
//console.log(1,e)
|
||||
}
|
||||
watch(
|
||||
() => myComment.value,
|
||||
(val) => {
|
||||
if (val && val.endsWith('@')) {
|
||||
openSelectUser();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const emojiButton = ref();
|
||||
function onSelectEmoji(emoji) {
|
||||
let temp = myComment.value || '';
|
||||
temp += emoji;
|
||||
myComment.value = temp;
|
||||
emojiButton.value.click();
|
||||
}
|
||||
|
||||
const visibleEmoji = ref(false);
|
||||
function showEmoji(e) {
|
||||
let temp = myComment.value || '';
|
||||
let str = e.colons;
|
||||
if (str.indexOf('::') > 0) {
|
||||
str = str.substring(0, str.indexOf(':') + 1);
|
||||
}
|
||||
myComment.value = temp + str;
|
||||
visibleEmoji.value = false;
|
||||
handleBlur();
|
||||
}
|
||||
|
||||
const pickerStyles = {
|
||||
width: '490px'
|
||||
/* height: '350px',
|
||||
top: '0px',
|
||||
left: '-75px',
|
||||
position: 'absolute',
|
||||
'z-index': 9999*/
|
||||
};
|
||||
function handleClickBlank(e) {
|
||||
console.log('handleClickBlank');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
visibleEmoji.value = false;
|
||||
commentActive.value = true;
|
||||
}
|
||||
function handleShowEmoji(e) {
|
||||
console.log('handleShowEmoji');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
visibleEmoji.value = !visibleEmoji.value;
|
||||
}
|
||||
|
||||
const { emojiIndex, getHtml } = useEmojiHtml();
|
||||
|
||||
const commentHtml = computed(() => {
|
||||
let temp = myComment.value;
|
||||
if (!temp) {
|
||||
return '请输入你的评论,可以@成员';
|
||||
}
|
||||
return getHtml(temp);
|
||||
});
|
||||
|
||||
const showHtml = ref(false);
|
||||
function handleClickHtmlShower(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showHtml.value = false;
|
||||
commentRef.value.focus();
|
||||
console.log(234);
|
||||
commentActive.value = true;
|
||||
}
|
||||
function handleBlur() {
|
||||
showHtml.value = true;
|
||||
}
|
||||
|
||||
const commentActive = ref(false);
|
||||
const noConent = computed(()=>{
|
||||
if(myComment.value.length>0){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
function changeActive(){
|
||||
if(myComment.value.length==0){
|
||||
commentActive.value = false
|
||||
uploadVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function selectFirstFile(fileName){
|
||||
if(myComment.value.length==0){
|
||||
myComment.value = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
myComment,
|
||||
sendComment,
|
||||
noComment,
|
||||
disabledButton,
|
||||
buttonLoading,
|
||||
commentRef,
|
||||
registerModal,
|
||||
openSelectUser,
|
||||
setValue,
|
||||
handleCommentChange,
|
||||
uploadRef,
|
||||
uploadVisible,
|
||||
onSelectEmoji,
|
||||
optionsName,
|
||||
emojiButton,
|
||||
emojiIndex,
|
||||
showEmoji,
|
||||
pickerStyles,
|
||||
visibleEmoji,
|
||||
handleClickBlank,
|
||||
handleShowEmoji,
|
||||
commentHtml,
|
||||
showHtml,
|
||||
handleClickHtmlShower,
|
||||
handleBlur,
|
||||
commentActive,
|
||||
noConent,
|
||||
changeActive,
|
||||
selectFirstFile
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.comment-content {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-variant: tabular-nums;
|
||||
list-style: none;
|
||||
font-feature-settings: tnum;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 4px 11px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 15px;
|
||||
line-height: 1.5715;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
width: 100%;
|
||||
border: solid 0px;
|
||||
outline: none;
|
||||
|
||||
.emoji-item {
|
||||
display: inline-block !important;
|
||||
width: 0 !important;
|
||||
}
|
||||
}
|
||||
.comment-buttons {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
.anticon {
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
.comment-html-shower {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 70px;
|
||||
&.bottom-div {
|
||||
z-index: -99;
|
||||
}
|
||||
&.top-div {
|
||||
z-index: 9;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-modal {
|
||||
> .ant-modal{
|
||||
right: 25% !important;
|
||||
margin-right: 16px !important;
|
||||
}
|
||||
.ant-modal-header{
|
||||
padding: 0 !important;
|
||||
}
|
||||
.emoji-mart-bar{
|
||||
display: none;
|
||||
}
|
||||
h3.emoji-mart-category-label{
|
||||
/* display: none;*/
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-active{
|
||||
border-color: #1e88e5 !important;
|
||||
box-shadow: 0 1px 1px 0 #90caf9, 0 1px 6px 0 #90caf9;
|
||||
}
|
||||
.no-content{
|
||||
color: #a1a1a1
|
||||
}
|
||||
|
||||
/**聊天表情本地化*/
|
||||
.emoji-type-image.emoji-set-apple {
|
||||
background-image: url("./image/emoji.png");
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<div v-if="visible">
|
||||
<a-alert type="info" class="jeecg-comment-files" style="margin: 0">
|
||||
<template #message>
|
||||
<span class="j-icon">
|
||||
<a-upload multiple v-model:file-list="selectFileList" :showUploadList="false" :before-upload="beforeUpload">
|
||||
<span class="inner-button"><upload-outlined />上传</span>
|
||||
</a-upload>
|
||||
</span>
|
||||
<span class="j-icon">
|
||||
<span class="inner-button"><folder-outlined />从文件库选择?</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<!-- 正在上传的文件 -->
|
||||
<div class="selected-file-warp" v-if="selectFileList && selectFileList.length > 0">
|
||||
<div class="selected-file-list">
|
||||
<div class="item" v-for="item in selectFileList">
|
||||
<div class="complex">
|
||||
<div class="content">
|
||||
<!-- 图片 -->
|
||||
<div v-if="isImage(item)" class="content-top" style="height: 100%">
|
||||
<div class="content-image" :style="{'height':'100%', 'backgroundImage': 'url('+getImageSrc(item)+')'}">
|
||||
<!-- <img style="height: 100%;" :src="getImageSrc(item)">-->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文件 -->
|
||||
<template v-else>
|
||||
<div class="content-top">
|
||||
<div class="content-icon" :style="{ background: 'url(' + getBackground(item) + ') no-repeat' }"></div>
|
||||
</div>
|
||||
<div class="content-bottom" :title="item.name">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="layer" :class="{'layer-image':isImage(item)}">
|
||||
<div class="next" @click="viewImage(item)">
|
||||
<div class="text">{{ item.name }} </div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="opt-icon">
|
||||
<Tooltip title="删除">
|
||||
<delete-outlined @click="handleRemove(item)" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item empty"></div><div class="item empty"></div><div class="item empty"></div> <div class="item empty"></div><div class="item empty"></div><div class="item empty"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRaw, watch } from 'vue';
|
||||
import { useFileList } from './useComment';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { UploadOutlined, FolderOutlined, DownloadOutlined, PaperClipOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
export default {
|
||||
name: 'UploadChunk',
|
||||
components: {
|
||||
Tooltip,
|
||||
UploadOutlined,
|
||||
FolderOutlined,
|
||||
DownloadOutlined,
|
||||
PaperClipOutlined,
|
||||
DeleteOutlined,
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits:['select'],
|
||||
setup(_p, {emit}) {
|
||||
const { selectFileList, beforeUpload, handleRemove, getBackground, isImage, getImageSrc, viewImage } = useFileList();
|
||||
|
||||
function getUploadFileList() {
|
||||
let list = toRaw(selectFileList.value);
|
||||
console.log(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
function clear(){
|
||||
selectFileList.value = [];
|
||||
}
|
||||
|
||||
watch(()=>selectFileList.value, (arr)=>{
|
||||
if(arr && arr.length>0){
|
||||
let name = arr[0].name;
|
||||
if(name){
|
||||
emit('select', name)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
selectFileList,
|
||||
beforeUpload,
|
||||
handleRemove,
|
||||
getBackground,
|
||||
getUploadFileList,
|
||||
clear,
|
||||
isImage,
|
||||
getImageSrc,
|
||||
viewImage
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'comment.less';
|
||||
</style>
|
|
@ -0,0 +1,234 @@
|
|||
/*文件上传列表-begin*/
|
||||
.selected-file-warp,
|
||||
.comment-file-his-list {
|
||||
margin: 10px 20px;
|
||||
&.in-comment{
|
||||
margin: 10px 6px;
|
||||
}
|
||||
}
|
||||
.selected-file-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-right: -6px;
|
||||
.item {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
flex: 1 1 0%;
|
||||
height: 118px;
|
||||
margin: 0 6px 6px 0;
|
||||
min-width: 140px;
|
||||
max-width: 200px;
|
||||
width: 150px;
|
||||
&.empty {
|
||||
height: 0;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
.complex {
|
||||
border: 1px solid #e0e0e0;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
.content-top {
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex: 1 1 0%;
|
||||
justify-content: center;
|
||||
.content-icon {
|
||||
background-position: 50%;
|
||||
background-size: contain !important;
|
||||
height: 55px;
|
||||
width: 40px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
.content-image{
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.content-bottom {
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-basis: 30px;
|
||||
font-size: 13px;
|
||||
justify-content: flex-start;
|
||||
padding: 0 10px;
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.layer {
|
||||
opacity: 0;
|
||||
background-color: #f5f5f5;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: opacity 0.2s;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.next {
|
||||
height: 75px;
|
||||
padding: 5px;
|
||||
.text {
|
||||
color: #1e88e5 !important;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-basis: 30px;
|
||||
font-size: 12px;
|
||||
justify-content: flex-start;
|
||||
padding: 3px 7px 4px;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
line-height: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
.buttons {
|
||||
flex-basis: 32px;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding-right: 5px;
|
||||
justify-content: flex-end;
|
||||
.opt-icon {
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
width: 32px;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
.anticon-delete:hover {
|
||||
color: red;
|
||||
}
|
||||
.anticon-download:hover{
|
||||
color: #1e88e5 !important
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.layer-image{
|
||||
background: #000;
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.next{
|
||||
.text{
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
.opt-icon{
|
||||
color: #000 !important;
|
||||
.anticon-delete:hover {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.jeecg-comment-files {
|
||||
margin: 0 20px;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
&.ant-alert-info{
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #f5f5f5;
|
||||
}
|
||||
.j-icon {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border: 1px solid #e6f7ff;
|
||||
padding: 2px 7px;
|
||||
margin: 0 10px;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
border-color: #fff;
|
||||
color: #096dd9;
|
||||
}
|
||||
.inner-button {
|
||||
display: inline-block;
|
||||
color:#9e9e9e;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
/*border-color: #fff;*/
|
||||
/* color: #096dd9;*/
|
||||
color: #000;
|
||||
}
|
||||
span{
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-file-list {
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
line-height: 24px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
height: 100%;
|
||||
|
||||
.item-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
min-width: 100px;
|
||||
width: 20%;
|
||||
max-width: 220px;
|
||||
background-color: #fafafa;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
/* border-left: 1px solid #f0f0f0;*/
|
||||
padding: 10px 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
border-right: 1px solid #f0f0f0;
|
||||
flex-grow: 1;
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
.anticon {
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 839 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.2 MiB |
|
@ -0,0 +1,416 @@
|
|||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
const globSetting = useGlobSetting();
|
||||
const baseUploadUrl = globSetting.uploadUrl;
|
||||
import { ref, toRaw, unref, reactive } from 'vue';
|
||||
import { uploadMyFile } from '/@/api/common/api';
|
||||
|
||||
import excel from '/@/assets/svg/fileType/excel.svg';
|
||||
import other from '/@/assets/svg/fileType/other.svg';
|
||||
import pdf from '/@/assets/svg/fileType/pdf.svg';
|
||||
import txt from '/@/assets/svg/fileType/txt.svg';
|
||||
import word from '/@/assets/svg/fileType/word.svg';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { createImgPreview } from '/@/components/Preview';
|
||||
import {EmojiIndex} from "emoji-mart-vue-fast/src";
|
||||
import data from "emoji-mart-vue-fast/data/apple.json";
|
||||
|
||||
enum Api {
|
||||
list = '/sys/comment/listByForm',
|
||||
addText = '/sys/comment/addText',
|
||||
deleteOne = '/sys/comment/deleteOne',
|
||||
fileList = '/sys/comment/fileList',
|
||||
logList = '/sys/dataLog/queryDataVerList',
|
||||
queryById = '/sys/comment/queryById',
|
||||
getFileViewDomain = '/sys/comment/getFileViewDomain',
|
||||
}
|
||||
|
||||
// 文件预览地址的domain 在后台配置的
|
||||
let onlinePreviewDomain = '';
|
||||
|
||||
/**
|
||||
* 获取文件预览的domain
|
||||
*/
|
||||
const getViewFileDomain = () => defHttp.get({ url: Api.getFileViewDomain });
|
||||
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 查询单条记录
|
||||
* @param params
|
||||
*/
|
||||
export const queryById = (id) => {
|
||||
let params = { id: id };
|
||||
return defHttp.get({ url: Api.queryById, params },{ isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 文件列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const fileList = (params) => defHttp.get({ url: Api.fileList, params });
|
||||
|
||||
/**
|
||||
* 删除单个
|
||||
*/
|
||||
export const deleteOne = (params) => {
|
||||
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存
|
||||
* @param params
|
||||
*/
|
||||
export const saveOne = (params) => {
|
||||
let url = Api.addText;
|
||||
return defHttp.post({ url: url, params }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 数据日志列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const getLogList = (params) => defHttp.get({ url: Api.logList, params }, {isTransformResponse: false});
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传接口
|
||||
*/
|
||||
export const uploadFileUrl = `${baseUploadUrl}/sys/comment/addFile`;
|
||||
|
||||
export function useCommentWithFile(props) {
|
||||
let uploadData = {
|
||||
biz: 'comment',
|
||||
commentId: '',
|
||||
};
|
||||
const { createMessage } = useMessage();
|
||||
const buttonLoading = ref(false);
|
||||
|
||||
//确定按钮触发
|
||||
async function saveCommentAndFiles(obj, fileList) {
|
||||
buttonLoading.value = true;
|
||||
setTimeout(() => {
|
||||
buttonLoading.value = false;
|
||||
}, 500);
|
||||
await saveComment(obj);
|
||||
await uploadFiles(fileList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存评论
|
||||
*/
|
||||
async function saveComment(obj) {
|
||||
const {fromUserId, toUserId, commentId, commentContent} = obj;
|
||||
let commentData = {
|
||||
tableName: props.tableName,
|
||||
tableDataId: props.dataId,
|
||||
fromUserId,
|
||||
commentContent,
|
||||
toUserId: '',
|
||||
commentId: ''
|
||||
};
|
||||
if(toUserId){
|
||||
commentData.toUserId = toUserId;
|
||||
}
|
||||
if(commentId){
|
||||
commentData.commentId = commentId;
|
||||
}
|
||||
uploadData.commentId = '';
|
||||
const res = await saveOne(commentData);
|
||||
if (res.success) {
|
||||
uploadData.commentId = res.result;
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
return Promise.reject('保存评论失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadOne(file) {
|
||||
let url = uploadFileUrl;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('tableName', props.tableName);
|
||||
formData.append('tableDataId', props.dataId);
|
||||
Object.keys(uploadData).map((k) => {
|
||||
formData.append(k, uploadData[k]);
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
uploadMyFile(url, formData).then((res: any) => {
|
||||
console.log('uploadMyFile', res);
|
||||
if (res && res.data) {
|
||||
if (res.data.result == 'success') {
|
||||
resolve(1);
|
||||
} else {
|
||||
createMessage.warning(res.data.message);
|
||||
reject();
|
||||
}
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function uploadFiles(fileList) {
|
||||
if (fileList && fileList.length > 0) {
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
let file = toRaw(fileList[i]);
|
||||
await uploadOne(file.originFileObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
saveCommentAndFiles,
|
||||
buttonLoading,
|
||||
};
|
||||
}
|
||||
|
||||
export function uploadMu(fileList) {
|
||||
const formData = new FormData();
|
||||
// let arr = []
|
||||
for(let file of fileList){
|
||||
formData.append('files[]', file.originFileObj);
|
||||
}
|
||||
console.log(formData)
|
||||
let url = `${baseUploadUrl}/sys/comment/addFile2`;
|
||||
uploadMyFile(url, formData).then((res: any) => {
|
||||
console.log('uploadMyFile', res);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示文件列表
|
||||
*/
|
||||
export function useFileList() {
|
||||
const imageSrcMap = reactive({});
|
||||
const typeMap = {
|
||||
xls: excel,
|
||||
xlsx: excel,
|
||||
pdf: pdf,
|
||||
txt: txt,
|
||||
docx: word,
|
||||
doc: word,
|
||||
};
|
||||
function getBackground(item) {
|
||||
console.log('获取文件背景图', item);
|
||||
if (isImage(item)) {
|
||||
return 'none'
|
||||
} else {
|
||||
const name = item.name;
|
||||
if(!name){
|
||||
return 'none';
|
||||
}
|
||||
const suffix = name.substring(name.lastIndexOf('.') + 1);
|
||||
console.log('suffix', suffix)
|
||||
let bg = typeMap[suffix];
|
||||
if (!bg) {
|
||||
bg = other;
|
||||
}
|
||||
return bg;
|
||||
}
|
||||
}
|
||||
|
||||
function getBase64(file, id){
|
||||
return new Promise((resolve, reject) => {
|
||||
//声明js的文件流
|
||||
let reader = new FileReader();
|
||||
if(file){
|
||||
//通过文件流将文件转换成Base64字符串
|
||||
reader.readAsDataURL(file);
|
||||
//转换成功后
|
||||
reader.onload = function () {
|
||||
let base = reader.result;
|
||||
console.log('base', base)
|
||||
imageSrcMap[id] = base;
|
||||
console.log('imageSrcMap', imageSrcMap)
|
||||
resolve(base)
|
||||
}
|
||||
}else{
|
||||
reject();
|
||||
}
|
||||
})
|
||||
}
|
||||
function handleImageSrc(file){
|
||||
if(isImage(file)){
|
||||
let id = file.uid;
|
||||
getBase64(file, id);
|
||||
}
|
||||
}
|
||||
|
||||
function downLoad(file) {
|
||||
let url = getFileAccessHttpUrl(file.url);
|
||||
if (url) {
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
|
||||
function getFileSize(item) {
|
||||
let size = item.fileSize;
|
||||
if (!size) {
|
||||
return '0B';
|
||||
}
|
||||
let temp = Math.round(size / 1024);
|
||||
return temp + ' KB';
|
||||
}
|
||||
|
||||
const selectFileList = ref<any[]>([]);
|
||||
function beforeUpload(file) {
|
||||
handleImageSrc(file);
|
||||
selectFileList.value = [...selectFileList.value, file];
|
||||
console.log('selectFileList', unref(selectFileList));
|
||||
return false
|
||||
}
|
||||
|
||||
function handleRemove(file) {
|
||||
const index = selectFileList.value.indexOf(file);
|
||||
const newFileList = selectFileList.value.slice();
|
||||
newFileList.splice(index, 1);
|
||||
selectFileList.value = newFileList;
|
||||
}
|
||||
|
||||
function isImage(item){
|
||||
const type = item.type||'';
|
||||
if (type.indexOf('image') >= 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getImageSrc(file){
|
||||
if(isImage(file)){
|
||||
let id = file.uid;
|
||||
if(id){
|
||||
if(imageSrcMap[id]){
|
||||
return imageSrcMap[id];
|
||||
}
|
||||
}else if(file.url){
|
||||
//数据库中地址
|
||||
let url = getFileAccessHttpUrl(file.url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示图片
|
||||
* @param item
|
||||
*/
|
||||
function getImageAsBackground(item){
|
||||
let url = getImageSrc(item);
|
||||
if(url){
|
||||
return {
|
||||
"backgroundImage": "url('"+url+"')"
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览列表 cell 图片
|
||||
* @param text
|
||||
*/
|
||||
async function viewImage(file) {
|
||||
if(isImage(file)){
|
||||
let text = getImageSrc(file)
|
||||
if (text) {
|
||||
let imgList = [text];
|
||||
createImgPreview({ imageList: imgList });
|
||||
}
|
||||
}else{
|
||||
if(file.url){
|
||||
//数据库中地址
|
||||
let url = getFileAccessHttpUrl(file.url);
|
||||
await initViewDomain();
|
||||
//本地测试需要将文件地址的localhost/127.0.0.1替换成IP, 或是直接修改全局domain
|
||||
//url = url.replace('localhost', '192.168.1.100')
|
||||
//如果集成的KkFileview-v3.3.0+ 需要对url再做一层base64编码 encodeURIComponent(encryptByBase64(url))
|
||||
window.open(onlinePreviewDomain+'?officePreviewType=pdf&url='+encodeURIComponent(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化domain
|
||||
*/
|
||||
async function initViewDomain(){
|
||||
if(!onlinePreviewDomain){
|
||||
onlinePreviewDomain = await getViewFileDomain();
|
||||
}
|
||||
if(!onlinePreviewDomain.startsWith('http')){
|
||||
onlinePreviewDomain = 'http://'+ onlinePreviewDomain;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectFileList,
|
||||
getBackground,
|
||||
getFileSize,
|
||||
downLoad,
|
||||
beforeUpload,
|
||||
handleRemove,
|
||||
isImage,
|
||||
getImageSrc,
|
||||
getImageAsBackground,
|
||||
viewImage
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于emoji渲染
|
||||
*/
|
||||
export function useEmojiHtml(){
|
||||
const COLONS_REGEX = new RegExp('([^:]+)?(:[a-zA-Z0-9-_+]+:(:skin-tone-[2-6]:)?)','g');
|
||||
let emojisToShowFilter = function() {
|
||||
return true;
|
||||
}
|
||||
let emojiIndex = new EmojiIndex(data, {
|
||||
emojisToShowFilter,
|
||||
exclude:['recent','people','nature','foods','activity','places','objects','symbols','flags']
|
||||
});
|
||||
|
||||
function getHtml(text) {
|
||||
if(!text){
|
||||
return ''
|
||||
}
|
||||
return text.replace(COLONS_REGEX, function (match, p1, p2) {
|
||||
const before = p1 || ''
|
||||
if (endsWith(before, 'alt="') || endsWith(before, 'data-text="')) {
|
||||
return match
|
||||
}
|
||||
let emoji = emojiIndex.findEmoji(p2)
|
||||
if (!emoji) {
|
||||
return match
|
||||
}
|
||||
return before + emoji2Html(emoji)
|
||||
})
|
||||
return text;
|
||||
}
|
||||
|
||||
function endsWith(str, temp){
|
||||
return str.endsWith(temp)
|
||||
}
|
||||
|
||||
function emoji2Html(emoji) {
|
||||
let style = `position: absolute;top: -3px;left: 3px;width: 18px; height: 18px;background-position: ${emoji.getPosition()}`
|
||||
return `<span style="width: 24px" class="emoji-mart-emoji"><span class="my-emoji-icon emoji-set-apple emoji-type-image" style="${style}"> </span> </span>`
|
||||
}
|
||||
|
||||
return {
|
||||
emojiIndex,
|
||||
getHtml
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取modal窗体高度
|
||||
*/
|
||||
export function getModalHeight(){
|
||||
return window.innerHeight;
|
||||
}
|
Loading…
Reference in New Issue