Compare commits

...
Author SHA1 Message Date
Your Name c0e3cbd52e 更新 2026-07-10 09:23:23 +08:00
Your Name c07dfe4e36 更新 2026-07-05 08:59:47 +08:00
Your Name 1777fe7d8b 新增美颜 2026-07-03 16:51:02 +08:00
853 changed files with 39997 additions and 2343 deletions
+7
View File
@@ -11,6 +11,7 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@highlightjs/vue-plugin": "^2.1.0",
"@mediapipe/tasks-vision": "^0.10.35",
"@tencentcloud/call-uikit-vue": "^4.0.12",
"@tencentcloud/chat-uikit-vue3": "^4.5.4",
"@trtc/calls-uikit-vue": "^4.4.6",
@@ -2484,6 +2485,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.35",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.35.tgz",
"integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==",
"license": "Apache-2.0"
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+1
View File
@@ -13,6 +13,7 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@highlightjs/vue-plugin": "^2.1.0",
"@mediapipe/tasks-vision": "^0.10.35",
"@tencentcloud/call-uikit-vue": "^4.0.12",
"@tencentcloud/chat-uikit-vue3": "^4.5.4",
"@trtc/calls-uikit-vue": "^4.4.6",
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,812 @@
<template>
<span class="beauty-trigger" @click="openPanel">
<slot name="reference">
<el-button type="primary" link size="small">
<el-icon><MagicStick /></el-icon>
<span class="beauty-btn-text">美颜</span>
</el-button>
</slot>
</span>
<Teleport to="body">
<div
v-if="visible"
class="beauty-window"
:style="windowStyle"
@mousedown.stop
>
<div class="beauty-window-header" @mousedown="onDragStart">
<span class="beauty-window-title">
<el-icon><MagicStick /></el-icon>
美颜设置
</span>
<el-button type="danger" link class="beauty-window-close" @mousedown.stop @click="closePanel">
<el-icon><Close /></el-icon>
</el-button>
</div>
<div class="beauty-window-body">
<!-- 左侧大预览 -->
<div class="beauty-preview-col">
<div ref="previewStageRef" class="beauty-preview-stage">
<video
v-show="previewing"
ref="previewVideoRef"
class="beauty-preview-video"
autoplay
muted
playsinline
></video>
<div v-if="!previewing" class="beauty-preview-placeholder">
<el-icon :size="36"><VideoCamera /></el-icon>
<span v-if="previewLoading">正在打开摄像头...</span>
<span v-else-if="previewError" class="beauty-preview-error-text">{{ previewError }}</span>
<span v-else>摄像头预览未开启</span>
</div>
<span v-if="previewing" class="beauty-preview-badge">对方看到的效果镜像显示</span>
<el-button
v-if="previewing"
class="beauty-fullscreen-btn"
circle
size="small"
title="全屏预览(按 Esc 退出)"
@click="toggleFullscreen"
>
<el-icon><FullScreen /></el-icon>
</el-button>
</div>
<div class="beauty-preview-toolbar">
<el-button
size="small"
:loading="previewLoading"
@click="togglePreview"
>
{{ previewing ? '关闭预览' : '打开预览' }}
</el-button>
<el-button
v-if="previewing"
size="small"
@click="toggleFullscreen"
>
<el-icon><FullScreen /></el-icon>
<span style="margin-left: 2px">全屏</span>
</el-button>
<span class="beauty-preview-note">预览效果即对方所见画面镜像显示</span>
</div>
</div>
<!-- 右侧调节项 -->
<div class="beauty-control-col">
<div class="beauty-section-header">
<span class="beauty-section-title">基础美颜</span>
<el-switch v-model="settings.enabled" size="small" />
</div>
<div class="beauty-section-body" :class="{ 'is-disabled': !settings.enabled }">
<div class="beauty-item">
<span class="beauty-label">磨皮算法</span>
<el-radio-group
v-model="settings.style"
size="small"
:disabled="!settings.enabled"
>
<el-radio-button :value="BEAUTY_STYLE_NATURE">自然</el-radio-button>
<el-radio-button :value="BEAUTY_STYLE_SMOOTH">光滑</el-radio-button>
</el-radio-group>
</div>
<div class="beauty-item">
<el-tooltip content="磨皮仅作用于人脸皮肤,眉眼/背景/衣物不受影响" placement="left">
<span class="beauty-label has-tip">磨皮</span>
</el-tooltip>
<el-slider
v-model="settings.beautyLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
<div class="beauty-item">
<span class="beauty-label">美白</span>
<el-slider
v-model="settings.whitenessLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
<div class="beauty-item">
<span class="beauty-label">红润</span>
<el-slider
v-model="settings.ruddinessLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
<div class="beauty-item">
<el-tooltip content="淡化脸上色斑 / 黑色素沉着,仅作用于人脸皮肤" placement="left">
<span class="beauty-label has-tip">祛斑</span>
</el-tooltip>
<el-slider
v-model="settings.spotLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
<div class="beauty-item">
<el-tooltip content="淡化脸上小面积深色痣点,仅作用于人脸皮肤" placement="left">
<span class="beauty-label has-tip">去痣</span>
</el-tooltip>
<el-slider
v-model="settings.moleLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
<div class="beauty-item">
<el-tooltip content="提亮眼下区域,减轻黑眼圈" placement="left">
<span class="beauty-label has-tip">去黑眼圈</span>
</el-tooltip>
<el-slider
v-model="settings.darkCircleLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
<div class="beauty-item">
<el-tooltip content="提升画面整体亮度,适合光线偏暗环境" placement="left">
<span class="beauty-label has-tip">亮度</span>
</el-tooltip>
<el-slider
v-model="settings.brightnessLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
<div class="beauty-item">
<el-tooltip content="锐化细节,让画面更清晰" placement="left">
<span class="beauty-label has-tip">清晰度</span>
</el-tooltip>
<el-slider
v-model="settings.clarityLevel"
:min="0"
:max="BEAUTY_LEVEL_MAX"
:step="1"
:disabled="!settings.enabled"
/>
</div>
</div>
<el-divider class="beauty-divider" />
<div class="beauty-section-header">
<span class="beauty-section-title">
高级美颜
<el-tooltip
placement="top"
content="瘦脸/口红/腮红由本机 AI 实时处理(免费,无需腾讯付费特效)。开关在下次开启摄像头时生效,通话中可关闭摄像头再打开。"
>
<el-icon class="beauty-help-icon"><QuestionFilled /></el-icon>
</el-tooltip>
</span>
<el-switch
v-model="settings.advanced.enabled"
size="small"
:disabled="!advancedSupported"
/>
</div>
<div v-if="!advancedSupported" class="beauty-unsupported">
当前浏览器不支持高级美颜需要 WebGL请使用新版 Chrome / Edge
</div>
<div
v-else
class="beauty-section-body"
:class="{ 'is-disabled': !settings.advanced.enabled }"
>
<div class="beauty-item">
<span class="beauty-label">瘦脸</span>
<el-slider
v-model="settings.advanced.slimStrength"
:min="0"
:max="100"
:step="1"
:disabled="!settings.advanced.enabled"
/>
</div>
<div class="beauty-item">
<span class="beauty-label">口红</span>
<el-slider
v-model="settings.advanced.lipstickStrength"
:min="0"
:max="100"
:step="1"
:disabled="!settings.advanced.enabled"
/>
<el-color-picker
v-model="settings.advanced.lipstickColor"
size="small"
:predefine="LIPSTICK_PRESETS"
:disabled="!settings.advanced.enabled"
/>
</div>
<div class="beauty-item">
<span class="beauty-label">腮红</span>
<el-slider
v-model="settings.advanced.blushStrength"
:min="0"
:max="100"
:step="1"
:disabled="!settings.advanced.enabled"
/>
<el-color-picker
v-model="settings.advanced.blushColor"
size="small"
:predefine="BLUSH_PRESETS"
:disabled="!settings.advanced.enabled"
/>
</div>
</div>
<div v-if="needCameraRestartHint" class="beauty-restart-hint">
通话中如果摄像头已按原始画面开启需关闭摄像头再打开才能生效
</div>
<div class="beauty-window-footer">
<span class="beauty-tip">全部为免费能力通话中实时生效</span>
<el-button link type="primary" size="small" @click="handleReset">
恢复默认
</el-button>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { reactive, ref, watch, computed, nextTick, onBeforeUnmount } from 'vue'
import { MagicStick, QuestionFilled, Close, VideoCamera, FullScreen } from '@element-plus/icons-vue'
import {
BEAUTY_LEVEL_MAX,
BEAUTY_STYLE_NATURE,
BEAUTY_STYLE_SMOOTH,
applyBeautyToEngine,
defaultBeautySettings,
hasAnyBeautyEffect,
loadBeautySettings,
saveBeautySettings
} from '@/utils/call-beauty'
import {
getRawUserMedia,
installBeautyMediaInterceptor,
preloadBeautyModel
} from '@/utils/beauty/beauty-media-interceptor'
import {
FaceBeautyPipeline,
hasActiveBeautyPipeline,
isAdvancedBeautySupported,
updateActiveBeautyPipelines
} from '@/utils/beauty/face-beauty-pipeline'
const LIPSTICK_PRESETS = ['#c94f5e', '#b03a48', '#d96a76', '#a52a3c', '#e0788a', '#8f2436']
const BLUSH_PRESETS = ['#e88193', '#f0a3b0', '#dd6b7f', '#f2b8c0', '#e5949e']
const WINDOW_WIDTH = 780
const props = withDefaults(
defineProps<{
/** 获取当前 TUICallEngine 实例(未初始化时返回 null 即可) */
getEngine: () => any
/** 面板 z-index,需高于承载视频通话的浮窗 */
popperZIndex?: number
}>(),
{
popperZIndex: 300001
}
)
// 拦截器必须先于 SDK 申请摄像头安装;组件被通话相关页面引入时即安装
installBeautyMediaInterceptor()
const settings = reactive(loadBeautySettings())
const advancedSupported = isAdvancedBeautySupported()
// 面板挂载即预热模型,避免首次打开预览时同步加载 WASM/模型卡死
if (advancedSupported) preloadBeautyModel()
/** 美颜已开启但当前没有活跃通话管线 → 摄像头是在开启前用原始画面打开的 */
const needCameraRestartHint = computed(() => {
return hasAnyBeautyEffect(settings) && !hasActiveBeautyPipeline()
})
// ---------------------------------------------------------------------------
// 浮动窗口(可拖拽)
// ---------------------------------------------------------------------------
const visible = ref(false)
const windowPos = reactive({ x: 0, y: 0 })
const windowStyle = computed(() => ({
left: `${windowPos.x}px`,
top: `${windowPos.y}px`,
width: `${WINDOW_WIDTH}px`,
zIndex: props.popperZIndex
}))
const dragState = { active: false, offsetX: 0, offsetY: 0 }
const onDragMove = (e: MouseEvent) => {
if (!dragState.active) return
windowPos.x = Math.max(0, Math.min(window.innerWidth - 120, e.clientX - dragState.offsetX))
windowPos.y = Math.max(0, Math.min(window.innerHeight - 60, e.clientY - dragState.offsetY))
}
const onDragEnd = () => {
dragState.active = false
document.removeEventListener('mousemove', onDragMove)
document.removeEventListener('mouseup', onDragEnd)
}
const onDragStart = (e: MouseEvent) => {
dragState.active = true
dragState.offsetX = e.clientX - windowPos.x
dragState.offsetY = e.clientY - windowPos.y
document.addEventListener('mousemove', onDragMove)
document.addEventListener('mouseup', onDragEnd)
e.preventDefault()
}
const openPanel = async () => {
if (visible.value) return
windowPos.x = Math.max(16, Math.floor((window.innerWidth - WINDOW_WIDTH) / 2))
windowPos.y = Math.max(16, Math.floor(window.innerHeight * 0.12))
visible.value = true
// 先预热模型,并等面板先画出「正在打开摄像头」再启预览,避免首开卡死
if (advancedSupported) {
preloadBeautyModel()
await nextTick()
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
void startPreview()
}
}
const closePanel = () => {
stopPreview()
visible.value = false
}
// ---------------------------------------------------------------------------
// 基础美颜:下发到 TUICallEngine
// ---------------------------------------------------------------------------
let applyTimer: ReturnType<typeof setTimeout> | null = null
/**
* 基础美颜下发到 TUICallEngineTRTC BasicBeauty 插件)。
* 仅作为 WebGL 不可用时的回退——正常情况下全部效果走本地管线。
* WebGL 可用时始终向引擎下发全 0,确保引擎侧插件保持关闭,
* 不会与本地管线叠加,也保证「关闭美颜」时引擎侧残留效果被清掉。
*/
const applyNow = async () => {
if (advancedSupported) {
await applyBeautyToEngine(props.getEngine(), { ...settings, enabled: false })
return
}
await applyBeautyToEngine(props.getEngine(), settings)
}
/** 通话建立后调用:仅在启用了美颜时下发,避免无意义地拉起插件 */
const applyIfEnabled = async () => {
if (!settings.enabled) return
await applyNow()
}
watch(
settings,
() => {
saveBeautySettings(settings)
// 全部美颜参数对活跃管线(含预览)实时生效
updateActiveBeautyPipelines({ ...settings, advanced: { ...settings.advanced } })
if (settings.advanced.enabled) preloadBeautyModel()
if (applyTimer) clearTimeout(applyTimer)
// 滑杆拖动会高频触发,防抖后再下发到引擎(仅 WebGL 回退场景实际生效)
applyTimer = setTimeout(() => {
applyTimer = null
void applyNow()
}, 200)
},
{ deep: true }
)
// ---------------------------------------------------------------------------
// 效果预览:本地摄像头 → 同一条美颜管线 → 大画面播放,不发起任何通话
// ---------------------------------------------------------------------------
const previewing = ref(false)
const previewLoading = ref(false)
const previewError = ref('')
const previewVideoRef = ref<HTMLVideoElement | null>(null)
const previewStageRef = ref<HTMLElement | null>(null)
let previewPipeline: FaceBeautyPipeline | null = null
const toggleFullscreen = () => {
if (document.fullscreenElement) {
void document.exitFullscreen().catch(() => {})
} else {
void previewStageRef.value?.requestFullscreen().catch(() => {})
}
}
const startPreview = async () => {
if (previewing.value || previewLoading.value) return
previewError.value = ''
previewLoading.value = true
try {
// 720p 采集保证预览清晰;管线内部会做适度降采样控制处理开销
const raw = await getRawUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
},
audio: false
})
const track = raw.getVideoTracks()[0]
if (!track) throw new Error('未获取到摄像头画面')
// 让出一帧,保证 loading 文案先渲染
await nextTick()
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
previewPipeline = new FaceBeautyPipeline(
track,
{ ...settings, advanced: { ...settings.advanced } },
{ isPreview: true }
)
const processedTrack = await previewPipeline.start()
previewing.value = true
await nextTick()
if (previewVideoRef.value) {
previewVideoRef.value.srcObject = new MediaStream([processedTrack])
await previewVideoRef.value.play().catch(() => {})
}
} catch (err: any) {
console.warn('[call-beauty] 预览启动失败', err)
previewPipeline?.destroy()
previewPipeline = null
previewing.value = false
previewError.value =
err?.name === 'NotAllowedError'
? '摄像头权限被拒绝,请在浏览器地址栏允许摄像头访问'
: err?.name === 'NotReadableError'
? '摄像头被其他程序占用,无法打开预览'
: '预览启动失败,请检查摄像头设备'
} finally {
previewLoading.value = false
}
}
const stopPreview = () => {
if (document.fullscreenElement && document.fullscreenElement === previewStageRef.value) {
void document.exitFullscreen().catch(() => {})
}
if (previewVideoRef.value) previewVideoRef.value.srcObject = null
// destroy 会同时停掉输出轨和源摄像头轨,释放摄像头占用
previewPipeline?.destroy()
previewPipeline = null
previewing.value = false
previewLoading.value = false
}
const togglePreview = () => {
if (previewing.value) {
stopPreview()
} else {
void startPreview()
}
}
const handleReset = () => {
const defaults = defaultBeautySettings()
Object.assign(settings.advanced, defaults.advanced, { enabled: settings.advanced.enabled })
Object.assign(settings, {
style: defaults.style,
beautyLevel: defaults.beautyLevel,
whitenessLevel: defaults.whitenessLevel,
ruddinessLevel: defaults.ruddinessLevel,
spotLevel: defaults.spotLevel,
moleLevel: defaults.moleLevel,
darkCircleLevel: defaults.darkCircleLevel,
brightnessLevel: defaults.brightnessLevel,
clarityLevel: defaults.clarityLevel
})
}
onBeforeUnmount(() => {
if (applyTimer) clearTimeout(applyTimer)
stopPreview()
onDragEnd()
})
defineExpose({
applyNow,
applyIfEnabled,
openPanel
})
</script>
<style lang="scss" scoped>
.beauty-trigger {
display: inline-flex;
align-items: center;
}
.beauty-btn-text {
margin-left: 2px;
font-size: 12px;
}
</style>
<style lang="scss">
.beauty-window {
position: fixed;
max-width: calc(100vw - 24px);
max-height: calc(100vh - 24px);
background: #fff;
border-radius: 10px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28);
overflow: hidden;
display: flex;
flex-direction: column;
.beauty-window-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
cursor: move;
user-select: none;
.beauty-window-title {
display: inline-flex;
align-items: center;
gap: 6px;
color: #fff;
font-size: 14px;
font-weight: 500;
}
.beauty-window-close {
color: #fff;
&:hover {
color: #ffd4d4;
}
}
}
.beauty-window-body {
display: flex;
gap: 16px;
padding: 16px;
flex: 1;
min-height: 0;
overflow: hidden;
@media (max-width: 820px) {
flex-direction: column;
overflow-y: auto;
}
}
.beauty-preview-col {
flex: 1 1 440px;
min-width: 0;
.beauty-preview-stage {
position: relative;
border-radius: 8px;
overflow: hidden;
background: #111;
aspect-ratio: 16 / 9;
.beauty-preview-video {
width: 100%;
height: 100%;
/* contain 完整显示画面,与实际发送给对方的内容一致,不裁切 */
object-fit: contain;
/* 本地自拍习惯镜像显示;实际发送给对方的画面不镜像 */
transform: scaleX(-1);
}
.beauty-preview-placeholder {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
color: #8a8f99;
font-size: 13px;
padding: 0 24px;
text-align: center;
.beauty-preview-error-text {
color: #f56c6c;
}
}
.beauty-preview-badge {
position: absolute;
left: 10px;
bottom: 10px;
padding: 3px 10px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.55);
font-size: 12px;
color: #fff;
line-height: 1.6;
}
.beauty-fullscreen-btn {
position: absolute;
right: 10px;
top: 10px;
background: rgba(0, 0, 0, 0.45);
border-color: transparent;
color: #fff;
&:hover {
background: rgba(0, 0, 0, 0.65);
color: #fff;
}
}
/* 全屏时铺满屏幕 */
&:fullscreen {
aspect-ratio: auto;
width: 100vw;
height: 100vh;
}
}
.beauty-preview-toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
.beauty-preview-note {
font-size: 12px;
color: #909399;
line-height: 1.5;
}
}
}
.beauty-control-col {
flex: 0 0 300px;
min-width: 260px;
display: flex;
flex-direction: column;
min-height: 0;
overflow-y: auto;
padding-right: 6px;
scrollbar-width: thin;
}
.beauty-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
.beauty-section-title {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 14px;
font-weight: 500;
color: #303133;
}
.beauty-help-icon {
color: #a8abb2;
cursor: help;
font-size: 14px;
}
}
.beauty-divider {
margin: 10px 0;
}
.beauty-section-body {
&.is-disabled {
opacity: 0.75;
}
.beauty-item {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 4px;
.beauty-label {
flex-shrink: 0;
width: 64px;
font-size: 13px;
color: #606266;
&.has-tip {
cursor: help;
text-decoration: underline dotted #c0c4cc;
text-underline-offset: 3px;
}
}
.el-slider {
flex: 1;
margin-right: 8px;
}
.el-color-picker {
flex-shrink: 0;
}
&.beauty-item-hint {
margin-top: -2px;
margin-bottom: 6px;
.beauty-hint {
font-size: 12px;
color: #909399;
line-height: 1.4;
}
}
}
}
.beauty-unsupported {
padding: 4px 0 8px;
font-size: 12px;
color: #e6a23c;
line-height: 1.5;
}
.beauty-restart-hint {
margin-top: 4px;
padding: 4px 8px;
border-radius: 4px;
background: #fdf6ec;
font-size: 12px;
color: #e6a23c;
line-height: 1.5;
}
.beauty-window-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
padding-top: 8px;
.beauty-tip {
font-size: 12px;
color: #909399;
}
}
}
</style>
+33 -1
View File
@@ -64,6 +64,20 @@
>
群视频
</el-button>
<!-- 美颜设置免费基础美颜 + 本地 AI 瘦脸/口红/腮红带大窗口实时预览仅对开启了美颜功能的医生显示 -->
<CallBeautyPanel
v-if="isCallReady && beautyAllowed"
ref="beautyPanelRef"
:get-engine="getCallEngine"
:popper-z-index="300002"
>
<template #reference>
<el-button size="small">
<el-icon><MagicStick /></el-icon>
<span style="margin-left: 2px">美颜</span>
</el-button>
</template>
</CallBeautyPanel>
</div>
</template>
</MessageInput>
@@ -127,7 +141,8 @@
<script setup lang="ts">
import { ref, nextTick, watch, computed, onMounted, onUnmounted } from 'vue'
import { Loading, Close, Rank, Camera, Fold, Expand } from '@element-plus/icons-vue'
import { Loading, Close, Rank, Camera, Fold, Expand, MagicStick } from '@element-plus/icons-vue'
import CallBeautyPanel from '@/components/call-beauty-panel/index.vue'
import { uploadImageBlob, uploadVideoBlob } from '@/api/file'
import { addDoctorNote } from '@/api/patient'
import {
@@ -187,6 +202,17 @@ const patientUserId = ref('')
const conversationId = ref('')
const isCallReady = ref(false)
const showCallKitWindow = ref(false) // 仅在通话中显示视频窗口
/** 美颜面板(本地 AI 管线 + 引擎兜底,非付费特效 SDK) */
const beautyPanelRef = ref<InstanceType<typeof CallBeautyPanel> | null>(null)
/** 医生编辑页「美颜功能」开关:关闭时不显示美颜入口 */
const beautyAllowed = computed(() => userStore.userInfo?.enable_beauty !== 0)
const getCallEngine = () => {
try {
return TUICallKitServer.getTUICallEngineInstance()
} catch {
return null
}
}
const { login, logout } = useLoginState()
const { setActiveConversation, createC2CConversation, activeConversation } = useConversationListState()
const userStore = useUserStore()
@@ -723,6 +749,8 @@ function setupCallRoomBinding() {
}
if (next === CALL_STATUS_CONNECTED) {
void tryBindRoomIfReady()
// 接通后重新下发已保存的美颜设置(摄像头此时已开启,插件可正常启动)
void beautyPanelRef.value?.applyIfEnabled()
clearLocalRecordingStartTimer()
if (!isLochostVodEnabled.value) {
previousCallStatus = next
@@ -1091,6 +1119,10 @@ const open = async (data: { patientId: number; patientName: string; diagnosisId?
showCallKitWindow.value = true
pendingCallRecordStart = ensureCallRecordStarted()
void pendingCallRecordStart
// 呼叫阶段本地摄像头预览已开启,延迟下发美颜让医生在预览中即可看到效果
setTimeout(() => {
void beautyPanelRef.value?.applyIfEnabled()
}, 1500)
},
// 须先 endCallDeleteCloudRecording),再 await 本地上传;否则上传 WebM 耗时数分钟会拖住云端停录,控制台房间长期「尚未结束」
afterCalling: () => {
+35 -1
View File
@@ -55,6 +55,20 @@
<!-- 窗口底部操作栏 -->
<div class="window-footer">
<!-- 美颜设置本地 AI 管线免费仅对开启了美颜功能的医生显示 -->
<CallBeautyPanel
v-if="isInitialized && !callRejected && beautyAllowed"
ref="beautyPanelRef"
:get-engine="getCallEngine"
:popper-z-index="2100"
>
<template #reference>
<el-button size="small">
<el-icon><MagicStick /></el-icon>
<span style="margin-left: 2px">美颜</span>
</el-button>
</template>
</CallBeautyPanel>
<el-button
@click="handleClose"
:disabled="calling && !callRejected"
@@ -92,11 +106,13 @@
<script setup lang="ts">
import { ref, onUnmounted, nextTick, computed } from 'vue'
import { VideoCamera, CircleClose, Phone } from '@element-plus/icons-vue'
import { VideoCamera, CircleClose, Phone, MagicStick } from '@element-plus/icons-vue'
import { getCallSignature, endCall } from '@/api/tcm'
import feedback from '@/utils/feedback'
import { formatTUICallUserError } from '@/utils/tuicall-error'
import { TUICallKitAPI, TUICallKit, TUICallType, STATUS } from '@trtc/calls-uikit-vue'
import CallBeautyPanel from '@/components/call-beauty-panel/index.vue'
import useUserStore from '@/stores/modules/user'
interface CallInfo {
diagnosisId: number
@@ -124,6 +140,18 @@ const callInfo = ref<CallInfo>({
isGroup: false
})
/** 美颜面板(本地 AI 管线 + 引擎兜底,非付费特效 SDK) */
const beautyPanelRef = ref<InstanceType<typeof CallBeautyPanel> | null>(null)
/** 医生编辑页「美颜功能」开关:关闭时不显示美颜入口 */
const beautyAllowed = computed(() => useUserStore().userInfo?.enable_beauty !== 0)
const getCallEngine = () => {
try {
return TUICallKitAPI.getTUICallEngineInstance()
} catch {
return null
}
}
// 窗口位置和大小
const windowRef = ref<HTMLElement>()
const windowPosition = ref({ x: 100, y: 100 })
@@ -233,6 +261,8 @@ const handleStatusChange = ({ oldStatus, newStatus }: any) => {
shouldAutoClose.value = false // 通话接通时,不自动关闭
statusText.value = '通话中...'
feedback.msgSuccess('通话已接通')
// 接通后重新下发已保存的美颜设置(摄像头此时已开启,插件可正常启动)
void beautyPanelRef.value?.applyIfEnabled()
// 如果是多人通话且还有其他用户需要邀请
if (callInfo.value.isGroup && callInfo.value.userIds && callInfo.value.userIds.length > 1) {
@@ -247,6 +277,10 @@ const handleStatusChange = ({ oldStatus, newStatus }: any) => {
calling.value = true
shouldAutoClose.value = false
statusText.value = '等待对方接听...'
// 呼叫阶段本地摄像头预览已开启,延迟下发美颜让医生在预览中即可看到效果
setTimeout(() => {
void beautyPanelRef.value?.applyIfEnabled()
}, 1500)
}
// 通话结束,回到空闲状态
@@ -0,0 +1,104 @@
/**
* getUserMedia 拦截器:给通话视频流透明注入本地 AI 高级美颜
*
* TUICallKit / TRTC SDK 申请摄像头时走 navigator.mediaDevices.getUserMedia
* 这里在其外层包一层:拿到真实摄像头流后,若高级美颜已开启,则把视频轨送入
* FaceBeautyPipeline 处理,返回处理后的流。SDK 对此无感知,无需改动任何 SDK 代码。
*
* 仅处理"摄像头视频"请求;纯音频、屏幕共享(getDisplayMedia)不受影响。
* 管线创建失败时始终回退原始流,不会因美颜故障导致通话不可用。
*/
import { hasAnyBeautyEffect, loadBeautySettings } from '@/utils/call-beauty'
import useUserStore from '@/stores/modules/user'
import {
FaceBeautyPipeline,
getFaceLandmarker,
isAdvancedBeautySupported
} from './face-beauty-pipeline'
/**
* 当前登录医生是否被允许使用美颜(la_admin.enable_beauty,医生编辑页配置)。
* 字段缺失(旧数据/旧接口)时视为允许,与后端默认值保持一致。
*/
export function isBeautyAllowedForCurrentUser(): boolean {
try {
const userStore = useUserStore()
return userStore.userInfo?.enable_beauty !== 0
} catch {
return true
}
}
let installed = false
let originalGetUserMedia: ((constraints?: MediaStreamConstraints) => Promise<MediaStream>) | null =
null
/**
* 绕过拦截器直接获取原始摄像头流。
* 供美颜预览使用:预览自己管理管线,避免被拦截器再包一层造成双重处理。
*/
export function getRawUserMedia(constraints?: MediaStreamConstraints): Promise<MediaStream> {
if (originalGetUserMedia) return originalGetUserMedia(constraints)
return navigator.mediaDevices.getUserMedia(constraints)
}
async function wrapStreamWithBeauty(stream: MediaStream): Promise<MediaStream> {
const videoTrack = stream.getVideoTracks()[0]
if (!videoTrack) return stream
const settings = loadBeautySettings()
const pipeline = new FaceBeautyPipeline(videoTrack, settings)
const processedTrack = await pipeline.start()
// SDK 停用处理后的轨道时,同步销毁管线并释放真实摄像头
const originalStop = processedTrack.stop.bind(processedTrack)
processedTrack.stop = () => {
originalStop()
pipeline.destroy()
stream.getTracks().forEach((t) => t.stop())
}
return new MediaStream([processedTrack, ...stream.getAudioTracks()])
}
/**
* 安装拦截器(幂等)。需在 SDK 发起摄像头请求前调用——
* 目前由美颜面板组件挂载时触发,早于任何通话开始。
*/
export function installBeautyMediaInterceptor() {
if (installed) return
if (!navigator.mediaDevices?.getUserMedia) return
installed = true
const original = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices)
originalGetUserMedia = original
navigator.mediaDevices.getUserMedia = async (constraints?: MediaStreamConstraints) => {
const stream = await original(constraints)
if (!constraints?.video) return stream
const settings = loadBeautySettings()
if (
!isBeautyAllowedForCurrentUser() ||
!hasAnyBeautyEffect(settings) ||
!isAdvancedBeautySupported()
) {
return stream
}
try {
return await wrapStreamWithBeauty(stream)
} catch (err) {
console.warn('[face-beauty] 美颜管线创建失败,使用原始摄像头画面', err)
return stream
}
}
}
/** 高级美颜开启时提前预热模型(约 4MB,加载一次全局复用),避免首帧卡顿 */
export function preloadBeautyModel() {
if (!isAdvancedBeautySupported()) return
getFaceLandmarker().catch(() => {
// 预热失败不打扰用户,实际使用时还会重试并有兜底
})
}
File diff suppressed because it is too large Load Diff
+191
View File
@@ -0,0 +1,191 @@
/**
* 视频通话美颜设置
*
* 全部效果(磨皮/美白/红润/祛斑/去痣/去黑眼圈/亮度/清晰度/瘦脸/口红/腮红)默认由本地管线处理:
* MediaPipe Face Landmarker(开源免费)识别人脸关键点 + WebGL/Canvas 逐帧渲染,
* 经 getUserMedia 拦截注入通话视频流(见 utils/beauty/ 目录),预览所见即所得。
*
* 仅当浏览器不支持 WebGL 时,磨皮/美白/红润回退到腾讯云 TRTC 免费 BasicBeauty
* 插件(TUICallEngine.setBeautyLevel)。不依赖付费的腾讯特效 SDK。
*/
/** 高级美颜(本地 AI 处理)设置 */
export interface AdvancedBeautySettings {
/** 总开关;通话中途开启需重新开关摄像头才能接管视频流 */
enabled: boolean
/** 瘦脸强度 0-100 */
slimStrength: number
/** 口红浓度 0-1000 为关闭) */
lipstickStrength: number
/** 口红颜色(hex */
lipstickColor: string
/** 腮红浓度 0-1000 为关闭) */
blushStrength: number
/** 腮红颜色(hex */
blushColor: string
}
export interface CallBeautySettings {
/** 是否启用基础美颜(关闭时以全 0 参数下发,引擎会停用 BasicBeauty 插件) */
enabled: boolean
/** 磨皮算法:0 光滑(TRTCBeautyStyleSmooth/ 1 自然(TRTCBeautyStyleNature */
style: number
/** 磨皮级别 0-9 */
beautyLevel: number
/** 美白级别 0-9 */
whitenessLevel: number
/** 红润级别 0-9 */
ruddinessLevel: number
/** 祛斑 / 淡化黑色素 0-9(仅人脸,压暗斑点向周围肤色靠拢) */
spotLevel: number
/** 去痣 0-9(仅人脸,强力淡化小面积深色痣点) */
moleLevel: number
/** 去黑眼圈 0-9(仅眼下区域提亮淡化) */
darkCircleLevel: number
/** 摄像头亮度 0-9(整帧提亮,0 为原亮度) */
brightnessLevel: number
/** 清晰度 0-9(锐化细节,0 为关闭) */
clarityLevel: number
/** 高级美颜(瘦脸/口红/腮红,本地 AI) */
advanced: AdvancedBeautySettings
}
export const BEAUTY_STYLE_SMOOTH = 0
export const BEAUTY_STYLE_NATURE = 1
export const BEAUTY_LEVEL_MAX = 9
const STORAGE_KEY = 'tcm_call_beauty_settings'
export const defaultAdvancedBeautySettings = (): AdvancedBeautySettings => ({
enabled: false,
slimStrength: 30,
lipstickStrength: 0,
lipstickColor: '#c94f5e',
blushStrength: 0,
blushColor: '#e88193'
})
export const defaultBeautySettings = (): CallBeautySettings => ({
enabled: false,
style: BEAUTY_STYLE_NATURE,
beautyLevel: 5,
whitenessLevel: 3,
ruddinessLevel: 2,
spotLevel: 0,
moleLevel: 0,
darkCircleLevel: 0,
brightnessLevel: 0,
clarityLevel: 0,
advanced: defaultAdvancedBeautySettings()
})
const clampLevel = (value: unknown): number => {
const num = Math.round(Number(value))
if (!Number.isFinite(num)) return 0
return Math.min(BEAUTY_LEVEL_MAX, Math.max(0, num))
}
const clampPercent = (value: unknown, fallback: number): number => {
const num = Math.round(Number(value))
if (!Number.isFinite(num)) return fallback
return Math.min(100, Math.max(0, num))
}
const normalizeHexColor = (value: unknown, fallback: string): string => {
if (typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)) return value
return fallback
}
/** 高级美颜是否有任一效果实际开启 */
export function hasAnyAdvancedEffect(advanced: AdvancedBeautySettings): boolean {
return (
advanced.enabled &&
(advanced.slimStrength > 0 || advanced.lipstickStrength > 0 || advanced.blushStrength > 0)
)
}
/** 全部美颜(基础 + 高级)是否有任一效果实际开启(决定是否接管摄像头流) */
export function hasAnyBeautyEffect(settings: CallBeautySettings): boolean {
const basicOn =
settings.enabled &&
(settings.beautyLevel > 0 ||
settings.whitenessLevel > 0 ||
settings.ruddinessLevel > 0 ||
settings.spotLevel > 0 ||
settings.moleLevel > 0 ||
settings.darkCircleLevel > 0 ||
settings.brightnessLevel > 0 ||
settings.clarityLevel > 0)
return basicOn || hasAnyAdvancedEffect(settings.advanced)
}
export function loadBeautySettings(): CallBeautySettings {
const defaults = defaultBeautySettings()
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return defaults
const parsed = JSON.parse(raw)
const advDefaults = defaults.advanced
const adv = parsed?.advanced ?? {}
return {
enabled: Boolean(parsed?.enabled),
style:
parsed?.style === BEAUTY_STYLE_SMOOTH
? BEAUTY_STYLE_SMOOTH
: BEAUTY_STYLE_NATURE,
beautyLevel: clampLevel(parsed?.beautyLevel ?? defaults.beautyLevel),
whitenessLevel: clampLevel(parsed?.whitenessLevel ?? defaults.whitenessLevel),
ruddinessLevel: clampLevel(parsed?.ruddinessLevel ?? defaults.ruddinessLevel),
spotLevel: clampLevel(parsed?.spotLevel ?? defaults.spotLevel),
moleLevel: clampLevel(parsed?.moleLevel ?? defaults.moleLevel),
darkCircleLevel: clampLevel(parsed?.darkCircleLevel ?? defaults.darkCircleLevel),
brightnessLevel: clampLevel(parsed?.brightnessLevel ?? defaults.brightnessLevel),
clarityLevel: clampLevel(parsed?.clarityLevel ?? defaults.clarityLevel),
advanced: {
enabled: Boolean(adv?.enabled),
slimStrength: clampPercent(adv?.slimStrength, advDefaults.slimStrength),
lipstickStrength: clampPercent(adv?.lipstickStrength, advDefaults.lipstickStrength),
lipstickColor: normalizeHexColor(adv?.lipstickColor, advDefaults.lipstickColor),
blushStrength: clampPercent(adv?.blushStrength, advDefaults.blushStrength),
blushColor: normalizeHexColor(adv?.blushColor, advDefaults.blushColor)
}
}
} catch {
return defaults
}
}
export function saveBeautySettings(settings: CallBeautySettings) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))
} catch {
// localStorage 不可用时静默忽略,仅本次会话生效
}
}
/**
* 将美颜设置应用到 TUICallEngine 实例。
* 引擎内部对 setBeautyLevel 自带 try-catch(摄像头未开启等场景不会抛错),
* 这里只兜底引擎不存在 / 接口缺失的情况。
*/
export async function applyBeautyToEngine(
engine: any,
settings: CallBeautySettings
): Promise<boolean> {
if (!engine || typeof engine.setBeautyLevel !== 'function') {
return false
}
const enabled = settings.enabled
try {
await engine.setBeautyLevel({
style: settings.style === BEAUTY_STYLE_SMOOTH ? BEAUTY_STYLE_SMOOTH : BEAUTY_STYLE_NATURE,
beautyLevel: enabled ? clampLevel(settings.beautyLevel) : 0,
whitenessLevel: enabled ? clampLevel(settings.whitenessLevel) : 0,
ruddinessLevel: enabled ? clampLevel(settings.ruddinessLevel) : 0
})
return true
} catch (error) {
console.warn('[call-beauty] 应用美颜设置失败', error)
return false
}
}
+15
View File
@@ -146,6 +146,19 @@
<span class="ml-2 text-gray-500">{{ formData.enable_charge === 1 ? '已开启' : '已关闭' }}</span>
</el-form-item>
<!-- 美颜功能 -->
<el-form-item label="美颜功能">
<div>
<el-switch
v-model="formData.enable_beauty"
:active-value="1"
:inactive-value="0"
/>
<span class="ml-2 text-gray-500">{{ formData.enable_beauty === 1 ? '已开启' : '已关闭' }}</span>
<div class="form-tips">开启后该医生视频通话时可配置美颜磨皮/美白/瘦脸/口红等关闭则不显示美颜入口</div>
</div>
</el-form-item>
<!-- 所属科室 -->
<el-form-item label="所属科室" prop="department">
<el-select v-model="formData.department" placeholder="请选择所属科室" clearable class="flex-1">
@@ -295,6 +308,7 @@ const formData = reactive({
enable_image_consult: 1, // 是否开启图文问诊
enable_video_consult: 1, // 是否开启视频问诊
enable_charge: 0, // 是否开启收费
enable_beauty: 1, // 是否开启美颜功能
department: '', // 所属科室
specialty: '', // 擅长领域
education: '', // 教育背景
@@ -436,6 +450,7 @@ const open = (type = 'add') => {
enable_image_consult: 1,
enable_video_consult: 1,
enable_charge: 0,
enable_beauty: 1,
department: '',
specialty: '',
education: '',
File diff suppressed because one or more lines are too long
@@ -86,6 +86,7 @@ class AdminLogic extends BaseLogic
'enable_image_consult' => $params['enable_image_consult'] ?? 1,
'enable_video_consult' => $params['enable_video_consult'] ?? 1,
'enable_charge' => $params['enable_charge'] ?? 0,
'enable_beauty' => $params['enable_beauty'] ?? 1,
]);
// 角色
@@ -151,6 +152,7 @@ class AdminLogic extends BaseLogic
'enable_image_consult' => $params['enable_image_consult'] ?? 1,
'enable_video_consult' => $params['enable_video_consult'] ?? 1,
'enable_charge' => $params['enable_charge'] ?? 0,
'enable_beauty' => $params['enable_beauty'] ?? 1,
];
// 头像
@@ -286,7 +288,7 @@ class AdminLogic extends BaseLogic
'gender', 'age', 'phone', 'title', 'department',
'specialty', 'education', 'experience', 'honors',
'license_no', 'qualification_images', 'enable_image_consult', 'enable_video_consult', 'enable_charge',
'work_wechat_userid'
'enable_beauty', 'work_wechat_userid'
])->findOrEmpty($params['id'])->toArray();
// 将资质图片JSON字符串转换为数组,供前端组件使用
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import t from"./error-D-UZdrhy.js";import{o,q as r,r as a,v as n,D as c,s}from"./.pnpm-BGZW0UGg.js";import"./index-CeIwrh_6.js";const p="/admin/assets/no_perms-jDxcYpYC.png",i={class:"error404"},x=o({__name:"403",setup(m){return(_,e)=>(r(),a("div",i,[n(t,{code:"403",title:"您的账号权限不足,请联系管理员添加权限!","show-btn":!1},{content:c(()=>[...e[0]||(e[0]=[s("div",{class:"flex justify-center"},[s("img",{class:"w-[150px] h-[150px]",src:p,alt:""})],-1)])]),_:1})]))}});export{x as default};
@@ -0,0 +1 @@
import r from"./error-BT3t0AXw.js";import{f as p,ak as i,I as m,a as e,aN as s,J as o}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-CXrrycLn.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-BAghejlR.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-CO3hhC4Q.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const a="/admin/assets/no_perms-jDxcYpYC.png",n={class:"error404"},W=p({__name:"403",setup(c){return(_,t)=>(i(),m("div",n,[e(r,{code:"403",title:"您的账号权限不足,请联系管理员添加权限!","show-btn":!1},{content:s(()=>[...t[0]||(t[0]=[o("div",{class:"flex justify-center"},[o("img",{class:"w-[150px] h-[150px]",src:a,alt:""})],-1)])]),_:1})]))}});export{W as default};
@@ -1 +0,0 @@
import e from"./error-D-UZdrhy.js";import{o,q as r,r as t,v as s}from"./.pnpm-BGZW0UGg.js";import"./index-CeIwrh_6.js";const a={class:"error404"},d=o({__name:"404",setup(c){return(n,_)=>(r(),t("div",a,[s(e,{code:"404",title:"哎呀,出错了!您访问的页面不存在…"})]))}});export{d as default};
@@ -0,0 +1 @@
import o from"./error-BT3t0AXw.js";import{f as r,ak as t,I as m,a as p}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-CXrrycLn.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-BAghejlR.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-CO3hhC4Q.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const i={class:"error404"},T=r({__name:"404",setup(e){return(a,s)=>(t(),m("div",i,[p(o,{code:"404",title:"哎呀,出错了!您访问的页面不存在…"})]))}});export{T as default};
@@ -0,0 +1 @@
function a(e){"@babel/helpers - typeof";return a=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},a(e)}function c(e,t,r,n,f,y,i){try{var u=e[y](i),o=u.value}catch(l){return void r(l)}u.done?t(o):Promise.resolve(o).then(n,f)}function p(e){return function(){var t=this,r=arguments;return new Promise(function(n,f){var y=e.apply(t,r);function i(o){c(y,n,f,i,u,"next",o)}function u(o){c(y,n,f,i,u,"throw",o)}i(void 0)})}}function b(e,t){if(a(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var n=r.call(e,t);if(a(n)!="object")return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function m(e){var t=b(e,"string");return a(t)=="symbol"?t:t+""}function s(e,t,r){return(t=m(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}export{a as _,p as a,s as b};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -0,0 +1 @@
import{H as u}from"../highlight.js-Bxt7hFFy.js";import{f as c,i as g,w as s,A as n}from"../@vue/runtime-core-C6bnekPw.js";import{n as h}from"../@vue/reactivity-DiY1c2vO.js";var i=c({props:{code:{type:String,required:!0},language:{type:String,default:""},autodetect:{type:Boolean,default:!0},ignoreIllegals:{type:Boolean,default:!0}},setup:function(e){var t=h(e.language);s((function(){return e.language}),(function(a){t.value=a}));var r=n((function(){return e.autodetect||!t.value})),o=n((function(){return!r.value&&!u.getLanguage(t.value)}));return{className:n((function(){return o.value?"":"hljs "+t.value})),highlightedCode:n((function(){var a;if(o.value)return console.warn('The language "'+t.value+'" you specified could not be found.'),e.code.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;");if(r.value){var l=u.highlightAuto(e.code);return t.value=(a=l.language)!==null&&a!==void 0?a:"",l.value}return(l=u.highlight(e.code,{language:t.value,ignoreIllegals:e.ignoreIllegals})).value}))}},render:function(){return g("pre",{},[g("code",{class:this.className,innerHTML:this.highlightedCode})])}}),v={install:function(e){e.component("highlightjs",i)},component:i};export{v as o};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import"./uikit-base-component-vue3-YgTqL4da.js";import{A as r}from"../tuikit-atomicx-vue3-Dln8Zi6e.js";import{f as s,ak as c,I as l,aq as m,G as u,at as i,H as p,A as d}from"../@vue/runtime-core-C6bnekPw.js";import{y as v}from"../@vue/reactivity-DiY1c2vO.js";import"./chat-uikit-engine-zx802ozq.js";const _=(t,o)=>{const a=t.__vccOpts||t;for(const[e,n]of o)a[e]=n;return a},f={key:0,class:"chat"},h=s({name:"Chat",__name:"Chat",props:{PlaceholderEmpty:{default:null}},setup(t){const{activeConversation:o}=r(),a=d(()=>{var e;return!((e=o.value)!=null&&e.conversationID)});return(e,n)=>v(o)?(c(),l("div",f,[m(e.$slots,"default",{},void 0,!0)])):a.value&&t.PlaceholderEmpty?(c(),u(i(t.PlaceholderEmpty),{key:1})):p("",!0)}}),A=_(h,[["__scopeId","data-v-1c9c77cd"]]);typeof window<"u"&&(window.__CHAT_ATOMICX_VUE3__={name:"@tencentcloud/chat-uikit-vue3",version:"4.5.4"},console.log("[@tencentcloud/chat-uikit-vue3] v4.5.4"));export{A as E};
@@ -0,0 +1 @@
.chat[data-v-1c9c77cd]{display:flex;flex-direction:column;min-width:0}.uikit-chat-header[data-v-a0c42ddc]{padding:14px 10px;height:64px;display:flex;justify-content:center;background-color:var(--bg-color-operate)}.uikit-chat-header__container[data-v-a0c42ddc]{padding:0 10px;flex-direction:row;align-items:center;justify-content:space-between}.uikit-chat-header__left[data-v-a0c42ddc]{flex:1 1 auto;display:flex;flex-direction:row;align-items:center}.uikit-chat-header__avatar[data-v-a0c42ddc]{margin-right:12px}.uikit-chat-header__info[data-v-a0c42ddc]{flex:1;display:flex;flex-direction:column;justify-content:center}.uikit-chat-header__title[data-v-a0c42ddc]{display:block;margin:0;font-size:16px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-color-primary)}.uikit-chat-header__typing-indicator[data-v-a0c42ddc]{font-size:12px;color:var(--text-color-secondary)}.uikit-chat-header__live[data-v-a0c42ddc]{margin-top:4px;font-size:12px;color:var(--text-color-secondary)}/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,*:after,*:before{box-sizing:border-box}ul,li{list-style:none;padding:0;margin:0}picture,img,video,canvas,svg{display:block;max-width:100%}img{max-width:100%;height:auto;vertical-align:middle;image-rendering:-webkit-optimize-contrast;aspect-ratio:attr(width)/attr(height);display:inline-block;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}img:not([src],[srcset]){visibility:hidden}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{C as A,F as C,a as w,L as h,q as D,J as R,H as g,u as k,n as W}from"../@vue/reactivity-DiY1c2vO.js";import{w as b,b as x,n as L,g as F,$ as M,a5 as P}from"../@vue/runtime-core-C6bnekPw.js";function J(e){return A()?(C(e),!0):!1}const d=new WeakMap,z=(...e)=>{var t;const r=e[0],n=(t=F())==null?void 0:t.proxy;if(n==null&&!M())throw new Error("injectLocal must be called in setup");return n&&d.has(n)&&r in d.get(n)?d.get(n)[r]:P(...e)},B=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const K=e=>typeof e<"u",I=Object.prototype.toString,Q=e=>I.call(e)==="[object Object]",m=()=>{};function j(e,t){function r(...n){return new Promise((a,o)=>{Promise.resolve(e(()=>t.apply(this,n),{fn:t,thisArg:this,args:n})).then(a).catch(o)})}return r}const S=e=>e();function V(...e){let t=0,r,n=!0,a=m,o,s,i,u,c;!w(e[0])&&typeof e[0]=="object"?{delay:s,trailing:i=!0,leading:u=!0,rejectOnCancel:c=!1}=e[0]:[s,i=!0,u=!0,c=!1]=e;const f=()=>{r&&(clearTimeout(r),r=void 0,a(),a=m)};return O=>{const l=h(s),v=Date.now()-t,p=()=>o=O();return f(),l<=0?(t=Date.now(),p()):(v>l&&(u||!n)?(t=Date.now(),p()):i&&(o=new Promise((y,T)=>{a=c?T:y,r=setTimeout(()=>{t=Date.now(),n=!0,y(p()),f()},Math.max(0,l-v))})),!u&&!r&&(r=setTimeout(()=>n=!0,l)),n=!1,o)}}function E(e=S,t={}){const{initialState:r="active"}=t,n=N(r==="active");function a(){n.value=!1}function o(){n.value=!0}const s=(...i)=>{n.value&&e(...i)};return{isActive:g(n),pause:a,resume:o,eventFilter:s}}function U(e){return e.endsWith("rem")?Number.parseFloat(e)*16:Number.parseFloat(e)}function G(e){return F()}function X(e){return Array.isArray(e)?e:[e]}function N(...e){if(e.length!==1)return R(...e);const t=e[0];return typeof t=="function"?g(k(()=>({get:t,set:m}))):W(t)}function Y(e,t=200,r=!1,n=!0,a=!1){return j(V(t,r,n,a),e)}function _(e,t,r={}){const{eventFilter:n=S,...a}=r;return b(e,j(n,t),a)}function Z(e,t,r={}){const{eventFilter:n,initialState:a="active",...o}=r,{eventFilter:s,pause:i,resume:u,isActive:c}=E(n,{initialState:a});return{stop:_(e,t,{...o,eventFilter:s}),pause:i,resume:u,isActive:c}}function ee(e,t=!0,r){G()?x(e,r):t?e():L(e)}function te(e=!1,t={}){const{truthyValue:r=!0,falsyValue:n=!1}=t,a=w(e),o=D(e);function s(i){if(arguments.length)return o.value=i,o.value;{const u=h(r);return o.value=o.value===u?h(n):u,o.value}}return a?s:[o,s]}function ne(e,t,r){return b(e,t,{...r,immediate:!0})}export{N as a,ee as b,Q as c,X as d,Z as e,z as f,K as g,Y as h,B as i,U as p,J as t,te as u,ne as w};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
import{i as C,Q as y,a as E}from"./editor-Cyf37SuL.js";import{ak as g,I as h,f as w,b as P,w as O,aJ as b}from"../@vue/runtime-core-C6bnekPw.js";import{n as d,t as $,q as F}from"../@vue/reactivity-DiY1c2vO.js";var B=Object.defineProperty,D=Object.defineProperties,j=Object.getOwnPropertyDescriptors,m=Object.getOwnPropertySymbols,H=Object.prototype.hasOwnProperty,S=Object.prototype.propertyIsEnumerable,_=(e,t,o)=>t in e?B(e,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[t]=o,A=(e,t)=>{for(var o in t||(t={}))H.call(t,o)&&_(e,o,t[o]);if(m)for(var o of m(t))S.call(t,o)&&_(e,o,t[o]);return e},M=(e,t)=>D(e,j(t));function u(e){let t=`请使用 '@${e}' 事件,不要放在 props 中`;return t+=`
Please use '@${e}' event instead of props`,t}var v=(e,t)=>{for(const[o,a]of t)e[o]=a;return e};const V=w({props:{mode:{type:String,default:"default"},defaultContent:{type:Array,default:[]},defaultHtml:{type:String,default:""},defaultConfig:{type:Object,default:{}},modelValue:{type:String,default:""}},setup(e,t){const o=d(null),a=F(null),i=d(""),s=()=>{if(!o.value)return;const f=$(e.defaultContent);C({selector:o.value,mode:e.mode,content:f||[],html:e.defaultHtml||e.modelValue||"",config:M(A({},e.defaultConfig),{onCreated(r){if(a.value=r,t.emit("onCreated",r),e.defaultConfig.onCreated){const n=u("onCreated");throw new Error(n)}},onChange(r){const n=r.getHtml();if(i.value=n,t.emit("update:modelValue",n),t.emit("onChange",r),e.defaultConfig.onChange){const l=u("onChange");throw new Error(l)}},onDestroyed(r){if(t.emit("onDestroyed",r),e.defaultConfig.onDestroyed){const n=u("onDestroyed");throw new Error(n)}},onMaxLength(r){if(t.emit("onMaxLength",r),e.defaultConfig.onMaxLength){const n=u("onMaxLength");throw new Error(n)}},onFocus(r){if(t.emit("onFocus",r),e.defaultConfig.onFocus){const n=u("onFocus");throw new Error(n)}},onBlur(r){if(t.emit("onBlur",r),e.defaultConfig.onBlur){const n=u("onBlur");throw new Error(n)}},customAlert(r,n){if(t.emit("customAlert",r,n),e.defaultConfig.customAlert){const l=u("customAlert");throw new Error(l)}},customPaste:(r,n)=>{if(e.defaultConfig.customPaste){const c=u("customPaste");throw new Error(c)}let l;return t.emit("customPaste",r,n,c=>{l=c}),l}})})};function p(f){const r=a.value;r!=null&&r.setHtml(f)}return P(()=>{s()}),O(()=>e.modelValue,f=>{f!==i.value&&p(f)}),{box:o}}}),I={ref:"box",style:{height:"100%"}};function L(e,t,o,a,i,s){return g(),h("div",I,null,512)}var J=v(V,[["render",L]]);const T=w({props:{editor:{type:Object},mode:{type:String,default:"default"},defaultConfig:{type:Object,default:{}}},setup(e){const t=d(null),o=a=>{if(t.value){if(a==null)throw new Error("Not found instance of Editor when create <Toolbar/> component");y.getToolbar(a)||E({editor:a,selector:t.value||"<div></div>",mode:e.mode,config:e.defaultConfig})}};return b(()=>{const{editor:a}=e;a!=null&&o(a)}),{selector:t}}}),R={ref:"selector"};function k(e,t,o,a,i,s){return g(),h("div",R,null,512)}var N=v(T,[["render",k]]);export{J as E,N as T};
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
.cell-stack[data-v-7005d187]{display:flex;flex-direction:column;gap:2px;line-height:1.35}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{o as C,R as D,q as d,r as I,ac as N,O as u,bj as B,D as o,v as l,bk as E,br as L,L as i,T as r,s as p,bi as R,M as w}from"./.pnpm-BGZW0UGg.js";import{a as V}from"./doctor-CF92xvl4.js";import{m as A,_ as M}from"./index-CeIwrh_6.js";const P={class:"appointment-record-panel"},$={class:"cell-stack"},j={class:"font-medium"},q={class:"text-gray-500 text-sm"},O={class:"cell-stack"},F={class:"text-gray-500 text-sm"},G=C({__name:"AppointmentRecordPanel",props:{diagnosisId:{}},setup(v,{expose:y}){const f=v,m=w(!1),g=w([]),h=w({});function x(t){return t?String(t).length>=8?String(t).slice(0,5):t:""}function k(t){return t==="morning"?"上午":t==="afternoon"?"下午":t==="all"?"全天":t||"—"}function S(t){const n=t.channel_source??t.channels??"";if(n===""||n===null||n===void 0)return"—";const a=String(n),s=h.value[a]||a,c=String(t.channel_source_detail??"").trim();return c!==""?`${s}${c}`:s}function T(t){return[...t].sort((n,a)=>{const s=String(n.appointment_date||""),c=String(a.appointment_date||"");if(s!==c)return c.localeCompare(s);const _=String(n.appointment_time||""),e=String(a.appointment_time||"");return _!==e?e.localeCompare(_):Number(a.id||0)-Number(n.id||0)})}const z=async()=>{try{const t=await A({type:"channels"}),n=((t==null?void 0:t.channels)||[]).filter(s=>s.status!==0),a={};for(const s of n)s.value!=null&&(a[String(s.value)]=s.name||String(s.value));h.value=a}catch{h.value={}}},b=async()=>{if(f.diagnosisId){m.value=!0;try{await z();const t=await V({patient_id:f.diagnosisId,diag_scope_relax:1,page_no:1,page_size:500}),n=(t==null?void 0:t.lists)||[];g.value=T(n)}catch(t){console.error(t),g.value=[]}finally{m.value=!1}}};return D(()=>f.diagnosisId,()=>{b()},{immediate:!0}),y({refresh:b}),(t,n)=>{const a=E,s=L,c=B,_=R;return d(),I("div",P,[N((d(),u(c,{data:g.value,border:"",stripe:"","empty-text":"暂无挂号记录"},{default:o(()=>[l(a,{label:"ID",prop:"id",width:"72",align:"center"}),l(a,{label:"状态",width:"100",align:"center"},{default:o(({row:e})=>[l(s,{type:e.status===1?"success":e.status===2?"info":e.status===3?"primary":"danger",size:"small",effect:"light"},{default:o(()=>[i(r(e.status_desc||"—"),1)]),_:2},1032,["type"])]),_:1}),l(a,{label:"患者(挂号人)","min-width":"150"},{default:o(({row:e})=>[p("div",$,[p("span",j,r(e.patient_name||"—"),1),p("span",q,r(e.patient_phone||""),1)])]),_:1}),l(a,{label:"挂号医生",prop:"doctor_name",width:"110","show-overflow-tooltip":""}),l(a,{label:"挂号助理",width:"110","show-overflow-tooltip":""},{default:o(({row:e})=>[i(r(e.assistant_name||"—"),1)]),_:1}),l(a,{label:"预约时间","min-width":"130"},{default:o(({row:e})=>[p("div",O,[p("span",null,r(e.appointment_date||"—"),1),p("span",F,r(x(e.appointment_time)),1)])]),_:1}),l(a,{label:"时段",width:"80",align:"center"},{default:o(({row:e})=>[i(r(k(e.period)),1)]),_:1}),l(a,{label:"类型",width:"100","show-overflow-tooltip":""},{default:o(({row:e})=>[i(r(e.appointment_type_desc||"—"),1)]),_:1}),l(a,{label:"渠道",width:"110","show-overflow-tooltip":""},{default:o(({row:e})=>[i(r(S(e)),1)]),_:1}),l(a,{label:"确认诊单",width:"92",align:"center"},{default:o(({row:e})=>[e.diagnosis_confirmed?(d(),u(s,{key:0,type:"success",size:"small",effect:"plain"},{default:o(()=>[...n[0]||(n[0]=[i("已确认",-1)])]),_:1})):(d(),u(s,{key:1,type:"warning",size:"small",effect:"plain"},{default:o(()=>[...n[1]||(n[1]=[i("未确认",-1)])]),_:1}))]),_:1}),l(a,{label:"开方",width:"80",align:"center"},{default:o(({row:e})=>[e.has_prescription?(d(),u(s,{key:0,type:"success",size:"small",effect:"plain"},{default:o(()=>[...n[2]||(n[2]=[i("已开方",-1)])]),_:1})):(d(),u(s,{key:1,type:"info",size:"small",effect:"plain"},{default:o(()=>[...n[3]||(n[3]=[i("未开方",-1)])]),_:1}))]),_:1}),l(a,{label:"备注",prop:"remark","min-width":"100","show-overflow-tooltip":""}),l(a,{label:"创建时间",width:"165",prop:"create_time"})]),_:1},8,["data"])),[[_,m.value]])])}}}),Q=M(G,[["__scopeId","data-v-7005d187"]]);export{Q as default};
@@ -0,0 +1 @@
.cell-stack[data-v-4de87dfa]{display:flex;flex-direction:column;gap:2px;line-height:1.35}
@@ -1 +0,0 @@
import{o as T,R as C,q as _,r as b,ac as D,O as h,bj as $,D as r,v as n,bk as L,L as c,T as d,br as k,s as E,a1 as A,w as P,u as B,ch as F,bi as M,M as w}from"./.pnpm-BGZW0UGg.js";import{a6 as V}from"./tcm-o2aZele6.js";import{_ as q}from"./index-CeIwrh_6.js";const K={class:"assign-log-panel"},j={key:1,class:"text-gray-400"},z=T({__name:"AssignLogPanel",props:{diagnosisId:{}},setup(v,{expose:y}){const u=v,m=w(!1),p=w([]);function N(a){const e=a.related_po_creator_name;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(a.related_po_creator_id);return Number.isFinite(t)&&t>0?`ID:${t}`:"—"}function x(a){const e=a.related_po_create_time_text;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(a.related_po_create_time);if(!Number.isFinite(t)||t<=0)return"—";const s=new Date(t*1e3);if(Number.isNaN(s.getTime()))return"—";const o=l=>String(l).padStart(2,"0");return`${s.getFullYear()}-${o(s.getMonth()+1)}-${o(s.getDate())} ${o(s.getHours())}:${o(s.getMinutes())}:${o(s.getSeconds())}`}function f(a,e){const t=e==="from"?"from_assistant_name":"to_assistant_name",s=e==="from"?"from_assistant_id":"to_assistant_id",o=a[t];if(o!=null&&String(o).trim()!==""&&String(o)!=="—")return String(o);const l=Number(a[s]);return Number.isFinite(l)&&l>0?`ID:${l}`:"—"}const g=async()=>{if(u.diagnosisId){m.value=!0;try{const a=await V({id:u.diagnosisId}),e=Array.isArray(a)?a:[];p.value=e}catch(a){console.error(a),p.value=[]}finally{m.value=!1}}};return C(()=>u.diagnosisId,()=>{g()},{immediate:!0}),y({refresh:g}),(a,e)=>{const t=L,s=k,o=P,l=A,S=$,I=M;return _(),b("div",K,[D((_(),h(S,{data:p.value,border:"",stripe:"","empty-text":"暂无指派记录"},{default:r(()=>[n(t,{label:"操作时间",width:"175",prop:"create_time_text"}),n(t,{label:"原医助","min-width":"120"},{default:r(({row:i})=>[c(d(f(i,"from")),1)]),_:1}),n(t,{label:"新医助","min-width":"120"},{default:r(({row:i})=>[c(d(f(i,"to")),1)]),_:1}),n(t,{label:"继承",width:"72",align:"center"},{default:r(({row:i})=>[Number(i.is_inherit)===1?(_(),h(s,{key:0,type:"success",size:"small"},{default:r(()=>[...e[0]||(e[0]=[c("是",-1)])]),_:1})):(_(),b("span",j,"否"))]),_:1}),n(t,{label:"快照·业务单创建人","min-width":"130","show-overflow-tooltip":""},{default:r(({row:i})=>[c(d(N(i)),1)]),_:1}),n(t,{label:"快照·业务单创建时间",width:"190"},{header:r(()=>[e[1]||(e[1]=E("span",null,"快照·业务单创建时间",-1)),n(l,{placement:"top",content:"related_po_create_time:触发本次操作的处方业务订单 create_time;与原文助一致时表示医助创建订单时间"},{default:r(()=>[n(o,{class:"assign-log-col-hint"},{default:r(()=>[n(B(F))]),_:1})]),_:1})]),default:r(({row:i})=>[c(d(x(i)),1)]),_:1}),n(t,{label:"操作人",width:"110",prop:"operator_name"}),n(t,{label:"操作账号",width:"120",prop:"operator_account","show-overflow-tooltip":""}),n(t,{label:"IP",width:"130",prop:"ip","show-overflow-tooltip":""})]),_:1},8,["data"])),[[I,m.value]])])}}}),Y=q(z,[["__scopeId","data-v-0a09e3d2"]]);export{Y as default};
@@ -1 +0,0 @@
.assign-log-col-hint[data-v-0a09e3d2]{margin-left:4px;vertical-align:middle;color:var(--el-text-color-secondary);cursor:help}
@@ -0,0 +1 @@
.assign-log-col-hint[data-v-f670e3e6]{margin-left:4px;vertical-align:middle;color:var(--el-text-color-secondary);cursor:help}
@@ -0,0 +1 @@
import{M as T,N as C,T as $,r as D,d as L,L as k}from"./element-plus-CXrrycLn.js";import{Y as E}from"./@element-plus/icons-vue-BAghejlR.js";import{a6 as P}from"./tcm-ujpoSvdA.js";import{f as A,w as B,ak as p,I as b,aP as F,G as h,aN as n,a as i,O as m,J as M}from"./@vue/runtime-core-C6bnekPw.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{y as V,n as w}from"./@vue/reactivity-DiY1c2vO.js";import{_ as K}from"./index-CO3hhC4Q.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const Y={class:"assign-log-panel"},q={key:1,class:"text-gray-400"},z=A({__name:"AssignLogPanel",props:{diagnosisId:{}},setup(N,{expose:v}){const _=N,d=w(!1),u=w([]);function y(o){const e=o.related_po_creator_name;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_creator_id);return Number.isFinite(t)&&t>0?`ID:${t}`:"—"}function x(o){const e=o.related_po_create_time_text;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_create_time);if(!Number.isFinite(t)||t<=0)return"—";const a=new Date(t*1e3);if(Number.isNaN(a.getTime()))return"—";const r=l=>String(l).padStart(2,"0");return`${a.getFullYear()}-${r(a.getMonth()+1)}-${r(a.getDate())} ${r(a.getHours())}:${r(a.getMinutes())}:${r(a.getSeconds())}`}function f(o,e){const t=e==="from"?"from_assistant_name":"to_assistant_name",a=e==="from"?"from_assistant_id":"to_assistant_id",r=o[t];if(r!=null&&String(r).trim()!==""&&String(r)!=="—")return String(r);const l=Number(o[a]);return Number.isFinite(l)&&l>0?`ID:${l}`:"—"}const g=async()=>{if(_.diagnosisId){d.value=!0;try{const o=await P({id:_.diagnosisId}),e=Array.isArray(o)?o:[];u.value=e}catch(o){console.error(o),u.value=[]}finally{d.value=!1}}};return B(()=>_.diagnosisId,()=>{g()},{immediate:!0}),v({refresh:g}),(o,e)=>{const t=C,a=$,r=L,l=D,S=T,I=k;return p(),b("div",Y,[F((p(),h(S,{data:u.value,border:"",stripe:"","empty-text":"暂无指派记录"},{default:n(()=>[i(t,{label:"操作时间",width:"175",prop:"create_time_text"}),i(t,{label:"原医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"from")),1)]),_:1}),i(t,{label:"新医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"to")),1)]),_:1}),i(t,{label:"继承",width:"72",align:"center"},{default:n(({row:s})=>[Number(s.is_inherit)===1?(p(),h(a,{key:0,type:"success",size:"small"},{default:n(()=>[...e[0]||(e[0]=[m("是",-1)])]),_:1})):(p(),b("span",q,"否"))]),_:1}),i(t,{label:"快照·业务单创建人","min-width":"130","show-overflow-tooltip":""},{default:n(({row:s})=>[m(c(y(s)),1)]),_:1}),i(t,{label:"快照·业务单创建时间",width:"190"},{header:n(()=>[e[1]||(e[1]=M("span",null,"快照·业务单创建时间",-1)),i(l,{placement:"top",content:"related_po_create_time:触发本次操作的处方业务订单 create_time;与原文助一致时表示医助创建订单时间"},{default:n(()=>[i(r,{class:"assign-log-col-hint"},{default:n(()=>[i(V(E))]),_:1})]),_:1})]),default:n(({row:s})=>[m(c(x(s)),1)]),_:1}),i(t,{label:"操作人",width:"110",prop:"operator_name"}),i(t,{label:"操作账号",width:"120",prop:"operator_account","show-overflow-tooltip":""}),i(t,{label:"IP",width:"130",prop:"ip","show-overflow-tooltip":""})]),_:1},8,["data"])),[[I,d.value]])])}}}),Ct=K(z,[["__scopeId","data-v-f670e3e6"]]);export{Ct as default};
@@ -0,0 +1 @@
.watch-state[data-v-7aff665d]{min-height:200px;display:flex;align-items:center;justify-content:center;color:var(--el-text-color-secondary);font-size:14px}.watch-error[data-v-7aff665d]{color:var(--el-color-danger)}.watch-grid[data-v-7aff665d]{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;min-height:220px}.watch-tile[data-v-7aff665d]{background:#0f0f0f;border-radius:8px;overflow:hidden;aspect-ratio:16 / 10;display:flex;flex-direction:column}.watch-tile-cap[data-v-7aff665d]{padding:6px 10px;font-size:12px;color:#e5e5e5;background:#0000008c}.watch-tile-view[data-v-7aff665d]{flex:1;min-height:0;position:relative}.watch-hint[data-v-7aff665d]{float:left;line-height:32px;font-size:12px;color:var(--el-text-color-secondary)}
@@ -1 +0,0 @@
.watch-state[data-v-8c719418]{min-height:200px;display:flex;align-items:center;justify-content:center;color:var(--el-text-color-secondary);font-size:14px}.watch-error[data-v-8c719418]{color:var(--el-color-danger)}.watch-grid[data-v-8c719418]{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;min-height:220px}.watch-tile[data-v-8c719418]{background:#0f0f0f;border-radius:8px;overflow:hidden;aspect-ratio:16 / 10;display:flex;flex-direction:column}.watch-tile-cap[data-v-8c719418]{padding:6px 10px;font-size:12px;color:#e5e5e5;background:#0000008c}.watch-tile-view[data-v-8c719418]{flex:1;min-height:0;position:relative}.watch-hint[data-v-8c719418]{float:left;line-height:32px;font-size:12px;color:var(--el-text-color-secondary)}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{o as B,R as D,q as E,O,D as I,r as _,T as S,P as x,ac as W,s as h,ad as P,v as U,K as $,L as j,bt as H,p as K,M as v,cW as d}from"./.pnpm-BGZW0UGg.js";import{a7 as Y}from"./tcm-o2aZele6.js";import{_ as q}from"./index-CeIwrh_6.js";const z={key:0,class:"watch-state"},F={key:1,class:"watch-state watch-error"},G=B({__name:"AssistantWatchCallDialog",props:{modelValue:{type:Boolean},diagnosisId:{}},emits:["update:modelValue","closed"],setup(R,{emit:A}){const u=R,g=A,y=K({get:()=>u.modelValue,set:t=>g("update:modelValue",t)}),r=v(null),l=v(!1),i=v(""),p=v("旁观视频通话"),n=new Map;let a=null,f=0;function w(t,e){return`${t}\0${String(e)}`}function N(t){return t.startsWith("patient_")?"患者":t.startsWith("doctor_")?"医护":t}async function T(t){if(!a||!r.value||t.streamType!==d.TYPE.STREAM_TYPE_MAIN)return;const e=w(t.userId,t.streamType);if(n.has(e))return;const o=document.createElement("div");o.className="watch-tile";const s=document.createElement("div");s.className="watch-tile-cap",s.textContent=N(t.userId);const c=document.createElement("div");c.className="watch-tile-view",o.appendChild(s),o.appendChild(c),r.value.appendChild(o),n.set(e,{wrap:o,userId:t.userId,streamType:t.streamType});try{await a.startRemoteVideo({userId:t.userId,streamType:t.streamType,view:c})}catch(b){console.warn("[AssistantWatchCall] startRemoteVideo",b)}}async function V(t){if(!a)return;const e=w(t.userId,t.streamType),o=n.get(e);if(o){try{await a.stopRemoteVideo({userId:t.userId,streamType:t.streamType})}catch{}o.wrap.remove(),n.delete(e)}}function C(){a&&(a.on(d.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.on(d.EVENT.REMOTE_VIDEO_UNAVAILABLE,V))}function k(){a&&(a.off(d.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.off(d.EVENT.REMOTE_VIDEO_UNAVAILABLE,V))}async function m(){if(k(),a){for(const[,t]of n){try{await a.stopRemoteVideo({userId:t.userId,streamType:t.streamType})}catch{}t.wrap.remove()}n.clear(),r.value&&(r.value.innerHTML="");try{await a.exitRoom()}catch{}try{a.destroy()}catch{}a=null}else n.clear(),r.value&&(r.value.innerHTML="")}async function L(){const t=++f;if(await m(),!u.diagnosisId){i.value="诊单无效";return}l.value=!0,i.value="",p.value="旁观视频通话";try{const e=await Y({diagnosis_id:u.diagnosisId});if(t!==f)return;e.patientName&&(p.value=`旁观视频通话 · ${e.patientName}`),a=d.create(),C();const o={sdkAppId:e.sdkAppId,userId:e.userId,userSig:e.userSig,autoReceiveAudio:!0,autoReceiveVideo:!0,...e.roomId!=null&&e.roomId>0?{roomId:e.roomId}:{strRoomId:e.strRoomId}};if(!(e.roomId!=null&&e.roomId>0)&&!e.strRoomId)throw new Error("缺少房间号");if(await a.enterRoom(o),t!==f){await m();return}l.value=!1}catch(e){l.value=!1;let o="进入房间失败";if(typeof e=="string")o=e;else if(e&&typeof e=="object"){const s=e;s.msg?o=String(s.msg):s.message&&(o=String(s.message))}i.value=o,await m()}}function M(){m(),l.value=!1,i.value="",p.value="旁观视频通话",g("closed")}return D(()=>[u.modelValue,u.diagnosisId],([t,e])=>{if(!t){f++,m();return}e>0&&L()}),(t,e)=>{const o=$,s=H;return E(),O(s,{modelValue:y.value,"onUpdate:modelValue":e[1]||(e[1]=c=>y.value=c),title:p.value,width:"760px","destroy-on-close":"","append-to-body":"","close-on-click-modal":!1,class:"assistant-watch-call-dialog",onClosed:M},{footer:I(()=>[e[3]||(e[3]=h("span",{class:"watch-hint"},"仅观看,不会开启摄像头与麦克风",-1)),U(o,{type:"primary",onClick:e[0]||(e[0]=c=>y.value=!1)},{default:I(()=>[...e[2]||(e[2]=[j("离开",-1)])]),_:1})]),default:I(()=>[l.value?(E(),_("div",z,"正在连接房间…")):i.value?(E(),_("div",F,S(i.value),1)):x("",!0),W(h("div",{ref_key:"gridRef",ref:r,class:"watch-grid"},null,512),[[P,!l.value&&!i.value]])]),_:1},8,["modelValue","title"])}}}),Z=q(G,[["__scopeId","data-v-8c719418"]]);export{Z as default};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.blood-record-list[data-v-3a187401]{padding:20px}
@@ -1 +0,0 @@
.blood-record-list[data-v-002163f9]{padding:20px}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{o as N,cY as O,R as P,q as d,r as f,v as o,D as i,K as V,L as n,P as h,ac as D,O as w,bj as L,bk as z,T as u,s as y,bi as M,M as v}from"./.pnpm-BGZW0UGg.js";import j from"./RecordingPlaybackBlock-By4EH6-v.js";import{U as x}from"./index-TD4t951S.js";import{i as c,_ as q}from"./index-CeIwrh_6.js";import{ab as K,ac as k,ad as Y}from"./tcm-o2aZele6.js";import"./RecordingVideoPlayer-CF68w3pY.js";import"./file-tQw_m7Aj.js";const A={class:"call-record-panel"},F={key:0,class:"call-record-toolbar"},G={class:"call-record-empty"},H={class:"call-record-empty__desc"},J={key:0,class:"text-primary"},Q={key:1,class:"text-gray-400"},W=N({__name:"CallRecordPanel",props:{diagnosisId:{},readOnly:{type:Boolean,default:!1}},setup(_,{expose:R}){const r=_,p=v(!1),g=v([]),U=O("toolbarUploadRef"),m=async()=>{if(r.diagnosisId){p.value=!0;try{g.value=await K({diagnosis_id:r.diagnosisId})||[]}catch(e){console.error(e),g.value=[]}finally{p.value=!1}}};P(()=>r.diagnosisId,()=>{m()},{immediate:!0}),R({refresh:m});function C(e){return{1:"进行中",2:"已结束",3:"未接听",4:"已取消"}[e]??"—"}async function S(e){const t=b(e);if(!t){c.msgError("上传成功但未返回视频地址");return}try{const a=await Y({diagnosis_id:r.diagnosisId});await k({diagnosis_id:r.diagnosisId,call_record_id:Number((a==null?void 0:a.id)||0),file_url:t}),c.msgSuccess("视频回放上传成功"),await m()}catch(a){c.msgError((a==null?void 0:a.message)||"写入回放失败")}}async function E(e,t){const a=b(t);if(!a){c.msgError("上传成功但未返回视频地址");return}try{await k({diagnosis_id:r.diagnosisId,call_record_id:Number(e.id||0),file_url:a}),c.msgSuccess("视频回放上传成功"),await m()}catch(l){c.msgError((l==null?void 0:l.message)||"写入回放失败")}}function b(e){var t,a;return String(((t=e==null?void 0:e.data)==null?void 0:t.uri)||((a=e==null?void 0:e.data)==null?void 0:a.url)||"").trim()}return(e,t)=>{const a=V,l=z,I=L,B=M;return d(),f("div",A,[_.readOnly?h("",!0):(d(),f("div",F,[o(x,{ref_key:"toolbarUploadRef",ref:U,type:"video",direct:"",multiple:!1,limit:1,"show-progress":!0,onSuccess:S},{default:i(()=>[o(a,{type:"primary"},{default:i(()=>[...t[0]||(t[0]=[n("上传视频",-1)])]),_:1})]),_:1},512)])),D((d(),w(I,{data:g.value,border:"",stripe:""},{empty:i(()=>[y("div",G,[t[1]||(t[1]=y("div",{class:"call-record-empty__title"},"暂无通话记录",-1)),y("div",H,u(_.readOnly?"暂无录制回放数据。":"现在可以直接点击上方“上传视频”。系统会自动生成一条默认通话记录来承载回放。"),1)])]),default:i(()=>[o(l,{label:"录制回放","min-width":"320"},{default:i(({row:s})=>[o(j,{"record-id":s.id,urls:s.recording_urls_list},null,8,["record-id","urls"])]),_:1}),o(l,{label:"开始时间",width:"170",prop:"start_time_text"}),o(l,{label:"结束时间",width:"170",prop:"end_time_text"}),o(l,{label:"通话类型",width:"100"},{default:i(({row:s})=>[n(u(s.call_type===1?"语音":"视频"),1)]),_:1}),o(l,{label:"房间号",width:"180"},{default:i(({row:s})=>[s.room_id?(d(),f("span",J,u(s.room_id),1)):(d(),f("span",Q,"—"))]),_:1}),o(l,{label:"时长",width:"110",prop:"duration_text"}),o(l,{label:"状态",width:"90"},{default:i(({row:s})=>[n(u(C(s.status)),1)]),_:1}),o(l,{label:"录制",width:"100"},{default:i(({row:s})=>[n(u(s.recording_status_text||"—"),1)]),_:1}),_.readOnly?h("",!0):(d(),w(l,{key:0,label:"上传回放",width:"180"},{default:i(({row:s})=>[o(x,{type:"video",direct:"",multiple:!1,limit:1,"show-progress":!0,onSuccess:T=>E(s,T)},{default:i(()=>[o(a,{type:"primary",plain:"",size:"small"},{default:i(()=>[...t[2]||(t[2]=[n("上传视频",-1)])]),_:1})]),_:1},8,["onSuccess"])]),_:1}))]),_:1},8,["data"])),[[B,p.value]])])}}}),sa=q(W,[["__scopeId","data-v-41737096"]]);export{sa as default};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.call-record-panel .call-record-toolbar[data-v-78d5c9e4]{display:flex;align-items:center;gap:12px;margin-bottom:12px}.call-record-panel .call-record-empty[data-v-78d5c9e4]{padding:28px 12px;color:var(--el-text-color-secondary);text-align:center}.call-record-panel .call-record-empty__title[data-v-78d5c9e4]{font-size:14px;color:var(--el-text-color-primary)}.call-record-panel .call-record-empty__desc[data-v-78d5c9e4]{margin-top:8px;font-size:12px;line-height:1.6}
@@ -1 +0,0 @@
.call-record-panel .call-record-toolbar[data-v-41737096]{display:flex;align-items:center;gap:12px;margin-bottom:12px}.call-record-panel .call-record-empty[data-v-41737096]{padding:28px 12px;color:var(--el-text-color-secondary);text-align:center}.call-record-panel .call-record-empty__title[data-v-41737096]{font-size:14px;color:var(--el-text-color-primary)}.call-record-panel .call-record-empty__desc[data-v-41737096]{margin-top:8px;font-size:12px;line-height:1.6}
@@ -0,0 +1 @@
import{i as L,L as T,N as V,T as D,M as z,W as M}from"./element-plus-CXrrycLn.js";import{ae as O}from"./tcm-ujpoSvdA.js";import{f as P,b as F,w as j,ak as a,I as n,a as i,aN as s,O as m,H as w,aP as A,G as g,F as H,J as R}from"./@vue/runtime-core-C6bnekPw.js";import{y as d,n as k}from"./@vue/reactivity-DiY1c2vO.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{_ as G}from"./index-CO3hhC4Q.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@element-plus/icons-vue-BAghejlR.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const J={class:"case-record-list"},Q={key:0,class:"mb-3 flex justify-end"},W={key:0},Y={key:1,class:"text-gray-400"},q={class:"void-detail text-xs text-gray-500 mt-1"},K=P({__name:"CaseRecordList",props:{diagnosisId:{type:Number,default:0},readOnly:{type:Boolean,default:!1}},emits:["view","openPrescription"],setup(y,{expose:x,emit:C}){const _=y,h=C,l=k([]),p=k(!1),u=async()=>{if(_.diagnosisId){p.value=!0;try{const e=await O({diagnosis_id:_.diagnosisId});l.value=Array.isArray(e)?e:[]}catch(e){console.error("获取病历记录失败:",e),l.value=[]}finally{p.value=!1}}},S=e=>{if(!e)return"";const t=new Date(e*1e3);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`},$=e=>{h("view",e)},B=()=>{h("openPrescription")};return F(()=>{u()}),j(()=>_.diagnosisId,()=>{u()}),x({refresh:u}),(e,t)=>{const b=L,r=V,v=D,E=z,N=M,I=T;return a(),n("div",J,[y.readOnly?w("",!0):(a(),n("div",Q,[i(b,{type:"primary",size:"small",onClick:B},{default:s(()=>[...t[0]||(t[0]=[m("开方",-1)])]),_:1})])),A((a(),g(E,{data:d(l),border:""},{default:s(()=>[i(r,{prop:"prescription_date",label:"就诊日期",width:"120"}),i(r,{prop:"visit_no",label:"门诊号",width:"120"}),i(r,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),i(r,{label:"处方摘要","min-width":"180"},{default:s(({row:o})=>[o.herbs&&o.herbs.length?(a(),n("span",W,c(o.herbs.slice(0,3).map(f=>`${f.name}${f.dosage}`).join("、"))+c(o.herbs.length>3?"...":""),1)):(a(),n("span",Y,"—"))]),_:1}),i(r,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),i(r,{label:"状态",width:"140",align:"center"},{default:s(({row:o})=>[o.void_status===1?(a(),n(H,{key:0},[i(v,{type:"danger",size:"small"},{default:s(()=>[...t[1]||(t[1]=[m("已作废",-1)])]),_:1}),R("div",q,c(o.void_by_name||"—")+" "+c(S(o.void_time)),1)],64)):(a(),g(v,{key:1,type:"success",size:"small"},{default:s(()=>[...t[2]||(t[2]=[m("正常",-1)])]),_:1}))]),_:1}),i(r,{label:"操作",width:"120",fixed:"right"},{default:s(({row:o})=>[i(b,{link:"",type:"primary",size:"small",onClick:f=>$(o)},{default:s(()=>[...t[3]||(t[3]=[m(" 查看 ",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[I,d(p)]]),!d(p)&&d(l).length===0?(a(),g(N,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):w("",!0)])}}}),zt=G(K,[["__scopeId","data-v-043d2738"]]);export{zt as default};
@@ -1 +0,0 @@
import{o as T,ap as V,R as I,q as o,r as l,v as s,K as N,D as i,L as c,P as k,ac as z,bi as M,u as p,O as g,bk as O,T as _,F as P,br as j,s as F,bj as R,bB as A,M as w}from"./.pnpm-BGZW0UGg.js";import{ae as q}from"./tcm-o2aZele6.js";import{_ as H}from"./index-CeIwrh_6.js";const K={class:"case-record-list"},Y={key:0,class:"mb-3 flex justify-end"},G={key:0},J={key:1,class:"text-gray-400"},Q={class:"void-detail text-xs text-gray-500 mt-1"},U=T({__name:"CaseRecordList",props:{diagnosisId:{type:Number,default:0},readOnly:{type:Boolean,default:!1}},emits:["view","openPrescription"],setup(y,{expose:x,emit:C}){const m=y,b=C,r=w([]),d=w(!1),u=async()=>{if(m.diagnosisId){d.value=!0;try{const t=await q({diagnosis_id:m.diagnosisId});r.value=Array.isArray(t)?t:[]}catch(t){console.error("获取病历记录失败:",t),r.value=[]}finally{d.value=!1}}},S=t=>{if(!t)return"";const e=new Date(t*1e3);return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")} ${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`},B=t=>{b("view",t)},$=()=>{b("openPrescription")};return V(()=>{u()}),I(()=>m.diagnosisId,()=>{u()}),x({refresh:u}),(t,e)=>{const h=N,n=O,v=j,E=R,D=A,L=M;return o(),l("div",K,[y.readOnly?k("",!0):(o(),l("div",Y,[s(h,{type:"primary",size:"small",onClick:$},{default:i(()=>[...e[0]||(e[0]=[c("开方",-1)])]),_:1})])),z((o(),g(E,{data:p(r),border:""},{default:i(()=>[s(n,{prop:"prescription_date",label:"就诊日期",width:"120"}),s(n,{prop:"visit_no",label:"门诊号",width:"120"}),s(n,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),s(n,{label:"处方摘要","min-width":"180"},{default:i(({row:a})=>[a.herbs&&a.herbs.length?(o(),l("span",G,_(a.herbs.slice(0,3).map(f=>`${f.name}${f.dosage}`).join("、"))+_(a.herbs.length>3?"...":""),1)):(o(),l("span",J,"—"))]),_:1}),s(n,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),s(n,{label:"状态",width:"140",align:"center"},{default:i(({row:a})=>[a.void_status===1?(o(),l(P,{key:0},[s(v,{type:"danger",size:"small"},{default:i(()=>[...e[1]||(e[1]=[c("已作废",-1)])]),_:1}),F("div",Q,_(a.void_by_name||"—")+" "+_(S(a.void_time)),1)],64)):(o(),g(v,{key:1,type:"success",size:"small"},{default:i(()=>[...e[2]||(e[2]=[c("正常",-1)])]),_:1}))]),_:1}),s(n,{label:"操作",width:"120",fixed:"right"},{default:i(({row:a})=>[s(h,{link:"",type:"primary",size:"small",onClick:f=>B(a)},{default:i(()=>[...e[3]||(e[3]=[c(" 查看 ",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[L,p(d)]]),!p(d)&&p(r).length===0?(o(),g(D,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):k("",!0)])}}}),ee=H(U,[["__scopeId","data-v-da9a20f3"]]);export{ee as default};
@@ -0,0 +1 @@
.case-record-list[data-v-043d2738]{padding:20px}
@@ -1 +0,0 @@
.case-record-list[data-v-da9a20f3]{padding:20px}
@@ -1 +0,0 @@
.daily-matrix[data-v-a5368e74]{padding:16px}.daily-matrix__toolbar[data-v-a5368e74]{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:12px}.daily-matrix__toolbar-left[data-v-a5368e74],.daily-matrix__toolbar-right[data-v-a5368e74]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.daily-matrix__table[data-v-a5368e74],.daily-matrix__table-wrap[data-v-a5368e74]{width:100%}.daily-matrix__chart[data-v-a5368e74]{margin-top:16px;padding:16px 18px;border:1px solid var(--el-border-color-lighter);border-radius:10px;background:linear-gradient(180deg,#fff,#f8fafc)}.daily-matrix__chart-canvas[data-v-a5368e74]{height:280px;width:100%}.daily-matrix__cell[data-v-a5368e74]{min-height:32px;display:inline-flex;align-items:center;justify-content:center;gap:2px;width:100%;color:var(--el-text-color-regular)}.daily-matrix__cell.is-clickable[data-v-a5368e74]{cursor:pointer}.daily-matrix__cell.is-empty[data-v-a5368e74]{color:var(--el-text-color-placeholder)}.daily-matrix__cell.is-high[data-v-a5368e74]{color:#dc2626;font-weight:700}.daily-matrix__cell.is-patient-self[data-v-a5368e74]{position:relative;background:linear-gradient(180deg,#8b5cf60a,#8b5cf61a);border-radius:4px}.daily-matrix__cell-up[data-v-a5368e74]{color:#dc2626;font-size:13px}.daily-matrix__cell-patient[data-v-a5368e74]{display:inline-block;margin-left:4px;padding:1px 6px;font-size:11px;font-weight:600;color:#6d28d9;background:#ede9fe;border:1px solid #ddd6fe;border-radius:999px;line-height:1.2;letter-spacing:.5px;white-space:nowrap}.daily-matrix__legend[data-v-a5368e74]{display:inline-flex;align-items:center;gap:6px;margin-right:12px;padding:2px 10px 2px 6px;background:#f8f6ff;border:1px dashed #ddd6fe;border-radius:999px}.daily-matrix__legend-text[data-v-a5368e74]{font-size:12px;color:#6d28d9;font-weight:500}.daily-matrix__todo[data-v-a5368e74]{margin-top:16px}.daily-matrix__section-title[data-v-a5368e74]{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--el-text-color-primary)}.daily-matrix__tracking-existing[data-v-a5368e74]{width:100%;max-height:180px;overflow:auto;padding:8px 10px;border:1px solid var(--el-border-color);border-radius:6px;background:var(--el-fill-color-light)}.daily-matrix__tracking-line[data-v-a5368e74]{font-size:12.5px;line-height:1.6;color:var(--el-text-color-regular);word-break:break-word}.daily-matrix__tracking-preview[data-v-a5368e74]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;width:100%;text-align:left}.daily-matrix__tracking-tooltip[data-v-a5368e74]{max-width:320px}.daily-matrix__tracking-tooltip-line[data-v-a5368e74]{font-size:12.5px;line-height:1.6;word-break:break-word}@media (max-width: 768px){.daily-matrix[data-v-a5368e74],.daily-matrix__chart[data-v-a5368e74]{padding:12px}.daily-matrix__chart-canvas[data-v-a5368e74]{height:240px}}
@@ -0,0 +1 @@
.daily-matrix[data-v-4d566204]{padding:16px}.daily-matrix__toolbar[data-v-4d566204]{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:12px}.daily-matrix__toolbar-left[data-v-4d566204],.daily-matrix__toolbar-right[data-v-4d566204]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.daily-matrix__table[data-v-4d566204],.daily-matrix__table-wrap[data-v-4d566204]{width:100%}.daily-matrix__chart[data-v-4d566204]{margin-top:16px;padding:16px 18px;border:1px solid var(--el-border-color-lighter);border-radius:10px;background:linear-gradient(180deg,#fff,#f8fafc)}.daily-matrix__chart-canvas[data-v-4d566204]{height:280px;width:100%}.daily-matrix__cell[data-v-4d566204]{min-height:32px;display:inline-flex;align-items:center;justify-content:center;gap:2px;width:100%;color:var(--el-text-color-regular)}.daily-matrix__cell.is-clickable[data-v-4d566204]{cursor:pointer}.daily-matrix__cell.is-empty[data-v-4d566204]{color:var(--el-text-color-placeholder)}.daily-matrix__cell.is-high[data-v-4d566204]{color:#dc2626;font-weight:700}.daily-matrix__cell.is-patient-self[data-v-4d566204]{position:relative;background:linear-gradient(180deg,#8b5cf60a,#8b5cf61a);border-radius:4px}.daily-matrix__cell-up[data-v-4d566204]{color:#dc2626;font-size:13px}.daily-matrix__cell-patient[data-v-4d566204]{display:inline-block;margin-left:4px;padding:1px 6px;font-size:11px;font-weight:600;color:#6d28d9;background:#ede9fe;border:1px solid #ddd6fe;border-radius:999px;line-height:1.2;letter-spacing:.5px;white-space:nowrap}.daily-matrix__legend[data-v-4d566204]{display:inline-flex;align-items:center;gap:6px;margin-right:12px;padding:2px 10px 2px 6px;background:#f8f6ff;border:1px dashed #ddd6fe;border-radius:999px}.daily-matrix__legend-text[data-v-4d566204]{font-size:12px;color:#6d28d9;font-weight:500}.daily-matrix__todo[data-v-4d566204]{margin-top:16px}.daily-matrix__section-title[data-v-4d566204]{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--el-text-color-primary)}.daily-matrix__tracking-existing[data-v-4d566204]{width:100%;max-height:180px;overflow:auto;padding:8px 10px;border:1px solid var(--el-border-color);border-radius:6px;background:var(--el-fill-color-light)}.daily-matrix__tracking-line[data-v-4d566204]{font-size:12.5px;line-height:1.6;color:var(--el-text-color-regular);word-break:break-word}.daily-matrix__tracking-preview[data-v-4d566204]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;width:100%;text-align:left}.daily-matrix__tracking-tooltip[data-v-4d566204]{max-width:320px}.daily-matrix__tracking-tooltip-line[data-v-4d566204]{font-size:12.5px;line-height:1.6;word-break:break-word}@media(max-width:768px){.daily-matrix[data-v-4d566204],.daily-matrix__chart[data-v-4d566204]{padding:12px}.daily-matrix__chart-canvas[data-v-4d566204]{height:240px}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.diagnosis-todo-list .toolbar[data-v-c136d72f]{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:12px}.diagnosis-todo-list .todo-table[data-v-c136d72f]{width:100%}.diagnosis-todo-list .pagination-wrap[data-v-c136d72f]{margin-top:12px;display:flex;justify-content:flex-end}.diagnosis-todo-list .text-danger[data-v-c136d72f]{color:var(--el-color-danger)}.diagnosis-todo-list .text-muted[data-v-c136d72f]{color:var(--el-text-color-placeholder)}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
.diagnosis-todo-list .toolbar[data-v-e71c3261]{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:12px}.diagnosis-todo-list .todo-table[data-v-e71c3261]{width:100%}.diagnosis-todo-list .pagination-wrap[data-v-e71c3261]{margin-top:12px;display:flex;justify-content:flex-end}.diagnosis-todo-list .text-danger[data-v-e71c3261]{color:var(--el-color-danger)}.diagnosis-todo-list .text-muted[data-v-e71c3261]{color:var(--el-text-color-placeholder)}
@@ -1 +0,0 @@
.diet-record-list[data-v-2ef076d1]{padding:20px}
@@ -0,0 +1 @@
.diet-record-list[data-v-7c66f363]{padding:20px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.exercise-record-list[data-v-ab6f0ec7]{padding:20px}
@@ -1 +0,0 @@
.exercise-record-list[data-v-6a7f7e37]{padding:20px}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
.im-chat-record-panel[data-v-e57593a0]{min-height:200px}.panel-tip[data-v-e57593a0]{margin:0;line-height:1.55;font-size:13px}.panel-tip code[data-v-e57593a0]{font-size:12px}.toolbar[data-v-e57593a0]{display:flex;align-items:center}.chat-wrap[data-v-e57593a0]{min-height:120px}.chat-list[data-v-e57593a0]{display:flex;flex-direction:column;gap:16px;max-height:min(60vh,520px);overflow-y:auto;padding:4px 8px 12px}.chat-row[data-v-e57593a0]{display:flex;flex-direction:column;max-width:88%}.chat-row.from-patient[data-v-e57593a0]{align-self:flex-start}.chat-row.from-patient .bubble[data-v-e57593a0]{background:var(--el-fill-color-light);border:1px solid var(--el-border-color-lighter)}.chat-row.from-doctor[data-v-e57593a0]{align-self:flex-end;align-items:flex-end}.chat-row.from-doctor .meta[data-v-e57593a0]{flex-direction:row-reverse}.chat-row.from-doctor .bubble[data-v-e57593a0]{background:var(--el-color-primary-light-9);border:1px solid var(--el-color-primary-light-7)}.meta[data-v-e57593a0]{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--el-text-color-secondary);margin-bottom:6px}.meta .name[data-v-e57593a0]{font-weight:500;color:var(--el-text-color-regular)}.bubble[data-v-e57593a0]{border-radius:8px;padding:10px 12px;word-break:break-word;font-size:14px;line-height:1.5}.text-content[data-v-e57593a0]{white-space:pre-wrap}.chat-img[data-v-e57593a0]{max-width:240px;max-height:200px;border-radius:4px}.muted[data-v-e57593a0]{color:var(--el-text-color-placeholder)}.friendly-text .friendly-main[data-v-e57593a0]{font-size:14px;line-height:1.5;color:var(--el-text-color-primary)}.friendly-text .friendly-sub[data-v-e57593a0]{margin-top:6px;font-size:12px;line-height:1.4;color:var(--el-text-color-secondary)}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{o as F,R as j,q as t,r as n,v as i,D as c,s as o,L as _,bh as q,w as G,u as x,cZ as H,K as $,by as J,ac as K,F as E,G as O,O as f,bB as W,P as v,bi as Z,M as m,p as Q,ae as U,T as r,br as X,aa as ee,bq as ae,a8 as se,E as C}from"./.pnpm-BGZW0UGg.js";import{d as te}from"./dayjs-BnultS00.js";import{aj as ne,ak as oe}from"./tcm-o2aZele6.js";import{p as re}from"./im-business-message-parse-_PY5JHD3.js";import{_ as le}from"./index-CeIwrh_6.js";const ie={class:"im-chat-record-panel"},ce={class:"toolbar mb-3"},de={class:"chat-wrap"},_e={key:0,class:"chat-list"},ue={class:"meta"},fe={class:"name"},me={class:"time"},pe={class:"bubble"},ye={key:0,class:"friendly-text"},ge={class:"friendly-main"},ve={key:0,class:"friendly-sub"},he={key:1,class:"text-content"},we={key:2,class:"text-content"},ke={key:3,class:"muted"},be=F({__name:"ImChatRecordPanel",props:{diagnosisId:{}},setup(B,{expose:M}){const u=B,d=m(!1),p=m(!1),y=se([]),L=m(""),g=m(""),D={text:"",image:"图片",file:"文件",sound:"语音",video:"视频",location:"位置",custom:"自定义",face:"表情",other:""},h=Q(()=>y.value.map(e=>{const s=P(e);let l="";return s!=null&&s.tag?l=s.tag:l=D[e.msg_type]||(e.msg_type!=="other"?e.msg_type:""),{raw:e,friendly:s,tag:l}}));function N(e){if(e==null||!e)return"—";const s=e>1e12?Math.floor(e/1e3):e;return te.unix(s).format("YYYY-MM-DD HH:mm:ss")}function P(e){const s=(e.text||"").trim();if(!s)return null;const l=s.startsWith("{")&&(/\bbusinessID\b/.test(s)||/\bcmd\b/.test(s));return e.msg_type==="custom"||l?re(s):null}function R(e){return e.is_from_doctor?e.from_staff_name?`医生(${e.from_staff_name}`:"医生/员工":g.value?`患者(${g.value}`:"患者"}async function w(){if(u.diagnosisId){d.value=!0;try{const e=await ne({diagnosis_id:u.diagnosisId,only_archived:1});y.value=(e==null?void 0:e.lists)||[],L.value=(e==null?void 0:e.patient_im_id)||"",g.value=(e==null?void 0:e.patient_name)||""}catch(e){console.error(e),y.value=[]}finally{d.value=!1}}}function k(){w()}async function S(){if(u.diagnosisId){p.value=!0;try{await oe({diagnosis_id:u.diagnosisId}),C.success('已发起后台同步,几秒后请点击"重新加载已归档"查看新消息')}catch(e){console.error(e),C.error("发起同步失败")}finally{p.value=!1}}}return j(()=>u.diagnosisId,()=>{w()},{immediate:!0}),M({refresh:k}),(e,s)=>{const l=q,b=G,I=$,T=X,Y=ee,V=ae,z=W,A=Z;return t(),n("div",ie,[i(l,{type:"info","show-icon":"",closable:!1,class:"mb-4",title:"说明"},{default:c(()=>[...s[0]||(s[0]=[o("p",{class:"panel-tip"},[_(" 展示单聊记录:已合并患者 与 "),o("strong",null,"所有医生 / 医助账号"),_("分别产生的会话,按时间排序。 数据由后台定时任务从腾讯云 IM 漫游消息同步至本地归档;点击下方按钮可立即触发后台同步。 ")],-1)])]),_:1}),o("div",ce,[i(I,{type:"primary",link:"",loading:p.value,onClick:S},{default:c(()=>[i(b,{class:"mr-1"},{default:c(()=>[i(x(H))]),_:1}),s[1]||(s[1]=_(" 同步最新(后台异步) ",-1))]),_:1},8,["loading"]),i(I,{type:"primary",link:"",loading:d.value,onClick:k},{default:c(()=>[i(b,{class:"mr-1"},{default:c(()=>[i(x(J))]),_:1}),s[2]||(s[2]=_(" 重新加载已归档 ",-1))]),_:1},8,["loading"])]),K((t(),n("div",de,[!d.value&&h.value.length?(t(),n("div",_e,[(t(!0),n(E,null,O(h.value,a=>(t(),n("div",{key:a.raw.msg_id,class:U(["chat-row",a.raw.is_from_doctor?"from-doctor":"from-patient"])},[o("div",ue,[o("span",fe,r(R(a.raw)),1),o("span",me,r(N(a.raw.time)),1),a.tag?(t(),f(T,{key:0,size:"small",type:"info",class:"ml-2"},{default:c(()=>[_(r(a.tag),1)]),_:2},1024)):v("",!0)]),o("div",pe,[a.raw.msg_type==="image"&&a.raw.image_url?(t(),f(Y,{key:0,src:a.raw.image_url,"preview-src-list":[a.raw.image_url],fit:"contain",class:"chat-img"},null,8,["src","preview-src-list"])):(a.raw.msg_type==="file"||a.raw.msg_type==="sound"||a.raw.msg_type==="video")&&a.raw.file_url?(t(),f(V,{key:1,href:a.raw.file_url,target:"_blank",type:"primary"},{default:c(()=>[_(r(a.raw.file_name||"打开文件"),1)]),_:2},1032,["href"])):(t(),n(E,{key:2},[a.friendly?(t(),n("div",ye,[o("div",ge,r(a.friendly.main),1),a.friendly.sub?(t(),n("div",ve,r(a.friendly.sub),1)):v("",!0)])):a.raw.msg_type==="text"&&a.raw.text?(t(),n("div",he,r(a.raw.text),1)):a.raw.text?(t(),n("div",we,r(a.raw.text),1)):(t(),n("span",ke,"(无法展示该消息类型)"))],64))])],2))),128))])):d.value?v("",!0):(t(),f(z,{key:1,description:"暂无 IM 聊天记录"}))])),[[A,d.value]])])}}}),Me=le(be,[["__scopeId","data-v-e57593a0"]]);export{Me as default};
@@ -0,0 +1 @@
.im-chat-record-panel[data-v-58b699dd]{min-height:200px}.panel-tip[data-v-58b699dd]{margin:0;line-height:1.55;font-size:13px}.panel-tip code[data-v-58b699dd]{font-size:12px}.toolbar[data-v-58b699dd]{display:flex;align-items:center}.chat-wrap[data-v-58b699dd]{min-height:120px}.chat-list[data-v-58b699dd]{display:flex;flex-direction:column;gap:16px;max-height:min(60vh,520px);overflow-y:auto;padding:4px 8px 12px}.chat-row[data-v-58b699dd]{display:flex;flex-direction:column;max-width:88%}.chat-row.from-patient[data-v-58b699dd]{align-self:flex-start}.chat-row.from-patient .bubble[data-v-58b699dd]{background:var(--el-fill-color-light);border:1px solid var(--el-border-color-lighter)}.chat-row.from-doctor[data-v-58b699dd]{align-self:flex-end;align-items:flex-end}.chat-row.from-doctor .meta[data-v-58b699dd]{flex-direction:row-reverse}.chat-row.from-doctor .bubble[data-v-58b699dd]{background:var(--el-color-primary-light-9);border:1px solid var(--el-color-primary-light-7)}.meta[data-v-58b699dd]{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--el-text-color-secondary);margin-bottom:6px}.meta .name[data-v-58b699dd]{font-weight:500;color:var(--el-text-color-regular)}.bubble[data-v-58b699dd]{border-radius:8px;padding:10px 12px;word-break:break-word;font-size:14px;line-height:1.5}.text-content[data-v-58b699dd]{white-space:pre-wrap}.chat-img[data-v-58b699dd]{max-width:240px;max-height:200px;border-radius:4px}.muted[data-v-58b699dd]{color:var(--el-text-color-placeholder)}.friendly-text .friendly-main[data-v-58b699dd]{font-size:14px;line-height:1.5;color:var(--el-text-color-primary)}.friendly-text .friendly-sub[data-v-58b699dd]{margin-top:6px;font-size:12px;line-height:1.4;color:var(--el-text-color-secondary)}

Some files were not shown because too many files have changed in this diff Show More