<script lang="ts" setup>
|
import type { UploadChangeParam, UploadFile } from 'ant-design-vue';
|
|
import { computed, nextTick, reactive, ref, toRefs } from 'vue';
|
|
import { useGlobSetting, useMessage } from '@jnpf/hooks';
|
import { createImgPreview } from '@jnpf/ui';
|
import { useModal } from '@jnpf/ui/modal';
|
|
import { useAccessStore } from '@vben/stores';
|
|
import { Upload as AUpload } from 'ant-design-vue';
|
|
import { getTaskUserList } from '#/api/workFlow/task';
|
import { createComment } from '#/api/workFlow/template';
|
import { QueryUserSelectModal } from '#/components/CommonModal';
|
import emojiJson from '#/layouts/header/chat/emoji.json';
|
import { getAuthMediaUrl } from '#/utils/jnpf';
|
|
interface State {
|
commentActive: boolean;
|
imgList: UploadFile[];
|
emojiList: any[];
|
popoverVisible: boolean;
|
imgUploading: boolean;
|
dataForm: any;
|
selectionStart: number;
|
selectForm: string;
|
btnLoading: boolean;
|
}
|
|
const props = defineProps({
|
inner: { type: Boolean, default: false },
|
taskId: { type: [String, Number], default: '' },
|
replyId: { type: String, default: '' },
|
});
|
const emit = defineEmits(['hideCommentInput', 'reload']);
|
const state = reactive<State>({
|
commentActive: false,
|
imgList: [],
|
emojiList: emojiJson,
|
popoverVisible: false,
|
imgUploading: false,
|
dataForm: {
|
text: '',
|
file: [],
|
image: [],
|
},
|
selectionStart: 0,
|
selectForm: '',
|
btnLoading: false,
|
});
|
const { commentActive, dataForm, btnLoading } = toRefs(state);
|
const [registerUserSelectModal, { openModal: openUserSelectModal }] = useModal();
|
const { createMessage } = useMessage();
|
const globSetting = useGlobSetting();
|
const accessStore = useAccessStore();
|
const token = accessStore.accessToken;
|
const uploadFileRef = ref<any>(null);
|
const textRef = ref<any>(null);
|
|
const getAction = computed(() => `${globSetting.uploadURL}/annexpic`);
|
const getHeaders = computed(() => ({ Authorization: token as string }));
|
const getWrapperClass = computed(() => {
|
return [
|
'comment-input-wrapper',
|
{
|
'comment-input-wrapper-inner': props.inner,
|
'comment-input-wrapper-active': state.commentActive,
|
},
|
];
|
});
|
const getSubmitDisabled = computed(() => !state.dataForm.text);
|
|
function showCommentInput() {
|
state.commentActive = true;
|
nextTick(() => {
|
textRef.value?.focus();
|
});
|
}
|
function hideCommentInput() {
|
state.commentActive = false;
|
clearAll();
|
if (props.inner) emit('hideCommentInput');
|
}
|
function clearAll() {
|
state.btnLoading = false;
|
state.dataForm.text = '';
|
state.imgList = [];
|
state.dataForm.image = [];
|
state.dataForm.file = [];
|
}
|
function sendComment() {
|
state.btnLoading = true;
|
const query = {
|
text: state.dataForm.text,
|
file: JSON.stringify(state.dataForm.file),
|
image: JSON.stringify(state.dataForm.image),
|
taskId: props.taskId,
|
replyId: props.replyId,
|
};
|
createComment(query)
|
.then(() => {
|
state.btnLoading = false;
|
clearAll();
|
emit('reload');
|
})
|
.catch(() => {
|
state.btnLoading = false;
|
});
|
}
|
function handleContentChange(e) {
|
if (e.data !== '@') return;
|
state.selectForm = 'input';
|
state.selectionStart = e.target.selectionStart;
|
openUserSelectModal(true, {});
|
}
|
function openSelectUser() {
|
state.selectForm = 'btn';
|
state.selectionStart = -1;
|
openUserSelectModal(true, {});
|
}
|
function handleSelectUser(data) {
|
if (!data || !data.length) return;
|
let addContent = state.selectForm === 'btn' ? '@' : '';
|
for (const [i, datum] of data.entries()) {
|
const str = `${i > 0 ? '@' : ''}{${datum.fullName}}`;
|
addContent += str;
|
}
|
if (state.selectionStart === -1) {
|
state.dataForm.text += addContent;
|
textRef.value.focus();
|
} else {
|
const oldValue = state.dataForm.text;
|
const rangeIndex = state.selectionStart + addContent.length;
|
state.dataForm.text = oldValue.slice(0, state.selectionStart) + addContent + oldValue.slice(state.selectionStart);
|
setTimeout(() => {
|
textRef.value.focus();
|
textRef.value?.resizableTextArea?.textArea?.setSelectionRange(rangeIndex, rangeIndex);
|
}, 50);
|
}
|
}
|
// 选择表情
|
function handleSelectEmoji(item) {
|
state.dataForm.text += item.alt;
|
state.popoverVisible = false;
|
textRef.value.focus();
|
}
|
function beforeUpload(file) {
|
const isTopLimit = state.dataForm.image.length >= 9;
|
if (isTopLimit) {
|
createMessage.error(`最多可以上传9张图片`);
|
return AUpload.LIST_IGNORE;
|
}
|
const isRightSize = file.size < 10 * 1024 * 1024;
|
if (!isRightSize) {
|
createMessage.error(`图片大小超过10MB`);
|
return AUpload.LIST_IGNORE;
|
}
|
const isAccept = /image\/*/.test(file.type);
|
if (!isAccept) {
|
createMessage.error(`请上传图片`);
|
return AUpload.LIST_IGNORE;
|
}
|
return isRightSize && isAccept;
|
}
|
function handleChange({ file }: UploadChangeParam) {
|
if (file.status === 'uploading') return (state.imgUploading = true);
|
if (file.status === 'error') {
|
state.imgUploading = false;
|
state.imgList = state.imgList.filter((o) => o.uid != file.uid);
|
createMessage.error('上传失败');
|
return;
|
}
|
if (file.status === 'done') {
|
state.imgUploading = false;
|
if (file.response.code === 200) {
|
const isTopLimit = state.dataForm.image.length >= 9;
|
if (isTopLimit) {
|
createMessage.error(`最多可以上传9张图片`);
|
return false;
|
}
|
state.dataForm.image.push({
|
name: file.name,
|
fileId: file.response.data.name,
|
url: file.response.data.url,
|
});
|
} else {
|
state.imgList = state.imgList.filter((o) => o.uid != file.uid);
|
createMessage.error(file.response.msg);
|
}
|
}
|
}
|
function handleImgRemove(index) {
|
state.dataForm.image.splice(index, 1);
|
state.imgList.splice(index, 1);
|
}
|
function handlePreview(index) {
|
const imageList = state.dataForm.image.map((o) => getAuthMediaUrl(o.url));
|
createImgPreview({ imageList, index });
|
}
|
function uploadFile() {
|
if (state.dataForm.file.length >= 9) return createMessage.error('最多可以上传9个文件');
|
uploadFileRef.value?.uploadFile();
|
}
|
</script>
|
|
<template>
|
<div :class="getWrapperClass">
|
<div class="comment-input-placeholder" v-if="!commentActive" @click="showCommentInput">请输入</div>
|
<div v-if="commentActive">
|
<a-textarea
|
v-model:value="dataForm.text"
|
class="comment-input-wrapper-content"
|
placeholder="请输入"
|
:auto-size="{ minRows: 4, maxRows: 4 }"
|
:maxlength="500"
|
show-count
|
ref="textRef"
|
@input="handleContentChange" />
|
<template v-if="dataForm.image.length">
|
<div class="comment-img-list">
|
<div class="img-item" v-for="(item, i) in dataForm.image" :key="i">
|
<img :src="getAuthMediaUrl(item.url)" class="img-item" @click="handlePreview(i)" />
|
<div class="badge" @click.stop="handleImgRemove(i)">
|
<i class="icon-ym icon-ym-nav-close"></i>
|
</div>
|
</div>
|
</div>
|
</template>
|
<jnpf-upload-file ref="uploadFileRef" v-model:value="dataForm.file" :limit="9" detailed :show-all-download="false" class="comment-upload-file" />
|
<div class="comment-input-wrapper-actions">
|
<div class="actions-left">
|
<a-tooltip title="选择@用户">
|
<i class="icon-ym icon-ym-roll-call" @click="openSelectUser()"></i>
|
</a-tooltip>
|
<AUpload
|
v-model:file-list="state.imgList"
|
accept="image/*"
|
:show-upload-list="false"
|
multiple
|
:max-count="9"
|
:action="getAction"
|
:headers="getHeaders"
|
:before-upload="beforeUpload"
|
@change="handleChange">
|
<a-tooltip title="上传图片">
|
<i class="icon-ym icon-ym-comment-img"></i>
|
</a-tooltip>
|
</AUpload>
|
<a-tooltip title="上传附件">
|
<i class="icon-ym icon-ym-comment-file" @click="uploadFile"></i>
|
</a-tooltip>
|
<a-popover placement="topLeft" trigger="click" overlay-class-name="emoji-popover" v-model:open="state.popoverVisible">
|
<a-tooltip title="表情">
|
<i class="icon-ym icon-ym-emoji"></i>
|
</a-tooltip>
|
<template #content>
|
<div class="emojiBox">
|
<ul class="emoji">
|
<li v-for="(item, i) in state.emojiList" :key="i" @click="handleSelectEmoji(item)">
|
<img :src="`/resource/emoji/${item.url}`" />
|
</li>
|
</ul>
|
</div>
|
</template>
|
</a-popover>
|
</div>
|
<div>
|
<a-button @click="hideCommentInput" class="mr-[10px]">取消</a-button>
|
<a-button type="primary" @click="sendComment" :disabled="getSubmitDisabled" :loading="btnLoading">发送</a-button>
|
</div>
|
</div>
|
</div>
|
<QueryUserSelectModal :api="getTaskUserList" :query="{ taskId }" @register="registerUserSelectModal" @confirm="handleSelectUser" />
|
</div>
|
</template>
|