You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
paopao-ce/web/src/components/compose-comment.vue

468 lines
14 KiB

3 years ago
<template>
<div>
<div class="compose-wrap" v-if="store.state.userInfo.id > 0">
<div class="compose-line">
<div class="compose-user">
<n-avatar
round
:size="30"
:src="store.state.userInfo.avatar"
/>
</div>
<n-mention
type="textarea"
size="large"
autosize
:bordered="false"
:options="optionsRef"
:prefix="['@']"
:loading="loading"
:value="content"
:disabled="props.lock === 1"
@update:value="changeContent"
@search="handleSearch"
@focus="focusComment"
:placeholder="
props.lock === 1
? ''
: '...'
"
/>
</div>
<n-upload
v-if="showBtn"
ref="uploadRef"
abstract
list-type="image"
:multiple="true"
:max="9"
:action="uploadGateway"
:headers="{
Authorization: uploadToken,
}"
:data="{
type: uploadType,
}"
:file-list="fileQueue"
3 years ago
@before-upload="beforeUpload"
@finish="finishUpload"
@error="failUpload"
@remove="removeUpload"
@update:file-list="updateUpload"
>
<div class="compose-line compose-options">
<div class="attachment">
<n-upload-trigger #="{ handleClick }" abstract>
<n-button
:disabled="
(fileQueue.length > 0 &&
uploadType === 'public/video') ||
fileQueue.length === 9
"
@click="
() => {
setUploadType('public/image');
handleClick();
}
"
quaternary
circle
type="primary"
>
<template #icon>
<n-icon
size="20"
color="var(--primary-color)"
>
<image-outline />
</n-icon>
</template>
</n-button>
</n-upload-trigger>
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<n-progress
class="text-statistic"
type="circle"
:show-indicator="false"
status="success"
:stroke-width="10"
:percentage="(content.length / defaultCommentMaxLength) * 100"
3 years ago
/>
</template>
{{ content.length }} / {{ defaultCommentMaxLength }}
3 years ago
</n-tooltip>
</div>
<div class="submit-wrap">
<n-button
quaternary
round
type="tertiary"
class="cancel-btn"
size="small"
@click="cancelComment"
>
</n-button>
<n-button
:loading="submitting"
@click="submitPost"
type="primary"
secondary
size="small"
round
>
</n-button>
</div>
</div>
<div class="attachment-list-wrap">
<n-upload-file-list />
</div>
</n-upload>
</div>
<div class="compose-wrap" v-else>
<div class="login-wrap">
<span class="login-banner"> </span>
</div>
<div v-if="!allowUserRegister" class="login-only-wrap">
<n-button
strong
secondary
round
type="primary"
@click="triggerAuth('signin')"
>
</n-button>
</div>
<div v-if="allowUserRegister" class="login-wrap">
3 years ago
<n-button
strong
secondary
round
type="primary"
@click="triggerAuth('signin')"
>
</n-button>
<n-button
strong
secondary
round
type="info"
@click="triggerAuth('signup')"
>
</n-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue';
3 years ago
import { useStore } from 'vuex';
import { debounce } from 'lodash';
import {
ImageOutline,
} from '@vicons/ionicons5';
import { createComment } from '@/api/post';
import { getSuggestUsers } from '@/api/user';
import { parsePostTag } from '@/utils/content';
import type { MentionOption, UploadFileInfo, UploadInst } from 'naive-ui';
3 years ago
3 years ago
const emit = defineEmits<{
(e: 'post-success'): void;
3 years ago
}>();
const props = withDefaults(
defineProps<{
lock: number;
postId: number;
}>(),
{
lock: 0,
postId: 0,
}
);
3 years ago
const store = useStore();
const optionsRef = ref<MentionOption[]>([]);
3 years ago
const showBtn = ref(false);
const loading = ref(false);
const submitting = ref(false);
const content = ref('');
const uploadRef = ref<UploadInst>();
3 years ago
const uploadType = ref('public/image');
const fileQueue = ref<UploadFileInfo[]>([]);
const imageContents = ref<Item.CommentItemProps[]>([]);
const allowUserRegister = ref(import.meta.env.VITE_ALLOW_USER_REGISTER.toLowerCase() === 'true')
const defaultCommentMaxLength = Number(import.meta.env.VITE_DEFAULT_COMMENT_MAX_LENGTH)
const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment';
3 years ago
const uploadToken = computed(() => {
return 'Bearer ' + localStorage.getItem('PAOPAO_TOKEN');
});
3 years ago
// 加载at用户列表
const loadSuggestionUsers = debounce((k) => {
getSuggestUsers({
k,
})
.then((res) => {
let options: MentionOption[] = [];
res.suggest.map((i) => {
3 years ago
options.push({
label: i,
value: i,
});
});
optionsRef.value = options;
loading.value = false;
})
.catch((err) => {
loading.value = false;
});
}, 200);
const handleSearch = (k: string, prefix: string) => {
3 years ago
if (loading.value) {
return;
}
loading.value = true;
if (prefix === '@') {
loadSuggestionUsers(k);
}
};
const changeContent = (v: string) => {
if (v.length > defaultCommentMaxLength) {
content.value = v.substring(0, defaultCommentMaxLength);
} else {
content.value = v;
3 years ago
}
};
const setUploadType = (type: string) => {
3 years ago
uploadType.value = type;
};
const updateUpload = (list: UploadFileInfo[]) => {
for (let i = 0; i < list.length; i++) {
var name = list[i].name;
var basename: string = name.split('.').slice(0, -1).join('.');
var ext: string = name.split('.').pop()!;
if (basename.length > 30) {
list[i].name = basename.substring(0, 18) + "..." + basename.substring(basename.length-9) + "." + ext;
}
}
3 years ago
fileQueue.value = list;
};
const beforeUpload = async (data: any) => {
3 years ago
// 图片类型校验
if (
uploadType.value === 'public/image' &&
!['image/png', 'image/jpg', 'image/jpeg', 'image/gif'].includes(
(data.file as any).file?.type
3 years ago
)
) {
window.$message.warning(' png/jpg/gif ');
return false;
}
if (
uploadType.value === 'image' &&
(data.file as any).file?.size > 10485760
) {
3 years ago
window.$message.warning('10MB');
return false;
}
return true;
};
const finishUpload = ({ file, event }: any): any => {
3 years ago
try {
let data = JSON.parse(event.target?.response);
if (data.code === 0) {
if (uploadType.value === 'public/image') {
imageContents.value.push({
id: file.id,
content: data.data.content,
} as Item.CommentItemProps);
3 years ago
}
}
} catch (error) {
window.$message.error('');
}
};
const failUpload = ({ file, event }: any): any => {
3 years ago
try {
let data = JSON.parse(event.target?.response);
if (data.code !== 0) {
let errMsg = data.msg || '';
if (data.details && data.details.length > 0) {
data.details.map((detail: string) => {
3 years ago
errMsg += ':' + detail;
});
}
window.$message.error(errMsg);
}
} catch (error) {
window.$message.error('');
}
};
const removeUpload = ({ file }: any) => {
3 years ago
let idx = imageContents.value.findIndex((item) => item.id === file.id);
if (idx > -1) {
imageContents.value.splice(idx, 1);
}
};
const focusComment = () => {
showBtn.value = true;
};
const cancelComment = () => {
showBtn.value = false;
// 置空
uploadRef.value?.clear();
fileQueue.value = [];
content.value = '';
imageContents.value = [];
};
// 发布动态
const submitPost = () => {
if (content.value.trim().length === 0) {
window.$message.warning('');
return;
}
// 解析用户at
let { users } = parsePostTag(content.value);
const contents = [];
let sort = 100;
contents.push({
content: content.value,
type: 2, // 文字
sort,
});
imageContents.value.map((img) => {
sort++;
contents.push({
content: img.content,
type: 3, // 图片
sort,
});
});
submitting.value = true;
createComment({
contents,
post_id: props.postId,
users: Array.from(new Set(users)),
})
.then((res) => {
window.$message.success('');
submitting.value = false;
emit('post-success');
// 置空
cancelComment();
})
.catch((err) => {
submitting.value = false;
});
};
const triggerAuth = (key: string) => {
3 years ago
store.commit('triggerAuth', true);
store.commit('triggerAuthKey', key);
};
</script>
<style lang="less" scoped>
.compose-wrap {
width: 100%;
padding: 16px;
box-sizing: border-box;
.compose-line {
display: flex;
flex-direction: row;
.compose-user {
width: 42px;
height: 42px;
display: flex;
align-items: center;
}
&.compose-options {
margin-top: 6px;
padding-left: 42px;
display: flex;
justify-content: space-between;
.submit-wrap {
display: flex;
align-items: center;
.cancel-btn {
margin-right: 8px;
}
}
}
}
.login-only-wrap {
display: flex;
justify-content: center;
width: 100%;
button {
margin: 0 4px;
width: 50%
}
}
3 years ago
.login-wrap {
display: flex;
justify-content: center;
width: 100%;
.login-banner {
margin-bottom: 12px;
opacity: 0.8;
}
button {
margin: 0 4px;
}
}
}
.attachment {
display: flex;
align-items: center;
.text-statistic {
margin-left: 8px;
width: 18px;
height: 18px;
transform: rotate(180deg);
}
}
.attachment-list-wrap {
margin-top: 12px;
margin-left: 42px;
.n-upload-file-info__thumbnail {
overflow: hidden;
}
}
.dark {
.compose-mention {
background-color: rgba(16, 16, 20, 0.75);
}
.compose-wrap {
background-color: rgba(16, 16, 20, 0.75);
}
}
3 years ago
</style>