🎨 updated comment
This commit is contained in:
@@ -7,6 +7,16 @@
|
||||
--comment-text-color: #767779;
|
||||
// 评论子评论背景颜色
|
||||
--comment-child-background-color: #f5f5f5;
|
||||
|
||||
// 评论列表背景颜色
|
||||
--comment-list-background-color: #fafafa;
|
||||
|
||||
// 举报弹窗文字颜色
|
||||
--comment-report-text-color: rgba(15, 15, 16, 0.58);
|
||||
// 评论框边框颜色
|
||||
--comment-child-box-border-color: #90d952;
|
||||
|
||||
|
||||
}
|
||||
|
||||
[data-dark="dark"] {
|
||||
@@ -18,6 +28,14 @@
|
||||
--comment-text-color: #ffffff;
|
||||
// 评论子评论背景颜色
|
||||
--comment-child-background-color: rgb(0, 0, 0);
|
||||
|
||||
// 评论列表背景颜色
|
||||
--comment-list-background-color: rgba(15, 15, 16, 0.3);
|
||||
|
||||
// 举报弹窗文字颜色
|
||||
--comment-report-text-color: #ffffff;
|
||||
// 评论框边框颜色
|
||||
--comment-child-box-border-color: #ffffff;
|
||||
}
|
||||
|
||||
#app {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
.comment-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #ccc;
|
||||
//border: 1px solid #ccc;
|
||||
margin-top: 20px;
|
||||
width: 650px;
|
||||
width: 700px;
|
||||
padding: 50px;
|
||||
|
||||
.comment-header-title {
|
||||
|
@@ -7,7 +7,7 @@
|
||||
margin-left: 20px;
|
||||
|
||||
.comment-text {
|
||||
width: 600px;
|
||||
width: 630px;
|
||||
}
|
||||
|
||||
.comment-editor {
|
||||
|
@@ -8,12 +8,12 @@
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" justify="flex-end">
|
||||
<AButton type="text" size="small" @click="getHotCommentList" :icon="h(FireOutlined)"
|
||||
:style="{color: router.currentRoute.value.query.type === 'hot'? '#08a327' : '#000'}"
|
||||
:style="{color: router.currentRoute.value.query.type === 'hot'? '#08a327' : 'var(--text-color)'}"
|
||||
class="reply-header-hot">
|
||||
{{ t('comment.hot') }}
|
||||
</AButton>
|
||||
<AButton type="text" size="small" @click="getLatestCommentList" :icon="h(ClockCircleOutlined)"
|
||||
:style="{color: router.currentRoute.value.query.type === 'latest'? '#08a327' : '#000'}"
|
||||
:style="{color: router.currentRoute.value.query.type === 'latest'? '#08a327' : 'var(--text-color)'}"
|
||||
class="reply-header-latest">
|
||||
{{ t('comment.latest') }}
|
||||
</AButton>
|
||||
@@ -25,16 +25,22 @@
|
||||
<div class="reply-item" v-for="(item, index) in comment.commentList.comments" :key="index">
|
||||
<AFlex :vertical="false" style="margin-top: 5px">
|
||||
<!-- 评论头像 -->
|
||||
<AFlex :vertical="true" class="reply-avatar" v-if="item.avatar">
|
||||
<AAvatar :size="50" class="reply-avatar-img" shape="circle" :src="item.avatar"/>
|
||||
</AFlex>
|
||||
<ABadge :offset="[0,0]" :dot="false">
|
||||
<template #count v-if="true">
|
||||
<img src="/level_icon/up.svg" style="width: 20px;height: 20px;" alt="lv2">
|
||||
</template>
|
||||
<AFlex :vertical="true" class="reply-avatar" v-if="item.avatar">
|
||||
<AAvatar :size="50" class="reply-avatar-img" shape="circle" :src="item.avatar"/>
|
||||
</AFlex>
|
||||
</ABadge>
|
||||
<!-- 评论内容 -->
|
||||
<AFlex :vertical="true" class="reply-content">
|
||||
<AFlex :vertical="true">
|
||||
<AFlex :vertical="false" align="flex-start">
|
||||
<span class="reply-name">{{ item.nickname }}</span>
|
||||
<a-tag color="cyan" class="reply-tag" size="small">Lv.5</a-tag>
|
||||
<a-tag color="red" class="reply-tag" size="small" v-if="item.author===1">UP</a-tag>
|
||||
<img src="/level_icon/3/lv5.png" class="reply-level-icon" alt="lv1">
|
||||
<img src="/level_icon/4/4.png" class="reply-level-icon" alt="lv2">
|
||||
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="flex-end" justify="space-between">
|
||||
<AFlex :vertical="false" align="center" justify="space-between">
|
||||
@@ -75,8 +81,10 @@
|
||||
comment.handleShowCommentReply(item.id);
|
||||
replyListThrottled(item.id)}" type="text" size="small"
|
||||
:icon="h(MessageOutlined)"
|
||||
:disabled="item.reply_count === 0"
|
||||
v-show="item.reply_count > 0"
|
||||
class="reply-action-btn">
|
||||
{{ item.reply_count }}
|
||||
查看{{ item.reply_count >= 99 ? '99+' : item.reply_count }}条回复
|
||||
</AButton>
|
||||
<AButton
|
||||
@click="comment.handleShowReplyInput(item.id)"
|
||||
@@ -113,13 +121,13 @@
|
||||
|
||||
</AFlex>
|
||||
<!-- 回复输入框 -->
|
||||
<ReplyInput :item="item" v-if="comment.showReplyInput && item.id === comment.showReplyInput"/>
|
||||
<ReplyInput :item="item" v-show="comment.showReplyInput && item.id === comment.showReplyInput"/>
|
||||
<!-- 子回复列表 -->
|
||||
<ReplyList :item="item" v-if="comment.showCommentReply && item.id === comment.showCommentReply"/>
|
||||
<ReplyList :item="item" v-show="comment.showCommentReply && item.id === comment.showCommentReply"/>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
</div>
|
||||
<APagination v-if="comment.commentList.total > 0" class="reply-pagination" @change="paginationCommentChange"
|
||||
<APagination v-if="comment.commentList.total > 5" class="reply-pagination" @change="paginationCommentChange"
|
||||
:current="Number(router.currentRoute.value.query.page) || comment.commentList.current"
|
||||
:page-size="comment.commentList.size" :total="comment.commentList.total"
|
||||
:default-page-size="comment.commentList.size"
|
||||
@@ -152,6 +160,8 @@ import {useRouter} from "vue-router";
|
||||
import ReplyInput from "@/components/CommentReply/src/ReplyInput/ReplyInput.vue";
|
||||
import ReplyList from "@/components/CommentReply/src/ReplyList/ReplyList.vue";
|
||||
import MessageReport from "@/components/CommentReply/src/MessageReport/MessageReport.vue";
|
||||
import {Comment} from "@/types/comment";
|
||||
import Badge from "@/components/MyUI/Badge/Badge.vue";
|
||||
|
||||
|
||||
const {t} = useI18n();
|
||||
@@ -171,7 +181,7 @@ async function getCommentList(page: number = 1, size: number = 5, hot: boolean =
|
||||
topic_id: topicId.value,
|
||||
page: page,
|
||||
size: size,
|
||||
is_hot: router.currentRoute.value.query.type === "hot" || hot,
|
||||
is_hot: router.currentRoute.value.query.type === "hot" ? hot : false,
|
||||
};
|
||||
await comment.getCommentList(params);
|
||||
}
|
||||
@@ -284,28 +294,38 @@ async function paginationCommentChange(page: number, pageSize: number) {
|
||||
* 热门评论
|
||||
*/
|
||||
async function getHotCommentList() {
|
||||
await getCommentList(1, 5, true);
|
||||
await router.push({
|
||||
path: "/main",
|
||||
query: {
|
||||
type: "hot",
|
||||
page: router.currentRoute.value.query.page,
|
||||
}
|
||||
comment.commentList = {} as Comment;
|
||||
comment.commentLoading = true;
|
||||
getCommentList(1, 5, true).then(() => {
|
||||
router.push({
|
||||
path: "/main",
|
||||
query: {
|
||||
type: "hot",
|
||||
page: router.currentRoute.value.query.page,
|
||||
}
|
||||
});
|
||||
comment.commentLoading = false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 最新评论
|
||||
*/
|
||||
async function getLatestCommentList() {
|
||||
await getCommentList(1, 5, false);
|
||||
await router.push({
|
||||
path: "/main",
|
||||
query: {
|
||||
type: "latest",
|
||||
page: router.currentRoute.value.query.page,
|
||||
}
|
||||
comment.commentList = {} as Comment;
|
||||
comment.commentLoading = true;
|
||||
getCommentList(1, 5, false).then(() => {
|
||||
router.push({
|
||||
path: "/main",
|
||||
query: {
|
||||
type: "latest",
|
||||
page: router.currentRoute.value.query.page,
|
||||
}
|
||||
});
|
||||
comment.commentLoading = false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
@@ -7,7 +7,21 @@
|
||||
}
|
||||
|
||||
.reply-list {
|
||||
margin-top: 30px;
|
||||
margin-top: 10px;
|
||||
border: 1px dashed #e9e9e9;
|
||||
border-radius: 10px;
|
||||
background-color: var(--comment-list-background-color);
|
||||
padding: 10px;
|
||||
min-width: 650px;
|
||||
|
||||
.reply-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.reply-avatar {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.reply-avatar-img {
|
||||
cursor: pointer;
|
||||
@@ -27,6 +41,9 @@
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.reply-name:hover {
|
||||
color: rgba(15, 15, 16, 0.68);
|
||||
}
|
||||
|
||||
.reply-tag {
|
||||
font-size: 10px;
|
||||
@@ -35,6 +52,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reply-level-icon {
|
||||
width: 40px;
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reply-ip {
|
||||
font-size: 12px;
|
||||
color: var(--comment-text-color);
|
||||
@@ -46,6 +69,7 @@
|
||||
}
|
||||
|
||||
.reply-card {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
width: 600px;
|
||||
//margin-top: 5px;
|
||||
.reply-text {
|
||||
|
@@ -66,7 +66,7 @@ import useStore from "@/store";
|
||||
const {t} = useI18n();
|
||||
const comment = useStore().comment;
|
||||
const radioStyle = reactive({
|
||||
color: 'rgba(15,15,16,0.66)',
|
||||
color: 'var(--comment-report-text-color)',
|
||||
fontSize: '13px',
|
||||
fontWeight: 'bold'
|
||||
});
|
||||
|
@@ -5,13 +5,13 @@
|
||||
|
||||
.message-report-title {
|
||||
font-size: 13px;
|
||||
color: rgba(15, 15, 16, 0.58);
|
||||
color: var(--comment-report-text-color);
|
||||
font-weight: bolder;
|
||||
}
|
||||
.message-report-content {
|
||||
border: 1px dashed #e9e9e9;
|
||||
border-radius: 10px;
|
||||
background-color: #fafafa;
|
||||
background-color: var(--comment-list-background-color);
|
||||
min-height: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
@@ -4,8 +4,8 @@
|
||||
<AFlex :vertical="true" v-if="comment.replyList.comments">
|
||||
<AFlex :vertical="false" style="margin-top: 5px" v-for="(child, index) in comment.replyList.comments"
|
||||
:key="index">
|
||||
<AFlex :vertical="true" class="reply-item-child-avatar">
|
||||
<AAvatar :size="40" shape="circle" :src="child.avatar"/>
|
||||
<AFlex :vertical="true" >
|
||||
<AAvatar :size="40" shape="circle" class="reply-item-child-avatar" :src="child.avatar"/>
|
||||
</AFlex>
|
||||
<AFlex :vertical="true" class="reply-item-child-content">
|
||||
<AFlex :vertical="true">
|
||||
@@ -96,7 +96,7 @@
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
<APagination v-if="comment.replyList.total > 0" class="reply-pagination-child" size="small"
|
||||
<APagination v-if="comment.replyList.total > 5" class="reply-pagination-child" size="small"
|
||||
:total="comment.replyList.total"
|
||||
:current="comment.replyList.current" :page-size="comment.replyList.size"
|
||||
:default-page-size="comment.replyList.size"
|
||||
|
@@ -3,6 +3,7 @@
|
||||
border-radius: 10px;
|
||||
background-color: var(--comment-child-background-color);
|
||||
padding: 10px;
|
||||
border-left: var(--comment-child-box-border-color) 2px solid;
|
||||
|
||||
.reply-pagination-child {
|
||||
display: flex;
|
||||
@@ -10,6 +11,10 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.reply-item-child-avatar {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.reply-item-child-content {
|
||||
margin-left: 10px;
|
||||
|
||||
@@ -29,6 +34,10 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reply-name-child:hover {
|
||||
color: rgba(15, 15, 16, 0.68);
|
||||
}
|
||||
|
||||
.reply-tag-child {
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
@@ -47,6 +56,7 @@
|
||||
}
|
||||
|
||||
.reply-card-child {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
width: 530px;
|
||||
|
||||
.reply-action-item-child {
|
||||
|
394
src/components/MyUI/Badge/Badge.vue
Normal file
394
src/components/MyUI/Badge/Badge.vue
Normal file
@@ -0,0 +1,394 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { useSlotsExist } from '../utils/index.ts';
|
||||
enum PresetColor {
|
||||
pink = 'pink',
|
||||
red = 'red',
|
||||
yellow = 'yellow',
|
||||
orange = 'orange',
|
||||
cyan = 'cyan',
|
||||
green = 'green',
|
||||
blue = 'blue',
|
||||
purple = 'purple',
|
||||
geekblue = 'geekblue',
|
||||
magenta = 'magenta',
|
||||
volcano = 'volcano',
|
||||
gold = 'gold',
|
||||
lime = 'lime'
|
||||
}
|
||||
enum Status {
|
||||
success = 'success',
|
||||
processing = 'processing',
|
||||
default = 'default',
|
||||
error = 'error',
|
||||
warning = 'warning'
|
||||
}
|
||||
interface Props {
|
||||
color?: PresetColor | string // 自定义小圆点的颜色,优先级高于 status
|
||||
value?: number | string // 展示的数字或文字,为数字时大于 max 显示为 max+,为 0 时隐藏 number | string | slot
|
||||
max?: number // 展示封顶的数字值
|
||||
showZero?: boolean // 当数值为 0 时,是否展示 Badge
|
||||
dot?: boolean // 不展示数字,只有一个小红点
|
||||
offset?: [number | string, number | string] // 设置状态点的位置偏移,距默认位置左侧、上方的偏移量 [x, y]: [水平偏移, 垂直偏移]
|
||||
status?: Status // 设置 Badge 为状态点
|
||||
text?: string // 在设置了 status 或 color 的前提下有效,设置状态点的文本 string | slot
|
||||
valueStyle?: CSSProperties // 设置徽标的样式
|
||||
zIndex?: number // 设置徽标的 z-index
|
||||
title?: string // 设置鼠标放在状态点上时显示的文字
|
||||
ripple?: boolean // 是否开启涟漪动画效果
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: undefined,
|
||||
value: undefined,
|
||||
max: 99,
|
||||
showZero: false,
|
||||
dot: false,
|
||||
offset: undefined,
|
||||
status: undefined,
|
||||
text: undefined,
|
||||
valueStyle: () => ({}),
|
||||
zIndex: 9,
|
||||
title: undefined,
|
||||
ripple: true
|
||||
});
|
||||
const slotsExist = useSlotsExist(['default', 'value']);
|
||||
const customStyle = computed(() => {
|
||||
if (props.color && !Object.keys(PresetColor).includes(props.color)) {
|
||||
if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0)) {
|
||||
return {
|
||||
backgroundColor: props.color
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
color: props.color,
|
||||
backgroundColor: props.color
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
});
|
||||
const presetClass = computed(() => {
|
||||
if (props.color) {
|
||||
if (Object.keys(PresetColor).includes(props.color)) {
|
||||
if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0)) {
|
||||
return `color-${props.color} white`;
|
||||
} else {
|
||||
return `color-${props.color}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props.status) {
|
||||
if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0)) {
|
||||
return `status-${props.status} white`;
|
||||
} else {
|
||||
return `status-${props.status}`;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
});
|
||||
const showContent = computed(() => {
|
||||
if (props.value !== undefined || props.dot || (!props.color && !props.status)) {
|
||||
return slotsExist.default;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const showValue = computed(() => {
|
||||
if (!props.color && !props.status) {
|
||||
return slotsExist.value;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const showBadge = computed(() => {
|
||||
if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0) || props.dot) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const showDot = computed(() => {
|
||||
return props.value === undefined || (props.value === 0 && !props.showZero) || props.dot;
|
||||
});
|
||||
const dotOffestStyle = computed(() => {
|
||||
if (props.offset?.length) {
|
||||
return {
|
||||
right: isNumber(props.offset[0]) ? -props.offset[0] + 'px' : handleOffset(props.offset[0] as string),
|
||||
marginTop: isNumber(props.offset[1]) ? props.offset[1] + 'px' : props.offset[1]
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
function isNumber(value: number | string): boolean {
|
||||
return typeof value === 'number';
|
||||
}
|
||||
function handleOffset(value: string): string {
|
||||
if (value.includes('-')) {
|
||||
return value.replace('-', '');
|
||||
} else {
|
||||
return `-${value}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="m-badge"
|
||||
:class="{ 'badge-status-color': value === undefined && (color || status) }"
|
||||
:style="[`--z-index: ${zIndex}`, value === undefined && !dot ? dotOffestStyle : null]"
|
||||
>
|
||||
<template v-if="value === undefined && !dot && (color || status)">
|
||||
<span class="status-dot" :class="[presetClass, { 'dot-ripple': ripple }]" :style="customStyle"></span>
|
||||
<span class="status-text">
|
||||
<slot>{{ text }}</slot>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="showContent">
|
||||
<slot></slot>
|
||||
</template>
|
||||
<span v-if="showValue" class="m-value" :class="{ 'only-number': !showContent }">
|
||||
<slot name="value"></slot>
|
||||
</span>
|
||||
<Transition name="zoom" v-else>
|
||||
<div
|
||||
v-if="showBadge"
|
||||
class="m-badge-value"
|
||||
:class="[
|
||||
{
|
||||
'small-num': typeof value === 'number' && value < 10,
|
||||
'only-number': !showContent,
|
||||
'only-dot': showDot
|
||||
},
|
||||
presetClass
|
||||
]"
|
||||
:style="[customStyle, dotOffestStyle, valueStyle]"
|
||||
:title="title || (value !== undefined ? String(value) : '')"
|
||||
>
|
||||
<span v-if="!dot" class="m-number" style="transition: none 0s ease 0s">
|
||||
<span class="u-number">{{ typeof value === 'number' && value > max ? max + '+' : value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.zoom-enter-active {
|
||||
animation: zoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46);
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
.zoom-leave-active {
|
||||
animation: zoomBadgeOut 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46);
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
@keyframes zoomBadgeIn {
|
||||
0% {
|
||||
transform: scale(0) translate(50%, -50%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) translate(50%, -50%);
|
||||
}
|
||||
}
|
||||
@keyframes zoomBadgeOut {
|
||||
0% {
|
||||
transform: scale(1) translate(50%, -50%);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0) translate(50%, -50%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.m-badge {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
line-height: 1;
|
||||
.status-dot {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot-ripple {
|
||||
&::after {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: inherit;
|
||||
border-radius: 50%;
|
||||
animation-name: dotRipple;
|
||||
animation-duration: 1.2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
content: '';
|
||||
}
|
||||
@keyframes dotRipple {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: scale(2.4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.status-text {
|
||||
margin-left: 8px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
font-size: 14px;
|
||||
}
|
||||
.m-value {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: var(--z-index);
|
||||
right: 0;
|
||||
transform: translate(50%, -50%);
|
||||
transform-origin: 100% 0%;
|
||||
}
|
||||
.m-badge-value {
|
||||
.m-value();
|
||||
overflow: hidden;
|
||||
padding: 0 8px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
color: #ffffff;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
background: #ff4d4f;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 0 1px #ffffff;
|
||||
transition: background 0.2s;
|
||||
.m-number {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
transition: all 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46);
|
||||
transform-style: preserve-3d;
|
||||
-webkit-transform-style: preserve-3d; // 设置元素的子元素是位于 3D 空间中还是平面中 flat | preserve-3d
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden; // 当元素背面朝向观察者时是否可见 hidden | visible
|
||||
.u-number {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
transform-style: preserve-3d;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
.small-num {
|
||||
padding: 0;
|
||||
}
|
||||
.only-number {
|
||||
position: relative;
|
||||
top: auto;
|
||||
display: block;
|
||||
transform-origin: 50% 50%;
|
||||
transform: none;
|
||||
}
|
||||
.only-dot {
|
||||
width: 6px;
|
||||
min-width: 6px;
|
||||
height: 6px;
|
||||
background: #ff4d4f;
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 0 0 1px #ffffff;
|
||||
padding: 0;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.status-success {
|
||||
color: #52c41a;
|
||||
background-color: #52c41a;
|
||||
}
|
||||
.status-error {
|
||||
color: #ff4d4f;
|
||||
background-color: #ff4d4f;
|
||||
}
|
||||
.status-default {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.status-processing {
|
||||
color: #1890ff;
|
||||
background-color: #1890ff;
|
||||
}
|
||||
.status-warning {
|
||||
color: #faad14;
|
||||
background-color: #faad14;
|
||||
}
|
||||
.color-pink {
|
||||
color: #eb2f96;
|
||||
background-color: #eb2f96;
|
||||
}
|
||||
.color-red {
|
||||
color: #f5222d;
|
||||
background-color: #f5222d;
|
||||
}
|
||||
.color-yellow {
|
||||
color: #fadb14;
|
||||
background-color: #fadb14;
|
||||
}
|
||||
.color-orange {
|
||||
color: #fa8c16;
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
.color-cyan {
|
||||
color: #13c2c2;
|
||||
background-color: #13c2c2;
|
||||
}
|
||||
.color-green {
|
||||
color: #52c41a;
|
||||
background-color: #52c41a;
|
||||
}
|
||||
.color-blue {
|
||||
color: #1890ff;
|
||||
background-color: #1890ff;
|
||||
}
|
||||
.color-purple {
|
||||
color: #722ed1;
|
||||
background-color: #722ed1;
|
||||
}
|
||||
.color-geekblue {
|
||||
color: #2f54eb;
|
||||
background-color: #2f54eb;
|
||||
}
|
||||
.color-magenta {
|
||||
color: #eb2f96;
|
||||
background-color: #eb2f96;
|
||||
}
|
||||
.color-volcano {
|
||||
color: #fa541c;
|
||||
background-color: #fa541c;
|
||||
}
|
||||
.color-gold {
|
||||
color: #faad14;
|
||||
background-color: #faad14;
|
||||
}
|
||||
.color-lime {
|
||||
color: #a0d911;
|
||||
background-color: #a0d911;
|
||||
}
|
||||
.white {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.badge-status-color {
|
||||
line-height: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
</style>
|
50
src/components/MyUI/utils/index.ts
Normal file
50
src/components/MyUI/utils/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 组合式函数
|
||||
* 监听给定名称或名称数组的插槽是否存在,支持监听单个插槽或一组插槽的存在
|
||||
*
|
||||
* @param slotsName - 插槽的名称或名称数组,默认为 'default'
|
||||
* @returns 如果是单个插槽名称,则返回一个计算属性,表示该插槽是否存在
|
||||
* 如果是插槽名称数组,则返回一个 reactive 对象,其中的每个属性对应该插槽是否存在
|
||||
*/
|
||||
import { useSlots, reactive, computed } from 'vue';
|
||||
export function useSlotsExist(slotsName: string | string[] = 'default') {
|
||||
const slots = useSlots(); // 获取当前组件的所有插槽
|
||||
// 检查特定名称的插槽是否存在且不为空
|
||||
const checkSlotsExist = (slotsName: string): boolean => {
|
||||
const slotsContent = slots[slotsName]?.();
|
||||
const checkExist = (slotContent: any) => {
|
||||
if (typeof slotContent.children === 'string') {
|
||||
// 排除 v-if="false" 的插槽内容
|
||||
if (slotContent.children === 'v-if') {
|
||||
return false;
|
||||
}
|
||||
return slotContent.children.trim() !== '';
|
||||
} else {
|
||||
if (slotContent.children === null) {
|
||||
if (slotContent.type === 'img' || typeof slotContent.type !== 'string') {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return Boolean(slotContent.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (slotsContent && slotsContent?.length) {
|
||||
const result = slotsContent.some((slotContent) => {
|
||||
return checkExist(slotContent);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (Array.isArray(slotsName)) {
|
||||
const slotsExist = reactive<any>({});
|
||||
slotsName.forEach((item) => {
|
||||
const exist = computed(() => checkSlotsExist(item));
|
||||
slotsExist[item] = exist; // 将一个 ref 赋值给一个 reactive 属性时,该 ref 会自动解包
|
||||
});
|
||||
return slotsExist;
|
||||
} else {
|
||||
return computed(() => checkSlotsExist(slotsName));
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@
|
||||
transition: background-color 0.3s;
|
||||
z-index: 3;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 20px;
|
||||
//border-radius: 20px;
|
||||
|
||||
.header-logo-container {
|
||||
min-width: 280px;
|
||||
|
@@ -4,7 +4,6 @@ import {Comment} from "@/types/comment";
|
||||
import {cancelCommentLikeApi, commentLikeApi, commentListApi, replyListApi} from "@/api/comment";
|
||||
import {message} from "ant-design-vue";
|
||||
import {getSlideCaptchaDataApi} from "@/api/captcha";
|
||||
import imageCompression from "browser-image-compression";
|
||||
import QQ_EMOJI from "@/constant/qq_emoji.ts";
|
||||
import {initNSFWJs, predictNSFW} from "@/utils/nsfw/nsfw.ts";
|
||||
import {NSFWJS} from "nsfwjs";
|
||||
@@ -180,17 +179,15 @@ export const useCommentStore = defineStore(
|
||||
async function beforeUpload(file: any) {
|
||||
uploadLoading.value = true;
|
||||
// 压缩图片配置
|
||||
const options = {
|
||||
maxSizeMB: 0.4,
|
||||
maxWidthOrHeight: 750,
|
||||
maxIteration: 2
|
||||
};
|
||||
|
||||
|
||||
// const options = {
|
||||
// maxSizeMB: 0.4,
|
||||
// maxWidthOrHeight: 750,
|
||||
// maxIteration: 2
|
||||
// };
|
||||
if (!window.FileReader) return false; // 判断是否支持FileReader
|
||||
const compressedFile = await imageCompression(file, options);
|
||||
// const compressedFile = await imageCompression(file, options);
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(compressedFile); // 文件转换
|
||||
reader.readAsDataURL(file); // 文件转换
|
||||
reader.onloadend = async function () {
|
||||
if (fileList.value.length < 3) {
|
||||
const img: HTMLImageElement = document.createElement('img');
|
||||
|
Reference in New Issue
Block a user