|
@@ -1,7 +1,12 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import type { SystemCwModel } from '@/model/care.model';
|
|
import type { SystemCwModel } from '@/model/care.model';
|
|
|
-import { computed } from 'vue';
|
|
|
|
|
|
|
+import { PlayCircleOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
+import { computed, h } from 'vue';
|
|
|
import { notification } from 'ant-design-vue';
|
|
import { notification } from 'ant-design-vue';
|
|
|
|
|
+import VxeUI from 'vxe-table';
|
|
|
|
|
+import ReviewMediaPreview from '@/service/ReviewMediaPreview.vue';
|
|
|
|
|
+import type { MediaItem } from '@/service/ReviewMediaPreview.vue';
|
|
|
|
|
+
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
data: SystemCwModel['items'][number];
|
|
data: SystemCwModel['items'][number];
|
|
|
}>();
|
|
}>();
|
|
@@ -62,6 +67,43 @@ const sellTypeText: Record<string, string> = {
|
|
|
'2': '线下服务',
|
|
'2': '线下服务',
|
|
|
'3': '线上权益',
|
|
'3': '线上权益',
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+// 用户评价假数据(淘宝式:每条评价有多张图/多个视频,先展示缩略图排列,点击可左右滑动预览)
|
|
|
|
|
+const mockReviewList = [
|
|
|
|
|
+ {
|
|
|
|
|
+ criterion: '描述相符',
|
|
|
|
|
+ score: 5,
|
|
|
|
|
+ content: '描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符描述相符 描述相符描述相符描述相符描述相符描述相符描述相符 描述相符',
|
|
|
|
|
+ //图片/视频列表
|
|
|
|
|
+ mediaList: [
|
|
|
|
|
+ { type: 'image', url: 'https://picsum.photos/id/1/400/400' },
|
|
|
|
|
+ { type: 'image', url: 'https://picsum.photos/id/10/400/400' },
|
|
|
|
|
+ { type: 'video', url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4' },
|
|
|
|
|
+ { type: 'image', url: 'https://picsum.photos/id/20/400/400' },
|
|
|
|
|
+ ] as MediaItem[],
|
|
|
|
|
+ },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const REVIEW_PREVIEW_MODAL_ID = 'review-media-preview-modal';
|
|
|
|
|
+//预览图片/视频
|
|
|
|
|
+function openPreview(list: MediaItem[], index: number) {
|
|
|
|
|
+ if (!list?.length) return;
|
|
|
|
|
+ VxeUI.modal.open({
|
|
|
|
|
+ id: REVIEW_PREVIEW_MODAL_ID,
|
|
|
|
|
+ title: '预览',
|
|
|
|
|
+ width: 720,
|
|
|
|
|
+ escClosable: true,
|
|
|
|
|
+ destroyOnClose: true,
|
|
|
|
|
+ slots: {
|
|
|
|
|
+ default() {
|
|
|
|
|
+ return h(ReviewMediaPreview, {
|
|
|
|
|
+ mediaList: list,
|
|
|
|
|
+ initialIndex: index,
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
@@ -213,7 +255,7 @@ const sellTypeText: Record<string, string> = {
|
|
|
<vxe-column type="seq" title="序号" width="60" align="center" />
|
|
<vxe-column type="seq" title="序号" width="60" align="center" />
|
|
|
<vxe-column field="arrangeDate" title="服务日期" align="center" />
|
|
<vxe-column field="arrangeDate" title="服务日期" align="center" />
|
|
|
<vxe-column field="arrangePeriod" title="服务时间段" align="center" />
|
|
<vxe-column field="arrangePeriod" title="服务时间段" align="center" />
|
|
|
- <vxe-column field="operateTime" title="服务状态" align="center">
|
|
|
|
|
|
|
+ <vxe-column field="verifyStatus" title="服务状态" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
{{ row.operateTime ? '已核销' : '已预约' }}
|
|
{{ row.operateTime ? '已核销' : '已预约' }}
|
|
|
</template>
|
|
</template>
|
|
@@ -239,6 +281,50 @@ const sellTypeText: Record<string, string> = {
|
|
|
<vxe-column field="feedback" title="治疗备注" align="center" />
|
|
<vxe-column field="feedback" title="治疗备注" align="center" />
|
|
|
</vxe-table>
|
|
</vxe-table>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <!-- 用户评价 -->
|
|
|
|
|
+ <div class="info-section">
|
|
|
|
|
+ <div class="info-title-row">
|
|
|
|
|
+ <h3 class="info-title">用户评价</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="info-content-wrapper">
|
|
|
|
|
+ <div v-for="(item, index) in mockReviewList" :key="index" class="review-item">
|
|
|
|
|
+ <div class="review-top-row">
|
|
|
|
|
+ <div class="review-rating-row">
|
|
|
|
|
+ <span class="review-criterion">{{ item.criterion }}</span>
|
|
|
|
|
+ <a-rate :value="item.score" disabled allow-half class="review-stars" />
|
|
|
|
|
+ <span class="review-score">{{ item.score }}分</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="review-content-block">
|
|
|
|
|
+ <span class="review-content-label">评价内容:</span>
|
|
|
|
|
+ <p class="review-content-text">{{ item.content }}</p>
|
|
|
|
|
+ <div v-if="item.mediaList?.length" class="review-media-row">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(media, i) in item.mediaList"
|
|
|
|
|
+ :key="i"
|
|
|
|
|
+ class="review-media-thumb"
|
|
|
|
|
+ :class="{ 'is-video': media.type === 'video' }"
|
|
|
|
|
+ role="button"
|
|
|
|
|
+ tabindex="0"
|
|
|
|
|
+ @click.stop.prevent="openPreview(item.mediaList, i)"
|
|
|
|
|
+ @keydown.enter.space.prevent="openPreview(item.mediaList, i)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <img
|
|
|
|
|
+ v-if="media.type === 'image'"
|
|
|
|
|
+ :src="media.url"
|
|
|
|
|
+ class="review-thumb-img"
|
|
|
|
|
+ alt=""
|
|
|
|
|
+ />
|
|
|
|
|
+ <div v-else class="review-thumb-video">
|
|
|
|
|
+ <video :src="media.url" muted preload="metadata" />
|
|
|
|
|
+ <span class="review-thumb-play"><PlayCircleOutlined /></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -279,6 +365,139 @@ const sellTypeText: Record<string, string> = {
|
|
|
color: #333;
|
|
color: #333;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .info-title-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ .info-title {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-item {
|
|
|
|
|
+ padding: 12px 0;
|
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
+
|
|
|
|
|
+ &:last-child {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-top-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 24px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-rating-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-right: 40px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-criterion {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-stars {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.ant-rate-star-full .ant-rate-star-second) {
|
|
|
|
|
+ color: #fadb14;
|
|
|
|
|
+ }
|
|
|
|
|
+ :deep(.ant-rate-star-full .ant-rate-star-first) {
|
|
|
|
|
+ color: #fadb14;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-score {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-content-block {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-content-label {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ margin-right: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-content-text {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ line-height: 22px;
|
|
|
|
|
+ margin: 8px 0 12px;
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+ max-width: 560px;
|
|
|
|
|
+ word-break: break-word;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-media-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-media-thumb {
|
|
|
|
|
+ width: 72px;
|
|
|
|
|
+ height: 72px;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ transition: border-color 0.2s, box-shadow 0.2s;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: #1890ff;
|
|
|
|
|
+ box-shadow: 0 0 0 1px #1890ff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-thumb-img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-thumb-video {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: #2a2a2a;
|
|
|
|
|
+
|
|
|
|
|
+ video {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .review-thumb-play {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.9);
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.info-content-wrapper .info-title {
|
|
.info-content-wrapper .info-title {
|
|
|
margin-top: 20px;
|
|
margin-top: 20px;
|
|
|
margin-bottom: 12px;
|
|
margin-bottom: 12px;
|