1. 开方线上功能基本完成
2. 修复了一些抽屉组件的滑动失效问题
This commit is contained in:
911
subPackages/sub_online_reception/components/MessageBubble.vue
Normal file
911
subPackages/sub_online_reception/components/MessageBubble.vue
Normal file
@@ -0,0 +1,911 @@
|
||||
<template>
|
||||
<view class="bubble-container" :class="{ mine: isMine, other: !isMine }">
|
||||
<image class="avatar" :src="avatar" mode="aspectFill"></image>
|
||||
|
||||
<view class="content-wrapper">
|
||||
<view
|
||||
class="message-bubble"
|
||||
:class="['type-' + msg.message_type, { 'no-padding': isMediaOrCard || isSystemPriceUpdate }]"
|
||||
>
|
||||
<!-- 0. 文本 -->
|
||||
<text v-if="msg.message_type === 0" class="text-content" user-select>{{ parsedText }}</text>
|
||||
|
||||
<!-- 1. 图片 -->
|
||||
<image
|
||||
v-else-if="msg.message_type === 1"
|
||||
:src="parsedText"
|
||||
mode="widthFix"
|
||||
class="media-content image"
|
||||
@click="$emit('previewImage', parsedText)"
|
||||
></image>
|
||||
|
||||
<!-- 2. 语音 -->
|
||||
<view
|
||||
v-else-if="msg.message_type === 2"
|
||||
class="audio-content"
|
||||
:style="{ width: getAudioWidth(msg.duration) }"
|
||||
@click="$emit('playAudio', msg)"
|
||||
>
|
||||
<block v-if="isMine">
|
||||
<text class="duration">{{ msg.duration }}''</text>
|
||||
<view class="voice-icon-wrap mine" :class="{ playing: isPlaying }">
|
||||
<u-icon name="volume-fill" size="20" color="#fff"></u-icon>
|
||||
</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view class="voice-icon-wrap other" :class="{ playing: isPlaying }">
|
||||
<u-icon name="volume-fill" size="20" color="#333"></u-icon>
|
||||
</view>
|
||||
<text class="duration">{{ msg.duration }}''</text>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 3. 视频 -->
|
||||
<video
|
||||
v-else-if="msg.message_type === 3"
|
||||
:src="videoSrc"
|
||||
controls
|
||||
class="media-content video"
|
||||
></video>
|
||||
|
||||
<!-- 9. 系统消息 -->
|
||||
<block v-else-if="msg.message_type === 9">
|
||||
<view
|
||||
v-if="parsedObj && (parsedObj.type === 'price_update' || parsedObj.type === 'price_adjust')"
|
||||
class="service-notify-card"
|
||||
>
|
||||
<view class="sn-header">
|
||||
<text class="sn-title">{{ parsedObj.type === 'price_update' ? '订单改价通知' : '费用调整提醒' }}</text>
|
||||
</view>
|
||||
<view class="sn-body">
|
||||
<block v-if="parsedObj.type === 'price_update'">
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">变动说明</text>
|
||||
<text class="sn-value">{{ parsedObj.message }}</text>
|
||||
</view>
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">最新应付</text>
|
||||
<text class="sn-value price">¥{{ parsedObj.total_pay_price }}</text>
|
||||
</view>
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">订单编号</text>
|
||||
<text class="sn-value sub">{{ parsedObj.order_no }}</text>
|
||||
</view>
|
||||
</block>
|
||||
<block v-else-if="parsedObj.type === 'price_adjust'">
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">调整项目</text>
|
||||
<text class="sn-value truncate">{{ parsedObj.drug_name || '医疗服务' }}</text>
|
||||
</view>
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">价格变动</text>
|
||||
<view class="sn-value-group">
|
||||
<text class="old">¥{{ parsedObj.old_price }}</text>
|
||||
<u-icon name="arrow-right" size="10" color="#999" style="margin: 0 4rpx"></u-icon>
|
||||
<text class="new">¥{{ parsedObj.new_price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">变动金额</text>
|
||||
<text class="sn-value" :class="parsedObj.adjust_amount >= 0 ? 'color-orange' : 'color-green'">
|
||||
{{ parsedObj.adjust_amount >= 0 ? '增加' : '减少' }} ¥{{ Math.abs(parsedObj.adjust_amount) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">当前总额</text>
|
||||
<text class="sn-value strong">¥{{ parsedObj.total_pay_price }}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="system-inner">
|
||||
<text>{{ systemNinePlainText }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 卡片类 4,10,11,12,13,14 -->
|
||||
<view v-else-if="isCardType" class="card-content">
|
||||
<!-- 10 挂号 -->
|
||||
<block v-if="msg.message_type === 10">
|
||||
<view class="card-header bg-green">
|
||||
<view class="title-group">
|
||||
<u-icon name="calendar-fill" size="16" color="#059669"></u-icon>
|
||||
<text class="card-title">预约挂号</text>
|
||||
</view>
|
||||
<text class="status-tag" :class="'status-' + parsedObj.status">{{ getRegisterStatusText(parsedObj.status) }}</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">订单号</text>
|
||||
<text class="value order-font">{{ parsedObj.order_no }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">就诊人</text>
|
||||
<text class="value">{{ (parsedObj.user_patient && parsedObj.user_patient.name) || '未知' }}</text>
|
||||
</view>
|
||||
<view class="info-row highlight">
|
||||
<text class="label">费用</text>
|
||||
<text class="value price">¥{{ parsedObj.price }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.chief_complaint" class="text-block">
|
||||
<text class="block-label">主诉</text>
|
||||
<text class="block-content">{{ parsedObj.chief_complaint }}</text>
|
||||
</view>
|
||||
<view v-if="registerCardDrugNamesLine" class="info-row">
|
||||
<text class="label">所选药品</text>
|
||||
<text class="value">{{ registerCardDrugNamesLine }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.number != null && parsedObj.number !== ''" class="info-row">
|
||||
<text class="label">数量</text>
|
||||
<text class="value">各 {{ parsedObj.number }} 盒</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 11 复诊用药 -->
|
||||
<block v-else-if="msg.message_type === 11 && parsedObj && parsedObj.flow === 'follow_up_drug'">
|
||||
<view class="card-header bg-gray">
|
||||
<view class="title-group">
|
||||
<u-icon name="question-circle-fill" size="16" color="#64748B"></u-icon>
|
||||
<text class="card-title">医生助理</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view v-if="parsedObj.question" class="text-block">
|
||||
<text class="block-content">{{ parsedObj.question }}</text>
|
||||
</view>
|
||||
<view v-for="(row, idx) in (parsedObj.drugs || [])" :key="'fd-' + idx" class="info-row">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<text class="value">×{{ row.quantity != null ? row.quantity : 1 }}</text>
|
||||
</view>
|
||||
<view v-if="followUpAnsweredLabel" class="info-row">
|
||||
<text class="label">患者选择</text>
|
||||
<text class="value">{{ followUpAnsweredLabel }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.answer_status === 'pending'" class="follow-up-pending-readonly">
|
||||
<text class="follow-up-pending-tip">请在挂号后的用药确认页完成选择,此处不可更改</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 11 就诊经历 -->
|
||||
<block v-else-if="msg.message_type === 11">
|
||||
<view class="card-header bg-gray">
|
||||
<view class="title-group">
|
||||
<u-icon name="file-text-fill" size="16" color="#64748B"></u-icon>
|
||||
<text class="card-title">患者就诊经历</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view v-if="parsedObj.drug_name" class="info-row">
|
||||
<text class="label">药品名称</text>
|
||||
<text class="value">{{ parsedObj.drug_name }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.illness_info" class="text-block">
|
||||
<text class="block-label">症状描述</text>
|
||||
<text class="block-content">{{ parsedObj.illness_info }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">是否就诊过</text>
|
||||
<text class="value">{{ parsedObj.has_visited === '1' ? '是' : '否' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">是否使用过药品</text>
|
||||
<text class="value">{{ parsedObj.has_used_drug === '1' ? '是' : '否' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="parsedObj.drug_name && canAddExperienceToRx && !isMine"
|
||||
class="card-footer card-footer--compact"
|
||||
>
|
||||
<button
|
||||
class="action-btn action-btn--ghost"
|
||||
@click.stop="$emit('addExperienceToRx', experienceRxPayload)"
|
||||
>
|
||||
加入处方
|
||||
</button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 4 电子处方(医生端仅详情) -->
|
||||
<block v-else-if="msg.message_type === 4">
|
||||
<view class="card-header bg-blue">
|
||||
<view class="title-group">
|
||||
<u-icon name="order" size="16" color="#0A84FF"></u-icon>
|
||||
<text class="card-title">电子处方</text>
|
||||
</view>
|
||||
<text class="status-tag blue">{{ getStatusText(parsedObj.status) }}</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">诊断单号</text>
|
||||
<text class="value small">{{ parsedObj.order_no }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">金额</text>
|
||||
<text class="value price">¥{{ parsedObj.total_pay_price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<button class="action-btn" @click="$emit('viewPrescription', parsedObj.id, parsedObj.order_no)">查看详情</button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 12 商品 -->
|
||||
<block v-else-if="msg.message_type === 12">
|
||||
<view class="card-header bg-blue">
|
||||
<view class="title-group">
|
||||
<u-icon name="shopping-cart-fill" size="16" color="#0A84FF"></u-icon>
|
||||
<text class="card-title">推荐商品</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">名称</text>
|
||||
<text class="value">{{ parsedObj.product_name }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">价格</text>
|
||||
<text class="value price">¥{{ parsedObj.price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 13 结束问诊 -->
|
||||
<block v-else-if="msg.message_type === 13">
|
||||
<view class="card-header bg-green">
|
||||
<view class="title-group">
|
||||
<u-icon name="checkmark-circle-fill" size="16" color="#059669"></u-icon>
|
||||
<text class="card-title">问诊结束</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="desc-text">{{ parsedObj.reason || '本次服务已完成' }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 14 转诊(只读,与患者端视觉一致) -->
|
||||
<block v-else-if="msg.message_type === 14">
|
||||
<view class="transfer-readonly">
|
||||
<view class="transfer-r-head">
|
||||
<u-icon name="order" size="20" color="#ea580c"></u-icon>
|
||||
<text class="transfer-r-title">转诊消息</text>
|
||||
</view>
|
||||
<view class="transfer-r-body">
|
||||
<view v-if="parsedObj.transfer_store" class="transfer-r-row">
|
||||
<text class="transfer-r-k">转诊诊所</text>
|
||||
<text class="transfer-r-v">{{ parsedObj.transfer_store.name || '未知' }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.delegate_store" class="transfer-r-row">
|
||||
<text class="transfer-r-k">委托诊所</text>
|
||||
<text class="transfer-r-v">{{ parsedObj.delegate_store.name || '未知' }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.prescription_no" class="transfer-r-row">
|
||||
<text class="transfer-r-k">处方编号</text>
|
||||
<text class="transfer-r-v">{{ parsedObj.prescription_no }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.transfer_reason" class="transfer-r-row">
|
||||
<text class="transfer-r-k">转诊原因</text>
|
||||
<text class="transfer-r-v">{{ parsedObj.transfer_reason }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.questions && parsedObj.questions.length" class="transfer-qa">
|
||||
<text class="transfer-qa-title">咨询问题</text>
|
||||
<view v-for="(qa, qix) in parsedObj.questions" :key="qix" class="transfer-qa-item">
|
||||
<text class="transfer-qa-q">{{ qix + 1 }}. {{ qa.question }}</text>
|
||||
<text class="transfer-qa-a">{{ qa.answer }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="transfer-r-foot">
|
||||
<text v-if="parsedObj.status === 0" class="transfer-r-status">待患者确认转诊</text>
|
||||
<text v-else-if="parsedObj.status === 1" class="transfer-r-status ok">患者已同意转诊</text>
|
||||
<text v-else-if="parsedObj.status === 3" class="transfer-r-status no">患者已拒绝转诊</text>
|
||||
<text v-else class="transfer-r-status">转诊状态:{{ parsedObj.status }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block v-else>
|
||||
<view class="card-body"><text>不支持的消息类型</text></view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<text v-else class="text-content">{{ unknownText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { parseMessageContent } from '../utils/messageParse.js';
|
||||
|
||||
export default {
|
||||
name: 'MessageBubble',
|
||||
props: {
|
||||
msg: { type: Object, required: true },
|
||||
isMine: { type: Boolean, default: false },
|
||||
avatar: { type: String, default: '' },
|
||||
isPlaying: { type: Boolean, default: false },
|
||||
/** 接诊中且可开方时,就诊经历卡片显示「加入处方」 */
|
||||
canAddExperienceToRx: { type: Boolean, default: false }
|
||||
},
|
||||
computed: {
|
||||
isMediaOrCard() {
|
||||
return [1, 3, 4, 10, 11, 12, 13, 14].includes(this.msg.message_type);
|
||||
},
|
||||
isSystemPriceUpdate() {
|
||||
const p = this.parsedObj;
|
||||
return this.msg.message_type === 9 && p && (p.type === 'price_update' || p.type === 'price_adjust');
|
||||
},
|
||||
isCardType() {
|
||||
return [4, 10, 11, 12, 13, 14].includes(this.msg.message_type);
|
||||
},
|
||||
parsedObj() {
|
||||
const { message_type, message_content } = this.msg;
|
||||
if (![4, 9, 10, 11, 12, 13, 14].includes(message_type)) return null;
|
||||
try {
|
||||
return typeof message_content === 'object' ? message_content : JSON.parse(message_content);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
parsedText() {
|
||||
const raw = parseMessageContent(this.msg.message_type, this.msg.message_content);
|
||||
if (this.msg.message_type === 0 || this.msg.message_type === 1) {
|
||||
return typeof raw === 'string' ? raw : '';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
videoSrc() {
|
||||
if (this.msg.message_type !== 3) return '';
|
||||
const p = parseMessageContent(3, this.msg.message_content);
|
||||
if (typeof p === 'string') return p;
|
||||
if (p && typeof p === 'object' && p.url) return p.url;
|
||||
return '';
|
||||
},
|
||||
systemNinePlainText() {
|
||||
const c = this.msg.message_content;
|
||||
if (c == null) return '';
|
||||
if (typeof c === 'object') return JSON.stringify(c);
|
||||
return String(c);
|
||||
},
|
||||
registerCardDrugNamesLine() {
|
||||
if (this.msg.message_type !== 10) return '';
|
||||
const pc = this.parsedObj;
|
||||
if (!pc || typeof pc !== 'object') return '';
|
||||
const arr = pc.selected_western_drugs;
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
return arr.map((d) => d && d.name).filter(Boolean).join('、');
|
||||
}
|
||||
if (pc.drug && pc.drug.name) return pc.drug.name;
|
||||
return '';
|
||||
},
|
||||
followUpAnsweredLabel() {
|
||||
const pc = this.parsedObj;
|
||||
if (!pc || pc.flow !== 'follow_up_drug') return '';
|
||||
const st = pc.answer_status;
|
||||
if (st === 'yes' || st === '1') return '是,曾使用过';
|
||||
if (st === 'no' || st === '0') return '否,未使用过';
|
||||
return '';
|
||||
},
|
||||
experienceRxPayload() {
|
||||
const pc = this.parsedObj;
|
||||
if (!pc || this.msg.message_type !== 11) return {};
|
||||
return {
|
||||
drug_name: pc.drug_name || pc.name,
|
||||
drug_id: pc.drug_id != null ? pc.drug_id : pc.id
|
||||
};
|
||||
},
|
||||
unknownText() {
|
||||
const c = this.msg.message_content;
|
||||
if (c == null) return '[消息]';
|
||||
if (typeof c === 'object') return '[卡片消息]';
|
||||
const s = String(c);
|
||||
return s.length > 120 ? s.slice(0, 120) + '…' : s;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAudioWidth(duration) {
|
||||
const min = 120;
|
||||
const max = 350;
|
||||
const unit = 12;
|
||||
let w = min + (parseInt(duration, 10) || 0) * unit;
|
||||
return Math.min(w, max) + 'rpx';
|
||||
},
|
||||
getStatusText(status) {
|
||||
const map = { 0: '待审核', 1: '已审核', 2: '未通过' };
|
||||
return map[status] || '状态未知';
|
||||
},
|
||||
getRegisterStatusText(status) {
|
||||
const map = { 0: '待就诊', 1: '已缴费', 2: '已就诊', 3: '已取消', 4: '已退费' };
|
||||
return map[status] || '状态未知';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$primary: #0a84ff;
|
||||
$bg-mine: #0a84ff;
|
||||
$bg-other: #ffffff;
|
||||
$text-main: #333333;
|
||||
$text-sub: #666666;
|
||||
$radius-bubble: 12rpx;
|
||||
|
||||
.bubble-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30rpx;
|
||||
width: 100%;
|
||||
|
||||
.avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 10rpx;
|
||||
flex-shrink: 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 72%;
|
||||
}
|
||||
|
||||
&.other {
|
||||
flex-direction: row;
|
||||
.avatar {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
.message-bubble {
|
||||
background-color: $bg-other;
|
||||
color: $text-main;
|
||||
border-radius: 4rpx $radius-bubble $radius-bubble $radius-bubble;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: -10rpx;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10rpx solid transparent;
|
||||
border-bottom: 10rpx solid transparent;
|
||||
border-right: 12rpx solid $bg-other;
|
||||
}
|
||||
&.no-padding::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mine {
|
||||
flex-direction: row-reverse;
|
||||
.avatar {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
.message-bubble {
|
||||
background-color: $bg-mine;
|
||||
color: #fff;
|
||||
border-radius: $radius-bubble 4rpx $radius-bubble $radius-bubble;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: -10rpx;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10rpx solid transparent;
|
||||
border-bottom: 10rpx solid transparent;
|
||||
border-left: 12rpx solid $bg-mine;
|
||||
}
|
||||
&.no-padding::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
min-height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.text-content {
|
||||
color: inherit;
|
||||
}
|
||||
&.mine.type-0 .text-content {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
background: transparent !important;
|
||||
box-shadow: none;
|
||||
&::before,
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-content {
|
||||
border-radius: 8rpx;
|
||||
&.image {
|
||||
max-width: 300rpx;
|
||||
display: block;
|
||||
}
|
||||
&.video {
|
||||
width: 300rpx;
|
||||
height: 170rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.duration {
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.voice-icon-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
&.other {
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
&.mine {
|
||||
margin-left: 6rpx;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
&.playing {
|
||||
animation: voice-pulse 1.2s infinite ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes voice-pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(var(--rotate-angle, 0deg));
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
transform: scale(0.92) rotate(var(--rotate-angle, 0deg));
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(var(--rotate-angle, 0deg));
|
||||
}
|
||||
}
|
||||
.voice-icon-wrap.mine.playing {
|
||||
--rotate-angle: 180deg;
|
||||
animation: voice-pulse 1.2s infinite ease-in-out;
|
||||
}
|
||||
.voice-icon-wrap.other.playing {
|
||||
--rotate-angle: 0deg;
|
||||
animation: voice-pulse 1.2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
border: 1rpx solid #eee;
|
||||
width: 480rpx;
|
||||
|
||||
.card-header {
|
||||
padding: 16rpx 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1rpx solid #f9f9f9;
|
||||
&.bg-green {
|
||||
background: #f0fdf4;
|
||||
}
|
||||
&.bg-blue {
|
||||
background: #eff6ff;
|
||||
}
|
||||
&.bg-gray {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
.card-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.status-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
background: #fff;
|
||||
color: $text-sub;
|
||||
border: 1rpx solid rgba(0, 0, 0, 0.05);
|
||||
&.blue {
|
||||
color: $primary;
|
||||
border-color: rgba(10, 132, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 24rpx;
|
||||
.label {
|
||||
color: #888;
|
||||
}
|
||||
.value {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
max-width: 70%;
|
||||
}
|
||||
.order-font {
|
||||
font-family: Courier, monospace;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
.price {
|
||||
color: #ef4444;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.small {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.desc-text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
.text-block {
|
||||
background: #f5f5f5;
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
.block-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
.block-content {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 16rpx 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
&.card-footer--compact {
|
||||
padding: 12rpx 20rpx;
|
||||
}
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
font-size: 26rpx;
|
||||
border-radius: 30rpx;
|
||||
background: $primary;
|
||||
color: #fff;
|
||||
border: none;
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
&.action-btn--ghost {
|
||||
background: #f0fdf4;
|
||||
color: #059669;
|
||||
border: 1rpx solid #bbf7d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service-notify-card {
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
width: 480rpx;
|
||||
border: 1rpx solid #eaeaea;
|
||||
overflow: hidden;
|
||||
.sn-header {
|
||||
padding: 24rpx 24rpx 16rpx 24rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
.sn-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.sn-body {
|
||||
padding: 20rpx 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
.sn-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
line-height: 1.4;
|
||||
.sn-label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
min-width: 110rpx;
|
||||
}
|
||||
.sn-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
&.truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 320rpx;
|
||||
}
|
||||
&.price {
|
||||
font-weight: 600;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
&.sub {
|
||||
color: #999;
|
||||
font-family: monospace;
|
||||
}
|
||||
&.strong {
|
||||
font-weight: bold;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
&.color-orange {
|
||||
color: #fa8c16;
|
||||
}
|
||||
&.color-green {
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
.sn-value-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
.old {
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.new {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-inner {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
align-self: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.follow-up-pending-readonly {
|
||||
margin-top: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 12rpx;
|
||||
.follow-up-pending-tip {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.transfer-readonly {
|
||||
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid #fed7aa;
|
||||
padding: 20rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.transfer-r-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
padding-bottom: 16rpx;
|
||||
margin-bottom: 12rpx;
|
||||
border-bottom: 2rpx solid #fed7aa;
|
||||
}
|
||||
.transfer-r-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
.transfer-r-body {
|
||||
font-size: 26rpx;
|
||||
color: #334155;
|
||||
}
|
||||
.transfer-r-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.transfer-r-k {
|
||||
color: #64748b;
|
||||
width: 160rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.transfer-r-v {
|
||||
flex: 1;
|
||||
color: #1e293b;
|
||||
}
|
||||
.transfer-qa {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #fed7aa;
|
||||
}
|
||||
.transfer-qa-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.transfer-qa-item {
|
||||
margin-bottom: 12rpx;
|
||||
padding: 12rpx;
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.transfer-qa-q {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #334155;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.transfer-qa-a {
|
||||
font-size: 24rpx;
|
||||
color: #0a84ff;
|
||||
}
|
||||
.transfer-r-foot {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 12rpx;
|
||||
border-top: 2rpx solid #fed7aa;
|
||||
}
|
||||
.transfer-r-status {
|
||||
font-size: 26rpx;
|
||||
color: #64748b;
|
||||
&.ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
&.no {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user