This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
<script setup lang="ts">
import type { PropType } from 'vue'
import { defineComponent, onMounted, ref } from 'vue'
import { PREFIX } from '../../_constants'
import { useTranslate } from '../../../locale'
import NutIcon from '../../icon/icon.vue'
const props = defineProps({
type: {
type: String,
default: 'base', // simplebasecomplex
},
info: {
type: Object,
default: () => ({}),
},
operation: {
type: Array as PropType<string[]>,
default: () => ['replay', 'like', 'more'],
},
})
const emit = defineEmits(['clickOperate', 'handleClick'])
const showPopver = ref(false)
const mergeOp = ref([])
onMounted(() => {
const deOp = ['replay', 'like', 'more']
if (props.operation) {
props.operation.forEach((name: string) => {
if (deOp.includes(name))
(mergeOp.value as any).push(name)
})
}
})
function operate(type: string) {
if (type === 'more')
showPopver.value = !showPopver.value
emit('clickOperate', type)
}
function handleClick() {
emit('handleClick')
}
</script>
<script lang="ts">
const componentName = `${PREFIX}-comment-bottom`
const { translate } = useTranslate(componentName)
export default defineComponent ({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view class="nut-comment-bottom">
<view class="nut-comment-bottom__lable" @click="handleClick">
<span v-if="type !== 'complex'" style="display: inline;white-space:none;">
{{ info.size }}
</span>
</view>
<view class="nut-comment-bottom__cpx">
<template v-for="(name, i) in mergeOp" :key="i">
<view class="nut-comment-bottom__cpx-item" :class="[`nut-comment-bottom__cpx-item--${name}`]" @click="operate(name)">
<template v-if="name !== 'more'">
<text>{{ info[name] }}</text>
<NutIcon v-if="name === 'like'" name="fabulous" />
<NutIcon v-else name="comment" />
</template>
<template v-if="name === 'more'">
<NutIcon name="more-x" />
<view v-if="showPopver" class="nut-comment-bottom__cpx-item-popover" @click="operate('popover')">
{{
translate('complaintsText')
}}
</view>
</template>
</view>
</template>
</view>
</view>
</template>
<style lang="scss">
.nut-theme-dark {
.nut-comment {
&-bottom {
&__cpx {
color: $dark-color;
&-item {
text {
color: $dark-color;
}
}
}
}
}
}
.nut-comment {
&-bottom {
display: flex;
justify-content: space-between;
margin-right: 5px;
color: $comment-bottom-label-color;
&__lable {
flex: 1;
margin-right: 10px;
// stylelint-disable-next-line at-rule-no-unknown
@include oneline-ellipsis;
}
&__cpx {
display: flex;
align-items: center;
justify-content: flex-end;
color: $black;
&-item {
position: relative;
display: flex;
align-items: center;
margin-right: 18px;
text {
margin-right: 5px;
color: $black;
}
&:last-child {
margin-right: 0;
}
&-popover {
position: absolute;
top: 35px;
right: 18px;
width: max-content;
padding: 10px;
background: $white;
border-radius: 5px 0 5px 5px;
box-shadow: 0 0 6px $disable-color;
&::after {
position: absolute;
top: -20px;
right: 0;
width: 0;
height: 0;
content: "";
border-top: 10px solid transparent;
border-right: 0 solid transparent;
border-bottom: 10px solid $white;
border-left: 14px solid transparent;
}
&::before {
position: absolute;
top: -22px;
right: -1px;
width: 0;
height: 0;
content: "";
border-top: 10px solid transparent;
border-right: 0 solid transparent;
border-bottom: 10px solid rgb(114 113 113 / 10%);
border-left: 14px solid transparent;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,242 @@
<script setup lang="ts">
import { defineComponent } from 'vue'
import { PREFIX } from '../../_constants'
import NutRate from '../../rate/rate.vue'
defineProps({
type: {
type: String,
default: 'default', // defaultcomplex
},
info: {
type: Object,
default: () => ({}),
},
})
const emit = defineEmits(['handleClick'])
function handleClick() {
emit('handleClick')
}
</script>
<script lang="ts">
const componentName = `${PREFIX}-comment-header`
export default defineComponent ({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view>
<view v-if="info" class="nut-comment-header" @click="handleClick">
<view class="nut-comment-header__user">
<view class="nut-comment-header__user-avter">
<image v-if="info.avatar" :src="info.avatar" />
</view>
<view v-if="type === 'default'" :class="[`nut-comment-header__user-${type}`]">
<view :class="[`nut-comment-header__user-${type}-name`]">
<text>{{ info.nickName }}</text>
<slot name="labels" />
</view>
<view class="nut-comment-header__user-score">
<!-- eslint-disable vue/no-mutating-props -->
<NutRate
v-model="info.score"
size="12"
spacing="5"
readonly
@change="handleClick"
/>
</view>
</view>
<view v-else :class="[`nut-comment-header__user-${type}`]">
<text :class="[`nut-comment-header__user-${type}-name`]">
{{ info.nickName }}
</text>
<slot name="labels" />
</view>
</view>
<view v-if="info.time" class="nut-comment-header__time">
{{ info.time }}
</view>
</view>
<view v-if="type === 'complex'" :class="[`nut-comment-header__${type}-score`]">
<NutRate
v-model="info.score"
size="12"
spacing="3"
readonly
/>
<i :class="[`nut-comment-header__${type}-score-i`]" />
<view :class="[`nut-comment-header__${type}-score-size`]">
{{ info.size }}
</view>
</view>
</view>
</template>
<style lang="scss">
.nut-theme-dark {
.nut-comment {
&-header {
&__user {
&-name {
color: $dark-color;
}
&-default {
&-name {
color: $dark-color;
}
}
}
}
}
}
.nut-comment {
&-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
&__user {
display: flex;
flex: 1;
align-items: center;
&-avter {
width: 20px;
height: 20px;
margin-right: 10px;
overflow: hidden;
border-radius: 50%;
image {
width: 20px;
height: 20px;
}
}
&-name {
/* stylelint-disable-next-line at-rule-no-unknown */
@include oneline-ellipsis;
width: auto;
max-width: 80px;
margin-right: 5px;
font-size: 12px;
color: $comment-header-user-name-color;
}
&-default {
flex: 1;
&-name {
display: flex;
align-items: center;
margin-bottom: 3px;
/* stylelint-disable-next-line at-rule-no-unknown */
@include oneline-ellipsis;
font-size: 12px;
color: $comment-header-user-name-color;
> text {
margin-right: 8px;
}
}
}
&-complex {
display: flex;
align-items: center;
color: $comment-header-user-name-color;
&-name {
max-width: 80px;
margin-right: 10px;
/* stylelint-disable-next-line at-rule-no-unknown */
@include text-ellipsis;
}
image {
max-width: 50px;
height: 16px;
}
}
&-score {
.nut-rate-item {
display: block !important;
line-height: 10px;
.nut-icon {
line-height: 10px;
}
}
}
}
&__time {
width: 100px;
font-size: 12px;
color: $comment-header-time-color;
text-align: right;
}
&__complex-score {
display: flex;
align-items: center;
margin-bottom: 10px;
.nut-rate-item {
display: block !important;
line-height: 12px;
.nut-icon {
line-height: 12px;
}
}
&-i {
display: inline-block;
width: 1px;
height: 6px;
margin: 0 8px 0 6px;
font-style: inherit;
background: $text-color;
opacity: 0.4;
}
&-size {
/* stylelint-disable-next-line at-rule-no-unknown */
@include oneline-ellipsis;
}
}
&__labels--item {
display: inline-block;
height: 16px;
margin-right: 4px;
&:last-child {
margin-right: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,228 @@
<script setup lang="ts">
import type { PropType } from 'vue'
import { defineComponent, onMounted, ref, watch } from 'vue'
import { PREFIX } from '../../_constants'
import NutIcon from '../../icon/icon.vue'
const props = defineProps({
type: {
type: String,
default: 'one', // one multi
},
videos: {
type: Array as PropType<VideosType[]>,
default: () => [],
},
images: {
type: Array as PropType<ImagesType[]>,
default: () => [],
},
})
const emit = defineEmits(['click', 'clickImages'])
interface VideosType {
id: number | string
mainUrl: string
videoUrl: string
}
interface ImagesType {
smallImgUrl: string
bigImgUrl: string
imgUrl: string
}
const totalImages = ref<(VideosType | ImagesType)[]>([])
watch(
() => [props.videos, props.images],
(value) => {
if (value[0].length > 0) {
value[0].forEach((el: any) => {
el.type = 'video'
})
}
totalImages.value = (value[0] as any).concat(value[1])
},
{ deep: true },
)
onMounted(() => {
if (props.videos.length > 0) {
props.videos.forEach((el: any) => {
el.type = 'video'
})
}
totalImages.value = (props.videos as any).concat(props.images)
})
function showImages(type: string, index: string | number) {
const { videos, images } = props
const i = type === 'img' ? (index as number) - videos.length : index
emit('clickImages', {
type,
index: i,
value: type === 'img' ? images[i as number] : videos[i as number],
})
}
</script>
<script lang="ts">
const componentName = `${PREFIX}-comment-images`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view :class="`nut-comment-images nut-comment-images--${type}`">
<!-- videos -->
<view
v-for="(itV, index) in videos"
:key="itV.id"
class="nut-comment-images__item nut-comment-images__item--video"
@click="showImages('video', index)"
>
<image :src="itV.mainUrl" />
<view class="nut-comment-images__play" />
</view>
<!-- images -->
<template v-for="(itI, index) in images" :key="index">
<view
v-if="(type === 'multi' && videos.length + index < 9) || type !== 'multi'"
class="nut-comment-images__item nut-comment-images__item--imgbox"
@click="showImages('img', index + videos.length)"
>
<image :src="itI.smallImgUrl ? itI.smallImgUrl : itI.imgUrl" />
<view
v-if="type === 'multi' && totalImages.length > 9 && videos.length + index > 7"
class="nut-comment-images__mask"
>
<text> {{ totalImages.length }} </text>
<NutIcon name="right" size="12px" />
</view>
</view>
</template>
</view>
</template>
<style lang="scss">
.nut-comment {
&-images {
display: flex;
margin: 10px 0 12px;
overflow: auto hidden;
&__item {
position: relative;
flex-shrink: 0;
width: 80px;
height: 80px;
margin-right: 5px;
overflow: hidden;
border-radius: 6px;
image {
width: 80px;
height: 80px;
}
// &--imgbox {
// // background: #f00;
// }
&--video {
// stylelint-disable-next-line rule-empty-line-before
image {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
// height: auto;
}
}
}
&__mask {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 90px;
font-size: 12px;
line-height: 90px;
color: rgb(255 255 255 / 100%);
background: rgb(0 0 0 / 50%);
}
}
&-images--multi {
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
margin: 10px auto 15px;
overflow: hidden;
.nut-comment-images__item {
width: calc(34% - 8px);
height: 90px;
margin: 8px 8px 0 0;
image {
width: 100%;
height: 100%;
}
.svg-demo {
width: 40px;
height: 40px;
}
&:nth-child(3n) {
margin-right: 0;
}
}
&::after {
display: block;
width: 105px;
content: "";
}
}
&-images__play {
position: absolute;
top: 50%;
left: 50%;
width: 40px;
height: 40px;
background: rgb(0 0 0 / 50%);
border-radius: 50%;
transform: translate(-50%);
transform: translate(-50%, -50%);
&::after {
position: absolute;
top: 11px;
left: 15px;
display: block;
content: "";
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
border-left: 15px solid #fff;
}
}
}
</style>