| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036 |
- <script setup lang="ts">
- import type { SystemCwModel } from '@/model/care.model';
- import { PlayCircleOutlined } from '@ant-design/icons-vue';
- import { computed, h, onMounted, ref } from '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';
- import seeEvaluate from '@/service/seeEvaluate.vue';
- import { getEvaluateDetailMethod } from '@/request/api/order.api';
- const props = defineProps<{
- data: SystemCwModel['items'][number];
- }>();
- // 服务记录--查看评价
- function openSeeEvaluate(row: any) {
- VxeUI.modal.open({
- title: '用户评价',
- width: 900,
- height: 600,
- escClosable: true,
- destroyOnClose: true,
- slots: {
- default() {
- return h(seeEvaluate, {
- data: row,
- });
- },
- },
- });
- }
- // 复制物流信息
- function handleCopyTracking() {
- const trackingNumber = mockLogisticsData.value.trackingNumber;
- if (trackingNumber) {
- navigator.clipboard.writeText(trackingNumber).then(() => {
- notification.success({ message: '复制成功' });
- }).catch(() => {
- notification.error({ message: '复制失败' });
- });
- }
- }
- // 分账信息数据
- const mockSplitAccountList = computed(() => {
- return (props.data as any).profitSharings;
- });
- // 物流信息数据
- const mockLogisticsData = computed(() => {
- console.log('props.data===', props.data);
- return {
- trackingNumber: `${expressTypeText[props.data.expressType || '']} ${props.data.expressNo || ''}`,
- recipientName: props.data.liaison ? props.data.liaison + ', ' : '',
- recipientPhone: props.data.phone ? props.data.phone + ', ' : '',
- recipientAddress: (props.data.provinceName !== null ? props.data.provinceName + ' ' : '') + (props.data.cityName !== null ? props.data.cityName + ' ' : '') + (props.data.areaName !== null ? props.data.areaName + ' ' : '') + (props.data.detailAddress !== null ? props.data.detailAddress : '')
- };
- });
- const expressTypeText: Record<string, string> = {
- '0': '邮政速递',
- '1': '顺丰速运',
- '2': '京东快递',
- '3': '中通快递',
- '4': '圆通速递',
- '5': '申通快递',
- '6': '韵达快递',
- '7': '极兔速递',
- };
- // 包裹数据
- const mockPackageItems = computed(() => {
- return (props.data as any).sameExpress ?? []
- });
- // 商品状态映射 - 收货状态(实体商品使用)
- const receiptStatusText: Record<string, string> = {
- '0': '待发货',
- '1': '已发货',
- '2': '已收货',
- };
- // 进度状态映射(线下服务和线上权益使用)
- const progressText: Record<string, string> = {
- '0': '进行中',
- '1': '已完成',
- '2': '未开始',
- };
- // 商品类型映射
- const sellTypeText: Record<string, string> = {
- '1': '实体商品',
- '2': '线下服务',
- '3': '线上权益',
- };
- function toScore(value: unknown) {
- const n = Number(value);
- return Number.isFinite(n) ? n : 0;
- }
- function normalizeMediaList(input: unknown): MediaItem[] {
- if (!Array.isArray(input)) return [];
- return input
- .map((it) => {
- if (typeof it === 'string') {
- const url = it;
- const type: MediaItem['type'] = /\.(mp4|webm|ogg)(\?|#|$)/i.test(url) ? 'video' : 'image';
- return { type, url } as MediaItem;
- }
- if (it && typeof it === 'object' && 'url' in it) {
- const anyIt = it as any;
- const url = String(anyIt.url ?? '');
- if (!url) return null;
- const type: MediaItem['type'] =
- anyIt.type === 'video' || anyIt.type === 'image'
- ? anyIt.type
- : /\.(mp4|webm|ogg)(\?|#|$)/i.test(url)
- ? 'video'
- : 'image';
- return { type, url } as MediaItem;
- }
- return null;
- })
- .filter((x): x is MediaItem => Boolean(x));
- }
- // 用户评价(与 seeEvaluate.vue 逻辑一致)
- const scroeType = ref([
- { name: '描述相符', score: 0 },
- ]);
- const evaluateDetail = ref({
- depict: '',
- mediaList: [] as MediaItem[],
- });
- const hasEvaluate = computed(() => {
- const depict = (evaluateDetail.value.depict ?? '').trim();
- const hasMedia = (evaluateDetail.value.mediaList?.length ?? 0) > 0;
- const hasScore = scroeType.value.some((i) => toScore(i.score) > 0);
- return Boolean(depict) || hasMedia || hasScore;
- });
- async function getEvaluateDetail() {
- const id = (props.data as any)?.id;
- if (!id) return;
- const res = await getEvaluateDetailMethod('1', id);
- if (!res) return;
- if (scroeType.value[0]) scroeType.value[0].score = toScore(res.complianceScore);
- evaluateDetail.value.depict = res.depict ?? '';
- evaluateDetail.value.mediaList = normalizeMediaList(res.imageVideos);
- }
- 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,
- });
- },
- },
- });
- }
- onMounted(() => {
- getEvaluateDetail();
- });
- </script>
- <template>
- <div class="service-detail">
- <!-- 商品信息 -->
- <div class="info-section">
- <h3 class="info-title">商品信息</h3>
- <div class="info-content-wrapper">
- <div class="product-info">
- <div class="product-image">
- <!-- 商品图片 -->
- <a-image v-if="data?.conditioningProgramPhoto" :width="80" :height="80" style="border-radius: 5px;"
- :src="data?.conditioningProgramPhoto" class="product-img" />
- </div>
- <div class="product-details">
- <!-- 商品名称 -->
- <div class="product-name">{{ data?.conditioningProgramName }}</div>
- <!-- 一口价计价 -->
- <div class="product-spec" v-if="data?.conditioningProgramDetail?.pricingType === '0'">
- {{ data?.convertDose }} {{ data?.convertUnit || '次' }}
- </div>
- <!-- 按穴位/经络/部位计价 -->
- <div class="product-spec" v-if="data?.conditioningProgramDetail?.pricingType === '1'">
- 1次
- </div>
- <!-- 商品价格 -->
- <div class="product-price" v-if="data?.unitPrice">
- ¥{{ data?.unitPrice }}
- </div>
- </div>
- </div>
- <div class="info-content">
- <div class="info-row">
- <div class="info-item">
- <span class="info-label">商品类型:</span>
- <span class="info-value">{{ sellTypeText[data.sellType] || '-' }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">方案类型:</span>
- <span class="info-value">{{ data.conditioningProgramType || '-' }}</span>
- </div>
- </div>
- <div class="info-row">
- <div class="info-item">
- <span class="info-label">数量:</span>
- <span class="info-value">{{ data?.totalMeasure || '-' }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">商品总价:</span>
- <span class="info-value">¥{{ data?.totalPrice || '-' }}</span>
- </div>
- <div class="info-item" v-if="data?.sellType === '1' ? data?.receiptStatus : data?.progress">
- <span class="info-label">商品状态:</span>
- <span class="info-value">
- {{ data?.sellType === '1'
- ? (data?.receiptStatus ? receiptStatusText[data.receiptStatus] : '-')
- : (data?.progress ? progressText[data.progress] : '-') }}
- </span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 分账信息 -->
- <div class="info-section">
- <h3 class="info-title">分账信息</h3>
- <vxe-table
- class="split-account-table"
- :data="mockSplitAccountList"
- border
- >
- <vxe-column field="profitSharingTime" title="分账时间" align="center" />
- <vxe-column field="conditioningProgramSupplierName" title="供应商" align="center" />
- <vxe-column field="profitSharingStatus" title="分账状态" align="center" >
- <template #default="{ row }">
- {{ row.profitSharingStatus === '1' ? '未分账' : row.profitSharingStatus === '2' ? '已分账' : row.profitSharingStatus === '3' ? '分账异常' : '' }}
- </template>
- </vxe-column>
- <vxe-column field="profitSharing" title="分账比例" align="center">
- <template #default="{ row }">
- {{ row.profitSharing || '-' }}%
- </template>
- </vxe-column>
- <vxe-column field="profitSharingAmount" title="预计分账金额" align="center" >
- <template #default="{ row }">
- {{ row.profitSharingAmount ? row.profitSharingAmount + '元' : '' }}
- </template>
- </vxe-column>
- <vxe-column field="realAmount" title="到账金额" align="center">
- <template #default="{ row }">
- {{ row.realAmount ? row.realAmount + '元' : '' }}
- </template>
- </vxe-column>
- </vxe-table>
- </div>
- <!-- 物流信息和包裹内商品 实体商品才显示 sellType==='1'-->
- <div class="info-section" v-if="data.sellType === '1'">
- <!-- receiptType 收货方式 0-快递 1-线下取货 -->
- <!-- <h3 class="info-title">{{ data.receiptType === '0' ? '物流信息' : data.receiptType === '1' ? '线下取货' : '' }}</h3> -->
- <h3 class="info-title">物流信息</h3>
- <div class="info-content-wrapper" v-if="data.receiptType">
- <!-- 物流信息 -->
- <div class="logistics-content" v-if="data.receiptType === '0'">
- <template v-if="mockPackageItems.length > 0">
- <div class="logistics-tracking">
- <span class="tracking-number">{{ mockLogisticsData.trackingNumber }}</span>
- <a @click="handleCopyTracking" class="copy-link">复制</a>
- </div>
- <div class="logistics-recipient">
- <span class="receive-icon">收</span>
- <span class="recipient-info">
- {{ mockLogisticsData.recipientName
- || '' }} {{ mockLogisticsData.recipientPhone || '' }} {{
- mockLogisticsData.recipientAddress || '' }}
- </span>
- </div>
- </template>
- <template v-else>
- 暂无
- </template>
- </div>
- <div class="logistics-content" v-if="data.receiptType === '1'">
- 线下取货
- </div>
- <!-- 包裹内商品 -->
- <div class="package-items-wrapper" v-if="mockPackageItems.length > 0">
- <h3 class="info-title package-title" v-if="data.receiptType === '0'">
- <span class="star-icon">★</span>
- 包裹内商品
- </h3>
- <div class="package-items">
- <div v-for="(item, index) in mockPackageItems" :key="index" class="package-item">
- <div class="package-item-image" v-if="item.conditioningProgramPhoto">
- <a-image v-if="item.conditioningProgramPhoto" :width="60" :height="60" style="border-radius: 4px;"
- :src="item.conditioningProgramPhoto" class="item-img" />
- </div>
- <div class="package-item-placeholder" v-else>
- <text class="placeholder-icon">📦</text>
- </div>
- <div class="package-item-details">
- <div class="package-item-name">{{ item.conditioningProgramName }}</div>
- <div class="package-item-spec">{{ item.convertDose }} {{ item.convertUnit }}</div>
- </div>
- <div class="package-item-details">
- <div class="package-item-price">¥{{ item.unitPrice }}</div>
- <div class="package-item-quantity">x{{ item.totalMeasure }}</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="info-content-wrapper" v-else>
- 暂无
- </div>
- </div>
- <!-- 服务记录 线下服务才显示 1-实体商品 2-线下服务 3-线上权益-->
- <div class="info-section" v-if="data?.sellType === '2'">
- <h3 class="info-title">
- 服务记录
- <span class="title-count">({{ data?.patientConditioningOfflines?.length || 0 }}/{{ data?.totalMeasure || 0
- }})</span>
- </h3>
- <vxe-table :data="data?.patientConditioningOfflines" border>
- <vxe-column type="seq" title="序号" width="60" align="center" />
- <vxe-column field="arrangeDate" title="服务日期" align="center" />
- <vxe-column field="arrangePeriod" title="服务时间段" align="center" />
- <vxe-column field="verifyStatus" title="服务状态" align="center">
- <template #default="{ row }">
- {{ row.operateTime ? '已核销' : '已预约' }}
- </template>
- </vxe-column>
- <vxe-column field="applyTime" title="预约时间" align="center" />
- <vxe-column field="pieBy" title="派单员" align="center" />
- <vxe-column field="pieTime" title="派单时间" align="center" />
- <vxe-column field="conditioningProgramSupplierName" title="服务机构" align="center" />
- <vxe-column field="operateTime" title="核销时间" align="center" />
- <vxe-column field="startTime" title="操作开始时间" align="center" />
- <vxe-column field="endTime" title="操作结束时间" align="center" />
- <vxe-column field="operateDuration" title="治疗时长" align="center">
- <template #default="{ row }">
- {{ row.operateDuration ? row.operateDuration + '分钟' : '' }}
- </template>
- </vxe-column>
- <vxe-column field="arrangeDuration" title="预定服务时长" align="center">
- <template #default="{ row }">
- {{ row.arrangeDuration ? row.arrangeDuration + '分钟' : '' }}
- </template>
- </vxe-column>
- <vxe-column field="operateBy" title="操作人" align="center" />
- <vxe-column field="feedback" title="治疗备注" align="center" />
- <vxe-column field="acuPointNames" title="用户评价" align="center">
- <template #default="{ row }">
- <vxe-button @click="openSeeEvaluate(row)" content="查看" status="primary" />
- </template>
- </vxe-column>
- </vxe-table>
- </div>
- <!-- 用户评价 -->
- <div class="info-section">
- <div class="info-title-row">
- <h3 class="info-title">用户评价</h3>
- </div>
- <div class="info-content-wrapper">
- <template v-if="hasEvaluate">
- <div class="review-item">
- <div class="review-top-row">
- <div class="review-rating-box">
- <div class="review-rating-row" v-for="scroe in scroeType" :key="scroe.name">
- <span class="review-criterion">{{ scroe.name }}</span>
- <a-rate :value="scroe.score" disabled allow-half class="review-stars" />
- <span class="review-score">{{ scroe.score }}分</span>
- </div>
- </div>
- <div class="review-content-block">
- <div class="review-content-line">
- <span class="review-content-label">评价内容:</span>
- <span class="review-content-text">{{ evaluateDetail.depict }}</span>
- </div>
- <div v-if="evaluateDetail.mediaList?.length" class="review-media-row">
- <div v-for="(media, i) in evaluateDetail.mediaList" :key="i" class="review-media-thumb"
- :class="{ 'is-video': media.type === 'video' }" role="button" tabindex="0"
- @click.stop.prevent="openPreview(evaluateDetail.mediaList, i)"
- @keydown.enter.space.prevent="openPreview(evaluateDetail.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>
- </template>
- <div v-else class="empty-text">无</div>
- </div>
- </div>
- </div>
- </template>
- <style scoped lang="scss">
- .service-detail {
- padding: 20px;
- color: black;
- background: #fff;
- .info-section {
- margin-top: 24px;
- margin-bottom: 24px;
- background: #fff;
- }
- .service-package-placeholder {
- width: 120rpx;
- height: 120rpx;
- border-radius: 8rpx;
- margin-right: 20rpx;
- background-color: #f5f5f5;
- border: 1px solid #e8e8e8;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .placeholder-icon {
- font-size: 40rpx;
- opacity: 0.3;
- }
- .info-title {
- font-size: 16px;
- font-weight: 600;
- margin-bottom: 12px;
- 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-box {
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 12px;
- min-width: 240px;
- }
- .review-rating-row {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-shrink: 0;
- }
- .review-criterion {
- font-size: 14px;
- color: #333;
- width: 72px;
- text-align: left;
- white-space: nowrap;
- }
- .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-line {
- display: flex;
- align-items: flex-start;
- gap: 6px;
- }
- .review-content-label {
- font-size: 14px;
- color: #333;
- margin-right: 4px;
- flex-shrink: 0;
- }
- .review-content-text {
- font-size: 14px;
- color: #333;
- line-height: 22px;
- margin: 0;
- white-space: pre-wrap;
- max-width: 560px;
- word-break: break-word;
- }
- .review-media-row {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- margin-top: 12px;
- margin-left: 70px;
- }
- .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 {
- margin-top: 20px;
- margin-bottom: 12px;
- padding-top: 20px;
- border-top: 1px solid #f0f0f0;
- }
- .info-content-wrapper .info-title:first-child {
- margin-top: 0;
- padding-top: 0;
- border-top: none;
- }
- .info-content-wrapper {
- border: 1px solid #e8e8e8;
- border-radius: 4px;
- padding: 10px 20px;
- }
- /* 分账表格仅按内容自适应高度,避免一行数据时出现多余空白 */
- .split-account-table {
- :deep(.vxe-table--wrapper),
- :deep(.vxe-table) {
- height: auto !important;
- }
- :deep(.vxe-table--body-wrapper) {
- height: auto !important;
- min-height: 0 !important;
- max-height: none !important;
- flex: unset !important;
- }
- }
- .empty-text {
- color: #999;
- font-size: 14px;
- line-height: 22px;
- padding: 8px 0;
- }
- .product-info {
- display: flex;
- align-items: flex-start;
- // margin-bottom: 20px;
- gap: 16px;
- padding-bottom: 10px;
- // border-bottom: 1px solid #f0f0f0;
- }
- .product-image {
- flex-shrink: 0;
- }
- .product-img {
- border-radius: 4px;
- object-fit: cover;
- }
- .product-details {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .product-name {
- font-size: 14px;
- font-weight: 500;
- color: #333;
- line-height: 20px;
- }
- .product-spec {
- font-size: 13px;
- color: #666;
- line-height: 18px;
- }
- .product-price {
- font-size: 14px;
- font-weight: 500;
- color: #333;
- line-height: 20px;
- }
- .info-content {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .info-row {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 24px;
- margin-top: 4px;
- }
- .info-item {
- display: flex;
- align-items: center;
- line-height: 24px;
- }
- .info-label {
- color: #666;
- margin-right: 8px;
- white-space: nowrap;
- // min-width: 80px;
- }
- .info-value {
- color: #333;
- font-weight: 400;
- }
- .title-badge {
- display: inline-block;
- width: 18px;
- height: 18px;
- line-height: 18px;
- text-align: center;
- background: #ffc107;
- color: #333;
- border-radius: 2px;
- margin-left: 6px;
- font-size: 12px;
- font-weight: 600;
- vertical-align: middle;
- }
- .title-count {
- margin-left: 8px;
- font-size: 14px;
- font-weight: 400;
- color: #666;
- }
- .logistics-content {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .logistics-tracking {
- display: flex;
- align-items: center;
- gap: 12px;
- line-height: 24px;
- }
- .tracking-number {
- font-weight: 600;
- color: #333;
- }
- .copy-link {
- color: #1890ff;
- cursor: pointer;
- text-decoration: none;
- font-weight: 400;
- }
- .copy-link:hover {
- text-decoration: underline;
- }
- .logistics-recipient {
- display: flex;
- align-items: flex-start;
- line-height: 24px;
- }
- .receive-icon {
- color: #ff4d4f;
- font-weight: 500;
- margin-right: 8px;
- flex-shrink: 0;
- }
- .recipient-info {
- color: #333;
- font-weight: 400;
- flex: 1;
- }
- .package-title {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .star-icon {
- color: #ffc107;
- font-size: 20px;
- margin-right: 0;
- }
- .package-items {
- display: flex;
- flex-direction: column;
- gap: 16px;
- width: 25%;
- }
- .package-item {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- }
- .package-item-image {
- flex-shrink: 0;
- }
- .package-item-placeholder {
- flex-shrink: 0;
- width: 60px;
- height: 60px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f5f5f5;
- border-radius: 4px;
- }
- .item-img {
- border-radius: 4px;
- object-fit: cover;
- }
- .package-item-details {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 6px;
- min-width: 0;
- }
- .package-item-name {
- font-size: 14px;
- font-weight: 500;
- color: #333;
- line-height: 20px;
- }
- .package-item-spec {
- font-size: 13px;
- color: #666;
- line-height: 18px;
- }
- .package-item-price {
- font-size: 14px;
- font-weight: 500;
- color: #333;
- line-height: 20px;
- text-align: end;
- }
- .package-item-quantity {
- font-size: 14px;
- color: #666;
- // margin-left: auto;
- flex-shrink: 0;
- padding-top: 2px;
- text-align: end;
- }
- .detail-item {
- display: flex;
- margin-bottom: 20px;
- .label {
- // width: 80px;
- margin-right: 10px;
- }
- .content {
- // flex: 1;
- .service-image {
- max-width: 200px;
- height: auto;
- }
- }
- }
- }
- .derivation-label {
- font-weight: 500;
- color: #222;
- margin-right: 8px;
- white-space: nowrap;
- font-size: 13px;
- }
- .derivation-item {
- display: flex;
- align-items: center;
- min-width: 200px;
- max-width: 300px;
- padding: 6px 12px;
- background: #f5f5f5;
- border-radius: 4px;
- border: 1px solid #e8e8e8;
- }
- .derivation-content {
- color: #555;
- font-size: 13px;
- flex: 1;
- word-break: break-all;
- }
- .derivation-container {
- display: flex;
- flex-wrap: wrap;
- gap: 16px;
- align-items: flex-start;
- }
- .video-preview-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- width: 120px;
- height: 100%;
- padding: 10px;
- background: #fafafa;
- border-radius: 8px;
- }
- .video-thumbnail {
- position: relative;
- width: 100%;
- height: 100px;
- border-radius: 6px;
- overflow: hidden;
- background: #000;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 8px;
- }
- .video-thumbnail .video-preview {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .video-overlay {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.7);
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- opacity: 0;
- transition: opacity 0.3s ease;
- }
- .video-preview-container:hover .video-overlay {
- opacity: 1;
- }
- .video-overlay .ant-btn {
- background: rgba(255, 255, 255, 0.95);
- border: none;
- color: #333;
- font-size: 11px;
- padding: 3px 8px;
- border-radius: 4px;
- transition: all 0.3s ease;
- min-width: 40px;
- height: 28px;
- line-height: 1.2;
- display: flex;
- align-items: center;
- justify-content: center;
- white-space: nowrap;
- }
- .video-overlay .ant-btn:hover {
- background: rgba(255, 255, 255, 1);
- transform: scale(1.05);
- }
- .video-overlay .ant-btn-danger {
- background: rgba(255, 77, 79, 0.95);
- color: #fff;
- }
- .video-overlay .ant-btn-danger:hover {
- background: rgba(255, 77, 79, 1);
- }
- .video-info {
- text-align: center;
- font-size: 11px;
- color: #666;
- }
- .video-name {
- font-weight: 500;
- color: #333;
- margin-bottom: 2px;
- }
- .video-size {
- color: #999;
- font-size: 10px;
- }
- .package-items-wrapper {
- border: 1px solid #e8e8e8;
- padding: 10px;
- margin-top: 10px;
- }
- </style>
|