diff --git a/src/components/jeecg/comment/CommentFiles.vue b/src/components/jeecg/comment/CommentFiles.vue new file mode 100644 index 0000000..e9e232a --- /dev/null +++ b/src/components/jeecg/comment/CommentFiles.vue @@ -0,0 +1,164 @@ + + + + + + + 上传 + + + + 从文件库选择? + + + + + + + + + + + + + + + + + + + + + + + {{ item.name }} + + + + + {{ item.name }} + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + + + + diff --git a/src/components/jeecg/comment/CommentList.vue b/src/components/jeecg/comment/CommentList.vue new file mode 100644 index 0000000..3afcd3e --- /dev/null +++ b/src/components/jeecg/comment/CommentList.vue @@ -0,0 +1,328 @@ + + + + + + + + {{ getAvatarText(item) }} + + + + + {{ item.fromUserId_dictText }} + + + 回复 + {{ item.toUserId_dictText }} + visibleChange(v, item)"> + + + + + + + + + + + + + {{ getDateDiff(item) }} + + + + + + 回复 + + + 删除 + + + + + + + + + + + + + + + replyComment(item, content, fileList)" :inputFocus="focusStatus"> + + + + + + + + + {{ getMyname() }} + + + + + + + + + + + + diff --git a/src/components/jeecg/comment/CommentPanel.vue b/src/components/jeecg/comment/CommentPanel.vue new file mode 100644 index 0000000..3416603 --- /dev/null +++ b/src/components/jeecg/comment/CommentPanel.vue @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/jeecg/comment/DataLogList.vue b/src/components/jeecg/comment/DataLogList.vue new file mode 100644 index 0000000..22fa619 --- /dev/null +++ b/src/components/jeecg/comment/DataLogList.vue @@ -0,0 +1,177 @@ + + + + + + + + + + + + @{{item.createBy}} + {{ item.dataContent }} + + + + {{ getDateDiff(item) }} + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/jeecg/comment/HistoryFileList.vue b/src/components/jeecg/comment/HistoryFileList.vue new file mode 100644 index 0000000..ba039ee --- /dev/null +++ b/src/components/jeecg/comment/HistoryFileList.vue @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + {{ item.name }} + + + + + + + {{ item.name }} + + + {{ getFileSize(item) }} + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/jeecg/comment/MyComment.vue b/src/components/jeecg/comment/MyComment.vue new file mode 100644 index 0000000..42b6f2d --- /dev/null +++ b/src/components/jeecg/comment/MyComment.vue @@ -0,0 +1,383 @@ + + + + + + + + + + + + + + + + + + + + + 取消 + 发 送 + + + + + + + + + + + + + + + + + diff --git a/src/components/jeecg/comment/UploadChunk.vue b/src/components/jeecg/comment/UploadChunk.vue new file mode 100644 index 0000000..6d13bde --- /dev/null +++ b/src/components/jeecg/comment/UploadChunk.vue @@ -0,0 +1,119 @@ + + + + + + + 上传 + + + + 从文件库选择? + + + + + + + + + + + + + + + + + + + + + + + {{ item.name }} + + + + + + {{ item.name }} + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/jeecg/comment/comment.less b/src/components/jeecg/comment/comment.less new file mode 100644 index 0000000..824390c --- /dev/null +++ b/src/components/jeecg/comment/comment.less @@ -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; + } + } + } + } +} diff --git a/src/components/jeecg/comment/image/emoji.png b/src/components/jeecg/comment/image/emoji.png new file mode 100644 index 0000000..eaef1f3 Binary files /dev/null and b/src/components/jeecg/comment/image/emoji.png differ diff --git a/src/components/jeecg/comment/image/emoji_native.png b/src/components/jeecg/comment/image/emoji_native.png new file mode 100644 index 0000000..9efea64 Binary files /dev/null and b/src/components/jeecg/comment/image/emoji_native.png differ diff --git a/src/components/jeecg/comment/useComment.ts b/src/components/jeecg/comment/useComment.ts new file mode 100644 index 0000000..5f8aa18 --- /dev/null +++ b/src/components/jeecg/comment/useComment.ts @@ -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([]); + 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 ` ` + } + + return { + emojiIndex, + getHtml + } +} + +/** + * 获取modal窗体高度 + */ +export function getModalHeight(){ + return window.innerHeight; +}