Compare commits

...
4 Commits
Author SHA1 Message Date
Your Name 928f72ec3d 更新 2026-09-03 16:03:41 +08:00
Your Name b4c11881b4 更新 2026-09-01 18:12:39 +08:00
Your Name 58ffde808f 更新 2026-09-01 17:49:29 +08:00
Your Name 486acc465d 更新 2026-09-01 17:18:42 +08:00
379 changed files with 2813 additions and 764 deletions
+5 -2
View File
@@ -100,6 +100,7 @@ export interface OssCredentialsResponse {
host?: string
cdn_domain?: string
key_prefix?: string
object_key?: string
max_size?: number
duration?: number
expired_time?: number
@@ -111,8 +112,10 @@ export interface OssCredentialsResponse {
}
}
export type OssDirectUploadType = 'video' | 'voice' | 'desktop_package'
/** 申请 STS 临时凭证 */
export function getOssCredentials(params: { type: 'video' }) {
export function getOssCredentials(params: { type: OssDirectUploadType; name?: string }) {
return request.post({
url: '/upload/ossCredentials',
params
@@ -121,7 +124,7 @@ export function getOssCredentials(params: { type: 'video' }) {
/** 直传完成回执:写 file 表 + HEAD 校验 */
export function confirmOssUpload(params: {
type: 'video'
type: OssDirectUploadType
key: string
name: string
size: number
+36
View File
@@ -293,6 +293,42 @@ export function wecomPromotionBatchSetOperators(params: WecomPromotionBatchSetOp
})
}
export interface WecomPromotionBatchUpdatePoolsParams {
pool_ids: number[]
changes: {
skip_verify?: 0 | 1
fallback_url?: string
status?: 0 | 1
automation_config?: Record<string, unknown>
}
}
export interface WecomPromotionBatchUpdatePoolResult {
id: number
name: string
success: boolean
sync_error?: string
sync_queued?: boolean
error?: string
}
export interface WecomPromotionBatchUpdatePoolsResult {
pool_ids: number[]
updated: number
failed: number
sync_error_count: number
sync_queued_count: number
results: WecomPromotionBatchUpdatePoolResult[]
}
export function wecomPromotionBatchUpdatePools(params: WecomPromotionBatchUpdatePoolsParams) {
return request.post<WecomPromotionBatchUpdatePoolsResult>({
url: '/firstvisit.wecomPromotion/batchUpdatePools',
params,
timeout: 120000
}, { ignoreCancelToken: true })
}
export function wecomPromotionDeletePool(params: { id: number }) {
return request.post({ url: '/firstvisit.wecomPromotion/deletePool', params, timeout: 120000 })
}
+5
View File
@@ -5,6 +5,11 @@ export function qywxCustomerLists(params: any) {
return request.get({ url: '/qywx.customer/lists', params })
}
// 删除一条本地企业微信客户同步记录
export function qywxCustomerDelete(params: { id: number }) {
return request.post({ url: '/qywx.customer/delete', params })
}
// 同步企业微信客户
export function qywxCustomerSync() {
return request.post({ url: '/qywx.customer/sync' })
+23 -8
View File
@@ -51,8 +51,10 @@ import useAppStore from '@/stores/modules/app'
import useUserStore from '@/stores/modules/user'
import feedback from '@/utils/feedback'
import {
DirectUploadApiError,
DirectUploadFallbackError,
uploadVideoDirectToCos
uploadDirectToCos,
type DirectUploadType
} from '@/utils/oss-direct-upload'
export default defineComponent({
@@ -83,7 +85,7 @@ export default defineComponent({
type: Boolean,
default: false
},
// 视频直传到 OSS绕开服务器中转,仅 type=video 生效)
// 直传到对象存储,绕开服务器中转
direct: {
type: Boolean,
default: false
@@ -102,8 +104,10 @@ export default defineComponent({
const visible = ref(false)
const fileList = ref<any[]>([])
// 仅 video/voice + direct 时才接管 http-request
const useDirect = computed(() => props.direct && ['video', 'voice'].includes(props.type))
const directTypes: DirectUploadType[] = ['video', 'voice', 'desktop_package']
const useDirect = computed(
() => props.direct && directTypes.includes(props.type as DirectUploadType)
)
const handleProgress = () => {
visible.value = true
@@ -131,7 +135,10 @@ export default defineComponent({
fileList.value = []
emit('allSuccess')
}
feedback.msgError(`${file.name}文件上传失败`)
if (!(event instanceof DirectUploadApiError)) {
const message = event instanceof Error ? event.message : ''
feedback.msgError(message || `${file.name}文件上传失败`)
}
uploadRefs.value?.abort(file)
visible.value = false
emit('change', file)
@@ -153,18 +160,20 @@ export default defineComponent({
return '.wmv,.avi,.mpg,.mpeg,.3gp,.mov,.mp4,.flv,.rmvb,.mkv'
case 'voice':
return '.mp3,.wav,.wma,.m4a,.aac,.amr'
case 'desktop_package':
return '.exe,.zip'
default:
return '*'
}
})
// 走 COS 直传:成功时模拟老接口的响应 envelope,失败/降级时回到默认 XHR
// 走 COS 直传:成功时模拟老接口的响应 envelope
const httpRequest = async (options: UploadRequestOptions) => {
visible.value = true
try {
const data = await uploadVideoDirectToCos({
const data = await uploadDirectToCos({
file: options.file,
type: props.type as any,
type: props.type as DirectUploadType,
cid: Number((options.data as any)?.cid ?? 0),
onProgress(info) {
// 触发 ElUpload 内部进度(保持与默认上传一致的体验)
@@ -178,6 +187,12 @@ export default defineComponent({
;(options as any).onSuccess?.({ code: RequestCodeEnum.SUCCESS, msg: 'ok', data })
} catch (err: any) {
if (err instanceof DirectUploadFallbackError) {
if (props.type === 'desktop_package') {
;(options as any).onError?.(
new Error('当前未启用腾讯云 COS,安装包无法直传,请配置 COS 后重试')
)
return
}
feedback.msgWarning('当前存储不支持直传,已切换为普通上传')
await defaultXhrUpload(options)
return
+43 -15
View File
@@ -3,10 +3,11 @@ import COS from 'cos-js-sdk-v5'
import {
confirmOssUpload,
getOssCredentials,
type OssCredentialsResponse
type OssCredentialsResponse,
type OssDirectUploadType
} from '@/api/file'
export type DirectUploadType = 'video'
export type DirectUploadType = OssDirectUploadType
export interface DirectUploadProgress {
/** 0-100 */
@@ -37,8 +38,17 @@ export interface DirectUploadOptions {
const SLICE_SIZE = 5 * 1024 * 1024 // 5MB
const ASYNC_LIMIT = 3
async function callDirectUploadApi<T>(request: () => Promise<T>): Promise<T> {
try {
return await request()
} catch (error) {
// request 拦截器已经展示过接口/网络错误,上传组件只负责收口失败状态
throw new DirectUploadApiError(error)
}
}
function buildKey(prefix: string, file: File): string {
const ext = (file.name.split('.').pop() || 'mp4').toLowerCase()
const ext = (file.name.split('.').pop() || 'bin').toLowerCase()
const ts = Date.now()
const rand = Math.random().toString(36).slice(2, 10)
return `${prefix}${ts}-${rand}.${ext}`
@@ -48,8 +58,13 @@ function buildKey(prefix: string, file: File): string {
* 直传到腾讯云 COS(含 STS 凭证申请、分片上传、回执)
* 不支持降级 / fallback=true 时抛错,由调用方决定走老链路。
*/
export async function uploadVideoDirectToCos(options: DirectUploadOptions): Promise<DirectUploadResult> {
const credentials: OssCredentialsResponse = await getOssCredentials({ type: options.type })
export async function uploadDirectToCos(options: DirectUploadOptions): Promise<DirectUploadResult> {
const credentials: OssCredentialsResponse = await callDirectUploadApi(() =>
getOssCredentials({
type: options.type,
name: options.file.name
})
)
if (credentials.fallback) {
const handled = options.onFallback?.(credentials.provider) ?? false
@@ -66,7 +81,7 @@ export async function uploadVideoDirectToCos(options: DirectUploadOptions): Prom
if (credentials.max_size && options.file.size > credentials.max_size) {
const mb = Math.round(credentials.max_size / 1024 / 1024)
throw new Error(`视频体积超出上限(${mb}MB`)
throw new Error(`文件体积超出上限(${mb}MB`)
}
const cred = credentials.credentials
@@ -85,7 +100,7 @@ export async function uploadVideoDirectToCos(options: DirectUploadOptions): Prom
}
})
const key = buildKey(credentials.key_prefix, options.file)
const key = credentials.object_key || buildKey(credentials.key_prefix, options.file)
await new Promise<void>((resolve, reject) => {
cos.uploadFile(
@@ -117,14 +132,16 @@ export async function uploadVideoDirectToCos(options: DirectUploadOptions): Prom
)
})
const confirmed = await confirmOssUpload({
type: options.type,
key,
name: options.file.name,
size: options.file.size,
content_type: options.file.type || '',
cid: options.cid ?? 0
})
const confirmed = await callDirectUploadApi(() =>
confirmOssUpload({
type: options.type,
key,
name: options.file.name,
size: options.file.size,
content_type: options.file.type || '',
cid: options.cid ?? 0
})
)
options.onProgress?.({ percent: 100, loaded: options.file.size, total: options.file.size, speed: 0 })
@@ -140,3 +157,14 @@ export class DirectUploadFallbackError extends Error {
this.provider = provider
}
}
/** 请求层已经展示过错误,避免 ElUpload 再弹一条通用失败提示。 */
export class DirectUploadApiError extends Error {
readonly originalError: unknown
constructor(error: unknown) {
super('')
this.name = 'DirectUploadApiError'
this.originalError = error
}
}
+299 -21
View File
@@ -94,15 +94,32 @@
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item label="跟进人">
<el-input
v-model="queryParams.follow_user"
<el-form-item label="跟进人">
<el-input
v-model="queryParams.follow_user"
placeholder="跟进人姓名(后台姓名或企微账号)"
clearable
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item label="添加时间">
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item label="渠道">
<el-select
v-model="queryParams.add_way"
clearable
filterable
placeholder="选择或搜索添加渠道"
style="width: 240px"
@change="resetPage"
>
<el-option
v-for="option in ADD_WAY_OPTIONS"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item label="添加时间">
<el-date-picker
v-model="addTimeRange"
type="daterange"
@@ -235,6 +252,32 @@
<span v-else class="text-gray-400"></span>
</template>
</el-table-column>
<el-table-column label="添加渠道" min-width="160">
<template #default="{ row }">
<div v-if="customerAddSources(row).length" class="flex items-center gap-1">
<el-tooltip
v-for="source in customerAddSources(row).slice(0, 1)"
:key="source.key"
:content="addSourceTooltip(source)"
placement="top"
>
<span class="inline-block max-w-[150px] truncate align-middle">
{{ source.label }}
</span>
</el-tooltip>
<el-tooltip
v-if="customerAddSources(row).length > 1"
:content="remainingAddSourcesTooltip(row)"
placement="top"
>
<span class="text-primary whitespace-nowrap cursor-help">
{{ customerAddSources(row).length - 1 }}
</span>
</el-tooltip>
</div>
<span v-else class="text-gray-400">未记录</span>
</template>
</el-table-column>
<el-table-column label="添加时间" width="160">
<template #default="{ row }">
{{ formatTime(firstExternalAddTime(row)) }}
@@ -245,9 +288,19 @@
{{ formatTime(row.update_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="viewDetail(row)">查看详情</el-button>
<el-button
v-perms="['qywx.customer/delete']"
type="danger"
link
:loading="deletingCustomerId === Number(row.id)"
:disabled="deletingCustomerId !== null"
@click="handleDelete(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
@@ -500,6 +553,21 @@
</el-descriptions-item>
<el-descriptions-item label="添加时间" :span="2">
{{ formatTime(firstExternalAddTime(currentCustomer)) }}
</el-descriptions-item>
<el-descriptions-item label="添加渠道" :span="2">
<div v-if="customerAddSources(currentCustomer).length" class="flex flex-wrap gap-1">
<el-tooltip
v-for="source in customerAddSources(currentCustomer)"
:key="source.key"
:content="addSourceTooltip(source)"
placement="top"
>
<el-tag size="small" type="info" effect="plain">
{{ source.label }}
</el-tag>
</el-tooltip>
</div>
<span v-else class="text-gray-400">未记录</span>
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ formatTime(currentCustomer.update_time) }}
@@ -549,8 +617,9 @@ import { Refresh, Setting, DataLine, CollectionTag } from '@element-plus/icons-v
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import {
qywxCustomerLists,
qywxCustomerSync,
qywxCustomerLists,
qywxCustomerDelete,
qywxCustomerSync,
qywxCustomerStats,
qywxSyncSettingsGet,
qywxSyncSettingsSave,
@@ -563,6 +632,7 @@ const syncing = ref(false)
const showSyncSettings = ref(false)
const showDetail = ref(false)
const currentCustomer = ref<any>(null)
const deletingCustomerId = ref<number | null>(null)
const stats = reactive({
total: 0,
@@ -591,17 +661,19 @@ const syncSettings = reactive({
interval: 3600
})
const queryParams = reactive<{
name: string
follow_user: string
tag_ids: string[]
const queryParams = reactive<{
name: string
follow_user: string
add_way: number | ''
tag_ids: string[]
add_time_start: string
add_time_end: string
dedupe_mode: 'first' | 'any'
}>({
name: '',
follow_user: '',
tag_ids: [],
name: '',
follow_user: '',
add_way: '',
tag_ids: [],
add_time_start: '',
add_time_end: '',
dedupe_mode: 'first'
@@ -639,6 +711,22 @@ interface TagStatsPayload {
groups: TagGroup[]
}
interface AddChannel {
state: string
label: string
source_type: 'promotion_pool' | 'state'
pool_id: number
user_id: string
event_time: number
}
interface AddSource extends AddChannel {
key: string
add_way: number | null
channel_label: string
staff_name: string
}
const tagStats = reactive<TagStatsPayload>({
total_tags: 0,
total_relations: 0,
@@ -847,10 +935,11 @@ const { pager, getLists, resetPage, resetParams } = usePaging({
params: queryParams
})
function handleReset() {
queryParams.name = ''
queryParams.follow_user = ''
queryParams.tag_ids = []
function handleReset() {
queryParams.name = ''
queryParams.follow_user = ''
queryParams.add_way = ''
queryParams.tag_ids = []
queryParams.add_time_start = ''
queryParams.add_time_end = ''
queryParams.dedupe_mode = 'first'
@@ -964,6 +1053,34 @@ function viewDetail(row: any) {
showDetail.value = true
}
async function handleDelete(row: Record<string, any>) {
const id = Number(row.id)
if (!Number.isInteger(id) || id <= 0 || deletingCustomerId.value !== null) return
const customerName = String(row.name || row.external_userid || '该客户')
try {
await feedback.confirm(
`确定删除企业微信客户“${customerName}”吗?此操作仅删除系统内的同步记录,不会删除企业微信中的客户关系;后续重新同步时可能再次出现。`
)
} catch {
return
}
deletingCustomerId.value = id
try {
await qywxCustomerDelete({ id })
if (pager.page > 1 && pager.lists.length === 1) {
pager.page -= 1
}
await Promise.all([getLists(), loadStats(), loadTagStats()])
feedback.msgSuccess('删除成功')
} catch (e: any) {
feedback.msgError(e?.message || e?.msg || '删除失败')
} finally {
deletingCustomerId.value = null
}
}
/** 列表接口会写入 admin_nameadmin.work_wechat_userid = userid */
function formatFollowUser(user: Record<string, any>) {
const adminName = String(user?.admin_name ?? '').trim()
@@ -985,6 +1102,167 @@ function followStaffTooltip(user: Record<string, any>) {
return parts.join('')
}
function customerAddChannels(row: Record<string, any> | null | undefined): AddChannel[] {
if (!row) return []
if (Array.isArray(row.add_channels)) {
return row.add_channels
.map((channel: Record<string, any>): AddChannel => ({
state: String(channel?.state ?? '').trim(),
label: String(channel?.label ?? channel?.state ?? '').trim(),
source_type: channel?.source_type === 'promotion_pool' ? 'promotion_pool' : 'state',
pool_id: Number(channel?.pool_id ?? 0),
user_id: String(channel?.user_id ?? '').trim(),
event_time: Number(channel?.event_time ?? 0)
}))
.filter((channel: AddChannel) => channel.state !== '')
}
// 兼容仅返回原始渠道数组的旧接口/灰度节点。
if (!Array.isArray(row.add_channel_states)) return []
return row.add_channel_states
.map((state: unknown) => String(state ?? '').trim())
.filter((state: string) => state !== '')
.map((state: string) => ({
state,
label: state,
source_type: 'state' as const,
pool_id: 0,
user_id: '',
event_time: 0
}))
}
const ADD_WAY_LABELS: Record<number, string> = {
0: '未知添加方式',
1: '通过扫描二维码添加',
2: '通过搜索手机号添加',
3: '通过名片分享添加',
4: '通过群聊添加',
5: '通过手机通讯录添加',
6: '通过微信联系人添加',
8: '安装第三方应用时自动添加',
9: '通过搜索邮箱添加',
10: '通过视频号添加',
11: '通过日程参与人添加',
12: '通过会议参与人添加',
13: '通过微信好友添加',
14: '通过智慧硬件专属客服添加',
15: '通过上门服务客服添加',
16: '通过获客链接添加',
17: '通过定制开发添加',
18: '通过需求回复添加',
21: '通过第三方售前客服添加',
22: '通过可能的商务伙伴添加',
24: '通过接受微信好友申请添加',
201: '通过内部成员共享添加',
202: '通过管理员或负责人分配添加'
}
const ADD_WAY_OPTIONS = Object.entries(ADD_WAY_LABELS).map(([value, label]) => ({
value: Number(value),
label
}))
function normalizeAddWay(value: unknown): number | null {
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) return value
if (typeof value !== 'string' || !/^\d+$/.test(value.trim())) return null
return Number(value.trim())
}
function addWayLabel(addWay: number) {
return ADD_WAY_LABELS[addWay] || `其他添加方式(${addWay}`
}
function customerAddSources(row: Record<string, any> | null | undefined): AddSource[] {
if (!row) return []
const channels = customerAddChannels(row)
const usedChannelIndexes = new Set<number>()
const sources: AddSource[] = []
const followUsers = Array.isArray(row.follow_users) ? row.follow_users : []
followUsers.forEach((user: Record<string, any>, index: number) => {
const userId = String(user?.userid ?? user?.UserId ?? '').trim()
const state = String(user?.state ?? user?.State ?? '').trim()
const addWay = normalizeAddWay(user?.add_way ?? user?.AddWay)
let channelIndex = channels.findIndex(
(channel, i) =>
!usedChannelIndexes.has(i) &&
userId !== '' &&
state !== '' &&
channel.user_id === userId &&
channel.state === state
)
if (channelIndex < 0 && state !== '') {
channelIndex = channels.findIndex(
(channel, i) => !usedChannelIndexes.has(i) && channel.state === state
)
}
if (channelIndex < 0 && userId !== '') {
channelIndex = channels.findIndex(
(channel, i) => !usedChannelIndexes.has(i) && channel.user_id === userId
)
}
const channel = channelIndex >= 0 ? channels[channelIndex] : undefined
if (channelIndex >= 0) usedChannelIndexes.add(channelIndex)
if (addWay === null && state === '' && !channel) return
const labelFromApi = String(user?.add_way_label ?? '').trim()
const sourceType = channel?.source_type ?? (/^zyt_pool:[1-9]\d*$/.test(state) ? 'promotion_pool' : 'state')
const label = labelFromApi || (addWay !== null
? addWayLabel(addWay)
: sourceType === 'promotion_pool'
? '通过获客链接添加'
: '通过其他渠道添加')
sources.push({
key: `follow:${index}:${userId}:${addWay ?? 'unknown'}:${state}`,
add_way: addWay,
label,
state: state || channel?.state || '',
channel_label: channel?.label || '',
source_type: sourceType,
pool_id: channel?.pool_id || 0,
user_id: userId || channel?.user_id || '',
staff_name: formatFollowUser(user),
event_time: channel?.event_time || Number(user?.createtime ?? 0)
})
})
// 兼容事件日志中仍有记录、但当前 follow_users 已不存在或旧接口未返回 add_way 的客户。
channels.forEach((channel, index) => {
if (usedChannelIndexes.has(index)) return
sources.push({
...channel,
key: `channel:${index}:${channel.user_id}:${channel.state}`,
add_way: channel.source_type === 'promotion_pool' ? 16 : null,
label: channel.source_type === 'promotion_pool' ? '通过获客链接添加' : '通过其他渠道添加',
channel_label: channel.label,
staff_name: channel.user_id || '—'
})
})
return sources.sort((a, b) => b.event_time - a.event_time)
}
function addSourceTooltip(source: AddSource) {
const parts: string[] = []
parts.push(`添加方式:${source.label}`)
if (source.source_type === 'promotion_pool' && source.channel_label) {
parts.push(`获客助手方案:${source.channel_label}`)
}
if (source.staff_name && source.staff_name !== '—') parts.push(`跟进人:${source.staff_name}`)
if (source.event_time > 0) parts.push(`添加时间:${formatTime(source.event_time)}`)
if (source.state) parts.push(`渠道参数:${source.state}`)
return parts.join('')
}
function remainingAddSourcesTooltip(row: Record<string, any>) {
return customerAddSources(row).slice(1).map(addSourceTooltip).join('\n')
}
/**
* 添加时间:优先接口字段 external_first_add_time(同步写入 + 列表对未回填行按 JSON 兜底);
* 再解析 follow_users;最后退回 create_time
@@ -2,9 +2,10 @@
<div class="automation-form">
<el-alert class="automation-note" type="info" show-icon :closable="false" title="自动化设置只作用于之后新添加的客户,不会写入企微获客链接详情中的“欢迎语/客户标签”配置。" description="系统会在客户添加回调中立即发送渠道欢迎语并添加标签,后台任务负责失败重试及其他补偿。测试时请使用系统复制的、带渠道参数的链接。" />
<section class="automation-section" :class="{ 'is-disabled': receptionDisabled }">
<h3 class="form-section-title">接待设置</h3>
<el-form-item label="接待模式">
<el-radio-group v-model="config.reception_mode" :disabled="disabled">
<el-radio-group v-model="config.reception_mode" :disabled="receptionDisabled">
<el-radio value="always">全天接待</el-radio>
<el-radio value="scheduled">按星期时段自动上下线</el-radio>
</el-radio-group>
@@ -12,50 +13,52 @@
</el-form-item>
<div v-if="config.reception_mode === 'scheduled'" class="reception-schedules">
<div v-for="(slot, index) in config.reception_schedule" :key="index" class="schedule-card">
<div class="schedule-heading"><strong>接待时段 {{ index + 1 }}</strong><el-button type="danger" text size="small" :disabled="disabled" @click="config.reception_schedule.splice(index, 1)">删除时段</el-button></div>
<el-checkbox-group v-model="slot.weekdays" class="weekday-select" :disabled="disabled"><el-checkbox v-for="(day, i) in weekdays" :key="day" :value="i + 1">{{ day }}</el-checkbox></el-checkbox-group>
<div class="time-row"><el-time-picker v-model="slot.start" :disabled="disabled" format="HH:mm" value-format="HH:mm" placeholder="开始时间" :clearable="false" /><span>至</span><el-time-picker v-model="slot.end" :disabled="disabled" format="HH:mm" value-format="HH:mm" placeholder="结束时间" :clearable="false" /><small v-if="slot.end < slot.start">次日结束</small></div>
<el-select v-model="slot.member_admin_ids" :disabled="disabled" multiple filterable clearable placeholder="从上方主接待成员中选择" style="width: 100%">
<div class="schedule-heading"><strong>接待时段 {{ index + 1 }}</strong><el-button type="danger" text size="small" :disabled="receptionDisabled" @click="config.reception_schedule.splice(index, 1)">删除时段</el-button></div>
<el-checkbox-group v-model="slot.weekdays" class="weekday-select" :disabled="receptionDisabled"><el-checkbox v-for="(day, i) in weekdays" :key="day" :value="i + 1">{{ day }}</el-checkbox></el-checkbox-group>
<div class="time-row"><el-time-picker v-model="slot.start" :disabled="receptionDisabled" format="HH:mm" value-format="HH:mm" placeholder="开始时间" :clearable="false" /><span>至</span><el-time-picker v-model="slot.end" :disabled="receptionDisabled" format="HH:mm" value-format="HH:mm" placeholder="结束时间" :clearable="false" /><small v-if="slot.end < slot.start">次日结束</small></div>
<el-select v-model="slot.member_admin_ids" :disabled="receptionDisabled" multiple filterable clearable placeholder="从上方主接待成员中选择" style="width: 100%">
<el-option v-for="member in mainMembers" :key="member.id" :value="Number(member.id)" :label="memberLabel(member)" />
</el-select>
<p v-if="slot.member_admin_ids.some((id) => !mainMemberIds.includes(id))" class="inline-error">该时段含已从主接待移除的成员请重新选择</p>
</div>
<el-button :icon="Plus" :disabled="disabled || config.reception_schedule.length >= 30" @click="addReceptionSlot">添加接待时段</el-button>
<el-button :icon="Plus" :disabled="receptionDisabled || config.reception_schedule.length >= 30" @click="addReceptionSlot">添加接待时段</el-button>
<p class="field-help">最多 30 个时段跨午夜时段归属开始日例如星期一 22:00 02:00 包含星期二凌晨接待时段重叠时取成员并集</p>
</div>
<el-form-item label="备用成员" :required="config.reception_mode === 'scheduled'">
<el-select v-model="config.backup_member_admin_ids" :disabled="disabled" multiple filterable clearable collapse-tags collapse-tags-tooltip :max-collapse-tags="3" :multiple-limit="500" placeholder="主接待成员均不可用时由备用成员接待" style="width: 100%">
<el-option v-for="member in members" :key="member.id" :value="Number(member.id)" :label="memberLabel(member)" :disabled="mainMemberIds.includes(Number(member.id))" />
<el-select v-model="config.backup_member_admin_ids" :disabled="receptionDisabled" multiple filterable clearable collapse-tags collapse-tags-tooltip :max-collapse-tags="3" :multiple-limit="500" placeholder="主接待成员均不可用时由备用成员接待" style="width: 100%">
<el-option v-for="member in members" :key="member.id" :value="Number(member.id)" :label="memberLabel(member)" :disabled="backupExcludedIds.includes(Number(member.id))" />
<el-option v-for="id in missingBackupIds" :key="`missing-${id}`" :value="id" :label="`成员 ${id}(当前不可选,请移除后重新选择)`" disabled />
</el-select>
<p class="field-help">备用成员不能与主接待重复按时段模式至少配置一名备用成员仅当无可用主接待时进入官方成员范围</p>
</el-form-item>
</section>
<section class="automation-section" :class="{ 'is-disabled': customerDisabled }">
<h3 class="form-section-title">客户设置</h3>
<el-form-item label="自动添加客户标签">
<el-switch v-model="config.tags_enabled" :disabled="disabled || tagsCreating" />
<el-switch v-model="config.tags_enabled" :disabled="customerDisabled || tagsCreating" />
<div v-if="hasMultipleTags" class="legacy-tags-warning full-width" role="alert">
<p>原方案设置了多个标签{{ selectedTagNames }}现在仅支持单选请重新选择一个标签或清空原标签</p>
<el-button size="small" :disabled="disabled || tagsCreating" @click="selectedTag = ''">清空原标签</el-button>
<el-button size="small" :disabled="customerDisabled || tagsCreating" @click="selectedTag = ''">清空原标签</el-button>
</div>
<div v-if="config.tags_enabled" class="full-width tags-content">
<div class="tag-select-row">
<el-select v-model="selectedTag" :disabled="disabled || tagsCreating" :loading="tagsLoading" filterable clearable placeholder="选择一个企业微信客户标签" aria-label="企业微信客户标签" class="tag-select">
<el-select v-model="selectedTag" :disabled="customerDisabled || tagsCreating" :loading="tagsLoading" filterable clearable placeholder="选择一个企业微信客户标签" aria-label="企业微信客户标签" class="tag-select">
<el-option-group v-for="group in tagGroups" :key="group.group_id" :label="group.group_name">
<el-option v-for="tag in group.tag" :key="tag.id" :value="tag.id" :label="tag.name" />
</el-option-group>
<el-option-group v-if="unknownTagIds.length" label="已选标签(名称暂不可用)"><el-option v-for="id in unknownTagIds" :key="id" :value="id" :label="`已选标签 · ${id}`" /></el-option-group>
</el-select>
<el-button :icon="Plus" :disabled="disabled || tagsCreating" @click="showCustomTag = !showCustomTag">自定义标签</el-button>
<el-button :icon="Refresh" :disabled="disabled || tagsCreating" :loading="tagsLoading" @click="loadTags">{{ tagsError ? '重试' : '刷新标签' }}</el-button>
<el-button :icon="Plus" :disabled="customerDisabled || tagsCreating" @click="showCustomTag = !showCustomTag">自定义标签</el-button>
<el-button :icon="Refresh" :disabled="customerDisabled || tagsCreating" :loading="tagsLoading" @click="loadTags">{{ tagsError ? '重试' : '刷新标签' }}</el-button>
</div>
<p v-if="tagsError" role="alert" class="inline-error">{{ tagsError }} 已保留原有标签点击重试重新加载</p>
<p v-else class="field-help">每个方案只选一个标签可选择已有企业微信标签也可自定义创建客户添加成功后由系统调用企微接口打标不会显示在企微获客链接详情的客户标签配置中</p>
<div v-if="showCustomTag" class="custom-tag-editor">
<label for="promotion-custom-tag-name">自定义标签名称</label>
<div class="custom-tag-row">
<el-input id="promotion-custom-tag-name" v-model="customTagName" :disabled="disabled || tagsCreating" maxlength="30" show-word-limit placeholder="例如:官网咨询" @input="customTagError = ''" @keydown.enter.prevent="createCustomTag" />
<el-button type="primary" :disabled="disabled || tagsLoading" :loading="tagsCreating" @click="createCustomTag">创建并选用</el-button>
<el-input id="promotion-custom-tag-name" v-model="customTagName" :disabled="customerDisabled || tagsCreating" maxlength="30" show-word-limit placeholder="例如:官网咨询" @input="customTagError = ''" @keydown.enter.prevent="createCustomTag" />
<el-button type="primary" :disabled="customerDisabled || tagsLoading" :loading="tagsCreating" @click="createCustomTag">创建并选用</el-button>
</div>
<p class="field-help">创建到企业微信推广渠道分组同组同名标签会复用创建后即保存到企微标签库取消方案编辑不会删除标签</p>
<p v-if="customTagError" role="alert" class="inline-error">{{ customTagError }} 原有选择未改变</p>
@@ -64,22 +67,24 @@
</div>
</el-form-item>
<el-form-item label="自动设置客户备注">
<el-switch v-model="config.remark_enabled" :disabled="disabled" />
<el-switch v-model="config.remark_enabled" :disabled="customerDisabled" />
<div v-if="config.remark_enabled" class="full-width remark-content">
<div class="token-buttons"><el-button v-for="token in templateTokens" :key="token.value" size="small" :disabled="disabled" @click="insertRemark(token.value)">插入{{ token.label }}</el-button></div>
<el-input ref="remarkInput" v-model="config.remark_template" :disabled="disabled" maxlength="200" show-word-limit placeholder="例如:官网-{customer_name}" @select="rememberRemarkSelection" @keyup="rememberRemarkSelection" @click="rememberRemarkSelection" @blur="rememberRemarkSelection" />
<div class="token-buttons"><el-button v-for="token in templateTokens" :key="token.value" size="small" :disabled="customerDisabled" @click="insertRemark(token.value)">插入{{ token.label }}</el-button></div>
<el-input ref="remarkInput" v-model="config.remark_template" :disabled="customerDisabled" maxlength="200" show-word-limit placeholder="例如:官网-{customer_name}" @select="rememberRemarkSelection" @keyup="rememberRemarkSelection" @click="rememberRemarkSelection" @blur="rememberRemarkSelection" />
<div class="remark-preview"><span>备注预览</span><strong>{{ remarkPreview || '—' }}</strong><small>{{ Array.from(remarkPreview).length }}/20 </small></div>
<p class="field-help">示例客户张女士员工{{ employeeName }}添加时间格式为 YYYY-MM-DD生成后的备注最多保留前 20 </p>
</div>
</el-form-item>
<el-form-item label="自动设置客户描述">
<el-switch v-model="config.description_enabled" :disabled="disabled" />
<el-input v-if="config.description_enabled" v-model="config.description" class="description-input" :disabled="disabled" type="textarea" :rows="3" maxlength="150" show-word-limit placeholder="请输入客户描述,最多 150 字" />
<el-switch v-model="config.description_enabled" :disabled="customerDisabled" />
<el-input v-if="config.description_enabled" v-model="config.description" class="description-input" :disabled="customerDisabled" type="textarea" :rows="3" maxlength="150" show-word-limit placeholder="请输入客户描述,最多 150 字" />
</el-form-item>
</section>
<section class="automation-section" :class="{ 'is-disabled': welcomeDisabled }">
<h3 class="form-section-title">欢迎语设置</h3>
<el-form-item label="欢迎语模式">
<el-radio-group v-model="config.welcome_mode" :disabled="disabled || anyUploading">
<el-radio-group v-model="config.welcome_mode" :disabled="welcomeDisabled || anyUploading">
<el-radio value="channel">渠道欢迎语</el-radio>
<el-radio value="default">默认欢迎语</el-radio>
<el-radio value="none">不发送欢迎语</el-radio>
@@ -90,20 +95,21 @@
</el-form-item>
<template v-if="config.welcome_mode === 'channel'">
<div class="welcome-block"><h4>基础渠道欢迎语</h4><p class="field-help">未开启分时欢迎语或新客户添加时间未匹配任何时段时使用以下内容</p>
<WelcomeMessageEditor v-model="config.welcome" :disabled="disabled" :employee-name="employeeName" @busy="(busy) => updateBusy('basic', busy)" />
<WelcomeMessageEditor v-model="config.welcome" :disabled="welcomeDisabled" :employee-name="employeeName" @busy="(busy) => updateBusy('basic', busy)" />
</div>
<el-form-item class="schedule-switch" label="分时欢迎语"><el-switch v-model="config.welcome_schedule_enabled" :disabled="disabled || anyUploading" /><span class="switch-help">按客户添加时的北京时间匹配时段不能重叠</span></el-form-item>
<el-form-item class="schedule-switch" label="分时欢迎语"><el-switch v-model="config.welcome_schedule_enabled" :disabled="welcomeDisabled || anyUploading" /><span class="switch-help">按客户添加时的北京时间匹配时段不能重叠</span></el-form-item>
<div v-if="config.welcome_schedule_enabled">
<div v-for="(slot, index) in config.welcome_schedule" :key="index" class="schedule-card welcome-schedule">
<div class="schedule-heading"><strong>欢迎语时段 {{ index + 1 }}</strong><el-button type="danger" text size="small" :disabled="disabled || anyUploading" @click="removeWelcomeSlot(index)">删除时段</el-button></div>
<el-checkbox-group v-model="slot.weekdays" class="weekday-select" :disabled="disabled"><el-checkbox v-for="(day, i) in weekdays" :key="day" :value="i + 1">{{ day }}</el-checkbox></el-checkbox-group>
<div class="time-row"><el-time-picker v-model="slot.start" :disabled="disabled" format="HH:mm" value-format="HH:mm" placeholder="开始时间" :clearable="false" /><span>至</span><el-time-picker v-model="slot.end" :disabled="disabled" format="HH:mm" value-format="HH:mm" placeholder="结束时间" :clearable="false" /><small v-if="slot.end < slot.start">次日结束</small></div>
<WelcomeMessageEditor :model-value="slot" :disabled="disabled" :employee-name="employeeName" @update:model-value="(message) => Object.assign(slot, message)" @busy="(busy) => updateBusy(`slot-${index}`, busy)" />
<div class="schedule-heading"><strong>欢迎语时段 {{ index + 1 }}</strong><el-button type="danger" text size="small" :disabled="welcomeDisabled || anyUploading" @click="removeWelcomeSlot(index)">删除时段</el-button></div>
<el-checkbox-group v-model="slot.weekdays" class="weekday-select" :disabled="welcomeDisabled"><el-checkbox v-for="(day, i) in weekdays" :key="day" :value="i + 1">{{ day }}</el-checkbox></el-checkbox-group>
<div class="time-row"><el-time-picker v-model="slot.start" :disabled="welcomeDisabled" format="HH:mm" value-format="HH:mm" placeholder="开始时间" :clearable="false" /><span>至</span><el-time-picker v-model="slot.end" :disabled="welcomeDisabled" format="HH:mm" value-format="HH:mm" placeholder="结束时间" :clearable="false" /><small v-if="slot.end < slot.start">次日结束</small></div>
<WelcomeMessageEditor :model-value="slot" :disabled="welcomeDisabled" :employee-name="employeeName" @update:model-value="(message) => Object.assign(slot, message)" @busy="(busy) => updateBusy(`slot-${index}`, busy)" />
</div>
<el-button :icon="Plus" :disabled="disabled || anyUploading || config.welcome_schedule.length >= 30" @click="addWelcomeSlot">添加欢迎语时段</el-button>
<el-button :icon="Plus" :disabled="welcomeDisabled || anyUploading || config.welcome_schedule.length >= 30" @click="addWelcomeSlot">添加欢迎语时段</el-button>
<p class="field-help">最多 30 个时段,支持跨午夜。时段外自动使用基础渠道欢迎语,不会随机选择内容。</p>
</div>
</template>
</section>
</div>
</template>
@@ -116,9 +122,22 @@ import WelcomeMessageEditor from './WelcomeMessageEditor.vue'
import { previewTemplate, templateTokens, validateCustomTagName, weekdays } from './promotion-automation'
import type { PromotionAutomationConfig, PromotionMemberChoice } from './promotion-automation'
const props = defineProps<{ modelValue: PromotionAutomationConfig; mainMemberIds: number[]; members: PromotionMemberChoice[]; disabled?: boolean }>()
type AutomationSection = 'reception' | 'customer' | 'welcome'
const props = defineProps<{
modelValue: PromotionAutomationConfig
mainMemberIds: number[]
members: PromotionMemberChoice[]
disabled?: boolean
disabledSections?: AutomationSection[]
backupExcludedMemberIds?: number[]
}>()
const emit = defineEmits<{ 'update:modelValue': [config: PromotionAutomationConfig]; busy: [value: boolean] }>()
const config = computed({ get: () => props.modelValue, set: (value) => emit('update:modelValue', value) })
const sectionDisabled = (section: AutomationSection) => Boolean(props.disabled || props.disabledSections?.includes(section))
const receptionDisabled = computed(() => sectionDisabled('reception'))
const customerDisabled = computed(() => sectionDisabled('customer'))
const welcomeDisabled = computed(() => sectionDisabled('welcome'))
const backupExcludedIds = computed(() => props.backupExcludedMemberIds || props.mainMemberIds)
const mainMembers = computed(() => props.members.filter((member) => props.mainMemberIds.includes(Number(member.id))))
const missingBackupIds = computed(() => config.value.backup_member_admin_ids.filter((id) => !props.members.some((member) => Number(member.id) === id)))
const employeeName = computed(() => mainMembers.value[0]?.name || '小陈')
@@ -153,7 +172,9 @@ const unknownTagIds = computed(() => {
const ids = new Set(tagGroups.value.flatMap((group) => group.tag.map((tag) => tag.id)))
return config.value.tag_ids.filter((id) => !ids.has(id))
})
watch(() => config.value.tags_enabled, (enabled) => { if (enabled && !tagsLoaded.value && !tagsLoading.value) void loadTags() }, { immediate: true })
watch([() => config.value.tags_enabled, customerDisabled], ([enabled, sectionIsDisabled]) => {
if (enabled && !sectionIsDisabled && !tagsLoaded.value && !tagsLoading.value) void loadTags()
}, { immediate: true })
function memberLabel(member: PromotionMemberChoice) { return `${member.name} · ${member.dept_names?.join(' / ') || member.userid || '未分部门'}` }
function addReceptionSlot() { config.value.reception_schedule.push({ weekdays: [1, 2, 3, 4, 5], start: '09:00', end: '18:00', member_admin_ids: [...props.mainMemberIds] }) }
function addWelcomeSlot() { config.value.welcome_schedule.push({ weekdays: [1, 2, 3, 4, 5], start: '09:00', end: '18:00', text: '', attachments: [] }) }
@@ -173,7 +194,7 @@ async function loadTags() {
} finally { tagsLoading.value = false }
}
async function createCustomTag() {
if (props.disabled || tagsCreating.value || tagsLoading.value) return
if (customerDisabled.value || tagsCreating.value || tagsLoading.value) return
customTagError.value = validateCustomTagName(customTagName.value)
customTagSuccess.value = ''
if (customTagError.value) return
@@ -215,6 +236,7 @@ onBeforeUnmount(() => emit('busy', false))
<style scoped>
.automation-form { width: 100%; }.automation-note { margin-top: 22px; }.automation-note :deep(.el-alert__description) { line-height: 1.7; }
.automation-section { min-width: 0; transition: opacity .2s ease; }.automation-section.is-disabled { opacity: .58; }
.form-section-title { margin: 28px 0 18px; padding: 0 0 12px; border-bottom: 1px solid #ebeef5; font-size: 15px; font-weight: 600; color: #303133; }.field-help { width: 100%; font-size: 12px; line-height: 1.7; margin: 6px 0 0; color: #909399; }.warning-help { color: #9f6d14; }.full-width { width: 100%; }.inline-error { width: 100%; color: #d93026; font-size: 12px; line-height: 1.7; margin: 8px 0 0; }
.schedule-card { padding: 16px; border: 1px solid #e4e7ed; border-radius: 6px; background: #fafbfd; margin-bottom: 12px; }.schedule-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; font-size: 13px; }.weekday-select { display: flex; flex-wrap: wrap; gap: 0 18px; }.weekday-select :deep(.el-checkbox) { margin-right: 0; }.time-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 12px 0; }.time-row :deep(.el-date-editor.el-input) { width: 150px; }.time-row > span { font-size: 12px; color: #909399; }.time-row > small { font-size: 12px; color: #b88230; }.reception-schedules { margin: 0 0 20px; }
.tags-content, .remark-content, .description-input { margin-top: 12px; }.tag-select-row { display: flex; gap: 10px; width: 100%; }.tag-select { flex: 1; min-width: 0; }.token-buttons { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }.token-buttons .el-button + .el-button { margin-left: 0; }.remark-preview { display: flex; gap: 14px; align-items: center; padding: 10px 12px; background: #f5f7fa; margin-top: 8px; border-radius: 4px; line-height: 1.7; }.remark-preview span, .remark-preview small { color: #909399; font-size: 12px; }.remark-preview strong { color: #303133; font-size: 13px; font-weight: 500; overflow-wrap: anywhere; }.remark-preview small { margin-left: auto; white-space: nowrap; }.welcome-block h4 { font-size: 13px; font-weight: 600; margin: 0 0 4px; }.welcome-block > .field-help { margin-bottom: 12px; }.schedule-switch { margin-top: 24px; }.switch-help { margin-left: 12px; color: #909399; font-size: 12px; }.welcome-schedule { background: #fff; }
@@ -58,6 +58,11 @@
</div>
<div class="section-heading-actions">
<span v-if="selectedPoolIds.length" class="selection-count">已选 {{ selectedPoolIds.length }} 个方案</span>
<el-button
:icon="Edit"
:disabled="!selectedPoolIds.length"
@click="openBatchConfigDialog()"
>批量修改方案</el-button>
<el-button
:icon="User"
:disabled="!selectedPoolIds.length"
@@ -138,7 +143,8 @@
description="当前全部可用医助会同时写入官方链接的成员范围,由企业微信在打开和添加阶段直接进行多人路由。回调只用于统计实际承接结果,并在禁用、过期或达到上限后更新成员范围。"
/>
<el-table :data="selectedMemberRules" class="link-table" stripe>
<div class="member-table-area">
<el-table :data="selectedMemberRules" class="link-table" height="100%" stripe>
<el-table-column label="推广成员" min-width="210" fixed="left">
<template #default="{ row }">
<div class="member-cell">
@@ -174,7 +180,8 @@
</el-table-column>
<template #empty><el-empty :image-size="72" description="编辑方案并选择获客医助" /></template>
</el-table>
</div>
</div>
</div>
</div>
<el-empty v-else description="创建方案并选择多个医助,保存后自动生成一个企业微信官方获客链接">
<el-button type="primary" :icon="Plus" @click="openPoolDialog()">创建第一个方案</el-button>
@@ -429,6 +436,94 @@
<template #footer><span v-if="automationBusy" class="uploading-save-tip">正在处理标签或素材,请稍候</span><el-button :disabled="savingPool || automationBusy" @click="poolDialogVisible = false">取消</el-button><el-button type="primary" :loading="savingPool" :disabled="automationBusy" @click="savePool">{{ poolForm.id ? '保存方案' : '保存并生成链接' }}</el-button></template>
</el-dialog>
<el-dialog
v-model="batchConfigDialogVisible"
title="批量修改分流方案"
width="1000px"
class="promotion-pool-dialog"
destroy-on-close
:close-on-click-modal="false"
:close-on-press-escape="!savingBatchConfig && !batchConfigBusy"
:show-close="!savingBatchConfig && !batchConfigBusy"
>
<div ref="batchConfigScroll" class="pool-form-scroll batch-config-scroll">
<el-alert
type="warning"
show-icon
:closable="false"
:title="`将统一修改 ${batchConfigForm.pool_ids.length} 个分流方案`"
description="仅勾选的项目会覆盖到所选方案;未勾选项目保留各方案原值。表单初始值取第一个所选方案。"
/>
<el-alert v-if="batchConfigError" class="pool-form-error batch-config-error" :title="batchConfigError" type="error" show-icon :closable="false" role="alert" />
<div v-if="batchConfigPools.length" class="batch-pool-summary">
<strong>已选方案</strong>
<span v-for="pool in batchConfigPools.slice(0, 8)" :key="pool.id">{{ pool.name }}</span>
<small v-if="batchConfigPools.length > 8">另有 {{ batchConfigPools.length - 8 }} </small>
</div>
<el-form class="batch-config-form" label-position="top" :disabled="savingBatchConfig">
<section class="batch-config-section">
<h3>基础设置</h3>
<div class="batch-field-grid">
<div class="batch-field" :class="{ 'is-disabled': !batchConfigApply.skip_verify }">
<el-checkbox v-model="batchConfigApply.skip_verify" :disabled="savingBatchConfig">批量修改验证方式</el-checkbox>
<el-form-item label="添加客户时跳过验证">
<el-switch v-model="batchConfigForm.skip_verify" :disabled="!batchConfigApply.skip_verify || savingBatchConfig" :active-value="1" :inactive-value="0" active-text="跳过验证" inactive-text="需要验证" />
</el-form-item>
</div>
<div class="batch-field" :class="{ 'is-disabled': !batchConfigApply.status }">
<el-checkbox v-model="batchConfigApply.status" :disabled="savingBatchConfig">批量修改运行状态</el-checkbox>
<el-form-item label="运行状态">
<el-switch v-model="batchConfigForm.status" :disabled="!batchConfigApply.status || savingBatchConfig" :active-value="1" :inactive-value="0" active-text="运行" inactive-text="停用" />
</el-form-item>
</div>
</div>
<div class="batch-field" :class="{ 'is-disabled': !batchConfigApply.fallback_url }">
<el-checkbox v-model="batchConfigApply.fallback_url" :disabled="savingBatchConfig">批量修改兜底获客助手链接</el-checkbox>
<el-form-item label="兜底获客助手链接">
<el-input v-model="batchConfigForm.fallback_url" :disabled="!batchConfigApply.fallback_url || savingBatchConfig" placeholder="留空表示清除;仅供旧版兼容跳转使用" />
<span class="form-tip">新生成的官方直链不经过本站跳转此项仅兼容旧安装代码</span>
</el-form-item>
</div>
</section>
<section v-if="overview.automation_installed" class="batch-config-section automation-batch-section">
<h3>自动化设置</h3>
<p class="batch-section-tip">先选择要覆盖的分组未选择的接待客户或欢迎语设置不会随本次批量操作改变</p>
<div class="batch-section-selectors">
<el-checkbox v-model="batchConfigApply.reception" :disabled="savingBatchConfig" border>批量修改接待设置</el-checkbox>
<el-checkbox v-model="batchConfigApply.customer" :disabled="savingBatchConfig" border>批量修改客户设置</el-checkbox>
<el-checkbox v-model="batchConfigApply.welcome" :disabled="savingBatchConfig" border>批量修改欢迎语设置</el-checkbox>
</div>
<el-alert
v-if="batchConfigApply.reception && !batchSharedPrimaryMemberIds.length"
type="warning"
show-icon
:closable="false"
title="所选方案没有共同主接待成员"
description="仍可统一改为全天接待;若使用按时段接待,时段成员必须同时属于全部所选方案。"
/>
<PromotionAutomationForm
v-model="batchConfigForm.automation_config"
:main-member-ids="batchSharedPrimaryMemberIds"
:backup-excluded-member-ids="batchPrimaryMemberUnion"
:members="overview.member_options"
:disabled="savingBatchConfig"
:disabled-sections="batchDisabledAutomationSections"
@busy="(busy) => batchConfigBusy = busy"
/>
</section>
<el-alert v-else type="warning" show-icon :closable="false" title="自动化配置尚未安装" description="本次仍可批量修改验证方式、兜底链接和运行状态;安装自动化数据表后即可批量修改接待、客户和欢迎语设置。" />
</el-form>
</div>
<template #footer>
<span v-if="batchConfigBusy" class="uploading-save-tip">正在处理标签或素材请稍候</span>
<el-button :disabled="savingBatchConfig || batchConfigBusy" @click="batchConfigDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="savingBatchConfig" :disabled="batchConfigBusy" @click="saveBatchConfig">批量保存</el-button>
</template>
</el-dialog>
<el-dialog
v-model="accessDialogVisible"
title="批量设置他人访问操作"
@@ -525,6 +620,7 @@ import {
} from '@element-plus/icons-vue'
import {
wecomPromotionBatchSetOperators,
wecomPromotionBatchUpdatePools,
wecomPromotionCheckApiPermission,
wecomPromotionCustomerStats,
wecomPromotionDeletePool,
@@ -539,8 +635,10 @@ import type { WecomPromotionCustomerChatStatus } from '@/api/first_visit'
import WecomFloatingWidgetBuilder from './components/WecomFloatingWidgetBuilder.vue'
import PromotionAutomationForm from './components/PromotionAutomationForm.vue'
import { cloneAutomationConfig, defaultAutomationConfig, isWebUrl, serializeAutomationConfig, validateAutomationConfig } from './components/promotion-automation'
import type { PromotionAutomationConfig } from './components/promotion-automation'
type TabName = 'links' | 'customer-stats' | 'configuration' | 'install'
type BatchAutomationSection = 'reception' | 'customer' | 'welcome'
interface PromotionDepartmentOption {
id: number | string
@@ -606,11 +704,18 @@ const togglingMemberId = ref(0)
const deletingPoolId = ref(0)
const accessDialogVisible = ref(false)
const savingAccess = ref(false)
const batchConfigDialogVisible = ref(false)
const savingBatchConfig = ref(false)
const batchConfigBusy = ref(false)
const batchConfigScroll = ref<HTMLElement>()
const batchConfigError = ref('')
const poolForm = reactive({ id: 0, name: '', fallback_url: '', status: 1, member_admin_ids: [] as number[], skip_verify: 0, main_url: '', automation_config: defaultAutomationConfig() })
const poolFormScroll = ref<HTMLElement>()
const poolFormError = ref('')
const automationBusy = ref(false)
const accessForm = reactive({ pool_ids: [] as number[], operator_admin_ids: [] as number[], action: 'grant' as 'grant' | 'revoke' })
const batchConfigApply = reactive({ skip_verify: false, fallback_url: false, status: false, reception: false, customer: false, welcome: false })
const batchConfigForm = reactive({ pool_ids: [] as number[], skip_verify: 0, fallback_url: '', status: 1, automation_config: defaultAutomationConfig() })
const memberForm = reactive({ id: 0, name: '', userid: '', daily_limit: 0, status: 1, active_range: [] as string[], remark: '' })
const customerStatsLoading = ref(false)
const customerStatsLoaded = ref(false)
@@ -633,6 +738,20 @@ const selectedPool = computed(() => overview.pools.find((item: any) => Number(it
const selectedMemberRules = computed(() => Array.isArray(selectedPool.value?.member_rules) ? selectedPool.value.member_rules : [])
const selectedInstallPool = computed(() => overview.pools.find((item: any) => Number(item.id) === selectedInstallPoolId.value))
const accessDialogPools = computed(() => overview.pools.filter((item: any) => accessForm.pool_ids.includes(Number(item.id))))
const batchConfigPools = computed(() => overview.pools.filter((item: any) => batchConfigForm.pool_ids.includes(Number(item.id))))
const batchPrimaryMemberSets = computed(() => batchConfigPools.value.map((pool: any) => new Set<number>(
(Array.isArray(pool.member_admin_ids) ? pool.member_admin_ids : []).map(Number)
)))
const batchPrimaryMemberUnion = computed(() => [...new Set(batchPrimaryMemberSets.value.flatMap((ids) => [...ids]))])
const batchSharedPrimaryMemberIds = computed(() => {
const sets = batchPrimaryMemberSets.value
if (!sets.length) return []
return [...sets[0]].filter((id) => sets.slice(1).every((ids) => ids.has(id)))
})
const batchDisabledAutomationSections = computed<BatchAutomationSection[]>(() => (
(['reception', 'customer', 'welcome'] as BatchAutomationSection[])
.filter((section) => !batchConfigApply[section])
))
const memberTreeProps = { value: 'value', label: 'label', children: 'children', disabled: 'disabled' }
const memberDepartmentTree = computed(() => buildMemberDepartmentTree(overview.department_options, overview.member_options))
const memberTreeDefaultExpandedKeys = computed(() => memberDepartmentTree.value.map((node) => node.value))
@@ -692,6 +811,135 @@ function togglePoolSelection(pool: any, checked: unknown) {
selectedPoolIds.value = [...next]
}
function openBatchConfigDialog(poolIds: number[] = selectedPoolIds.value) {
const manageableIds = new Set(overview.pools
.filter((pool: any) => pool.can_manage_access)
.map((pool: any) => Number(pool.id)))
const ids = [...new Set(poolIds.map(Number).filter((id) => manageableIds.has(id)))]
if (!ids.length) return ElMessage.warning('请先选择可管理的分流方案')
if (ids.length > 100) return ElMessage.warning('单次最多设置 100 个分流方案')
const reference = overview.pools.find((pool: any) => Number(pool.id) === ids[0])
Object.assign(batchConfigApply, {
skip_verify: false,
fallback_url: false,
status: false,
reception: false,
customer: false,
welcome: false
})
Object.assign(batchConfigForm, {
pool_ids: ids,
skip_verify: Number(reference?.skip_verify) === 1 ? 1 : 0,
fallback_url: String(reference?.fallback_url || ''),
status: Number(reference?.status) === 1 ? 1 : 0,
automation_config: cloneAutomationConfig(reference?.automation_config)
})
batchConfigError.value = ''
batchConfigBusy.value = false
batchConfigDialogVisible.value = true
}
function copyAutomationSection(
target: PromotionAutomationConfig,
source: PromotionAutomationConfig,
keys: Array<keyof PromotionAutomationConfig>
) {
const targetRecord = target as unknown as Record<string, unknown>
const sourceRecord = source as unknown as Record<string, unknown>
keys.forEach((key) => { targetRecord[key] = JSON.parse(JSON.stringify(sourceRecord[key])) })
}
function setBatchConfigError(message: string) {
batchConfigError.value = message
batchConfigScroll.value?.scrollTo({ top: 0, behavior: 'smooth' })
ElMessage.warning(message)
}
async function saveBatchConfig() {
if (savingBatchConfig.value || batchConfigBusy.value) return
batchConfigError.value = ''
const hasAutomationChange = overview.automation_installed
&& (batchConfigApply.reception || batchConfigApply.customer || batchConfigApply.welcome)
if (!batchConfigApply.skip_verify && !batchConfigApply.fallback_url && !batchConfigApply.status && !hasAutomationChange) {
return setBatchConfigError('请至少勾选一项需要批量修改的配置')
}
if (batchConfigApply.fallback_url && batchConfigForm.fallback_url.trim() && !isWebUrl(batchConfigForm.fallback_url.trim())) {
return setBatchConfigError('兜底获客助手链接必须为有效的 HTTP/HTTPS 地址')
}
const automation = cloneAutomationConfig(batchConfigForm.automation_config)
if (batchConfigApply.reception && automation.reception_mode !== 'scheduled') automation.reception_schedule = []
if (batchConfigApply.welcome && automation.welcome_mode !== 'channel') automation.welcome = { text: '', attachments: [] }
if (batchConfigApply.welcome && (automation.welcome_mode !== 'channel' || !automation.welcome_schedule_enabled)) automation.welcome_schedule = []
if (hasAutomationChange) {
const validationConfig = defaultAutomationConfig()
if (batchConfigApply.reception) copyAutomationSection(validationConfig, automation, ['reception_mode', 'reception_schedule', 'backup_member_admin_ids'])
if (batchConfigApply.customer) copyAutomationSection(validationConfig, automation, ['tags_enabled', 'tag_ids', 'remark_enabled', 'remark_template', 'description_enabled', 'description'])
if (batchConfigApply.welcome) copyAutomationSection(validationConfig, automation, ['welcome_mode', 'welcome', 'welcome_schedule_enabled', 'welcome_schedule'])
const backupConflict = batchConfigApply.reception
&& automation.backup_member_admin_ids.some((id) => batchPrimaryMemberUnion.value.includes(Number(id)))
const validationError = backupConflict
? '备用成员不能是任一所选方案的主接待成员'
: validateAutomationConfig(validationConfig, batchSharedPrimaryMemberIds.value)
if (validationError) return setBatchConfigError(validationError)
}
const changes: {
skip_verify?: 0 | 1
fallback_url?: string
status?: 0 | 1
automation_config?: Record<string, unknown>
} = {}
if (batchConfigApply.skip_verify) changes.skip_verify = batchConfigForm.skip_verify === 1 ? 1 : 0
if (batchConfigApply.fallback_url) changes.fallback_url = batchConfigForm.fallback_url.trim()
if (batchConfigApply.status) changes.status = batchConfigForm.status === 1 ? 1 : 0
if (hasAutomationChange) {
const serialized = serializeAutomationConfig(automation) as unknown as Record<string, unknown>
const automationPatch: Record<string, unknown> = {}
const assignKeys = (keys: string[]) => keys.forEach((key) => { automationPatch[key] = serialized[key] })
if (batchConfigApply.reception) assignKeys(['reception_mode', 'reception_schedule', 'backup_member_admin_ids'])
if (batchConfigApply.customer) assignKeys(['tags_enabled', 'tag_ids', 'remark_enabled', 'remark_template', 'description_enabled', 'description'])
if (batchConfigApply.welcome) assignKeys(['welcome_mode', 'welcome', 'welcome_schedule_enabled', 'welcome_schedule'])
changes.automation_config = automationPatch
}
savingBatchConfig.value = true
try {
const result = await wecomPromotionBatchUpdatePools({
pool_ids: [...batchConfigForm.pool_ids],
changes
})
if (result.updated === 0) {
const detail = result.results
.slice(0, 2)
.map((item) => `${item.name || `方案 ${item.id}`}${item.error || '保存失败'}`)
.join('')
return setBatchConfigError(detail || '所选方案均未能保存,请检查配置后重试')
}
batchConfigDialogVisible.value = false
selectedPoolIds.value = []
await loadOverview()
if (result.failed > 0) {
const detail = result.results
.filter((item) => !item.success)
.slice(0, 2)
.map((item) => `${item.name || `方案 ${item.id}`}${item.error || '保存失败'}`)
.join('')
ElMessage.warning(`已更新 ${result.updated} 个方案,${result.failed} 个失败${detail ? `${detail}` : ''}`)
} else if (result.sync_error_count > 0) {
ElMessage.warning(`已更新 ${result.updated} 个方案,其中 ${result.sync_error_count} 个企微范围将在后台自动重试同步`)
} else if (result.sync_queued_count > 0) {
ElMessage.success(`已批量更新 ${result.updated} 个分流方案,${result.sync_queued_count} 个企微链接配置将在后台同步`)
} else {
ElMessage.success(`已批量更新 ${result.updated} 个分流方案`)
}
} catch (error: any) {
setBatchConfigError(error?.message || '批量修改分流方案失败')
} finally {
savingBatchConfig.value = false
}
}
function openAccessDialog(poolIds: number[] = selectedPoolIds.value) {
const manageableIds = new Set(overview.pools
.filter((pool: any) => pool.can_manage_access)
@@ -1319,18 +1567,18 @@ h1, h2, h3, p { margin: 0; }
.section-heading-actions { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.section-heading-actions .el-button + .el-button { margin-left: 0; }
.selection-count { color: #117f75; font-size: 11px; font-weight: 600; }
.pool-layout { display: grid; grid-template-columns: 252px minmax(0, 1fr); min-height: 430px; border: 1px solid var(--line); border-radius: 11px; overflow: hidden; }
.pool-sidebar { padding: 8px; border-right: 1px solid var(--line); background: #f7f9fa; }
.pool-layout { display: grid; grid-template-columns: 252px minmax(0, 1fr); height: clamp(430px, calc(100vh - 330px), 720px); min-height: 430px; border: 1px solid var(--line); border-radius: 11px; overflow: hidden; }
.pool-sidebar { min-height: 0; padding: 8px; border-right: 1px solid var(--line); overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; background: #f7f9fa; }
.pool-select-row { display: grid; grid-template-columns: 22px minmax(0, 1fr); align-items: center; gap: 4px; margin-bottom: 5px; }
.pool-select-row :deep(.el-checkbox) { justify-content: center; margin-right: 0; }
.pool-item { display: grid; grid-template-columns: 9px minmax(0, 1fr) 16px; align-items: center; gap: 9px; width: 100%; min-height: 62px; padding: 10px; border: 1px solid transparent; border-radius: 8px; text-align: left; background: transparent; cursor: pointer; }
.pool-item:hover { background: #fff; }.pool-select-row.active .pool-item { border-color: #bfe1dc; background: #fff; box-shadow: 0 5px 16px rgba(25,69,70,.05); }
.pool-item strong, .pool-item small { display: block; }.pool-item strong { overflow: hidden; color: #253348; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }.pool-item small { margin-top: 4px; color: #8c98a7; font-size: 10px; }
.pool-status { width: 8px; height: 8px; border-radius: 50%; }.pool-status.online { background: #18a277; }.pool-status.offline { background: #aab3bf; }.pool-item > .el-icon { color: #9ca7b4; }
.pool-main { min-width: 0; }.pool-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 78px; padding: 12px 15px; border-bottom: 1px solid var(--line); }
.pool-main { display: flex; min-width: 0; min-height: 0; flex-direction: column; overflow: hidden; }.pool-toolbar { display: flex; flex: 0 0 auto; align-items: center; justify-content: space-between; gap: 16px; min-height: 78px; padding: 12px 15px; border-bottom: 1px solid var(--line); }
.pool-title-row { gap: 8px; }.pool-title-row h3 { font-size: 15px; }.pool-toolbar p { margin-top: 6px; color: #8b97a6; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 10px; }.toolbar-actions { flex-wrap: wrap; justify-content: flex-end; gap: 7px; }.toolbar-actions .el-button + .el-button { margin-left: 0; }
.status-tag, .owner-tag, .availability { display: inline-flex; align-items: center; min-height: 22px; padding: 0 8px; border-radius: 11px; font-size: 10px; }.status-tag.is-online, .availability.is-ok { color: #16895f; background: #eaf8ef; }.status-tag.is-offline, .availability.is-muted { color: #788696; background: #eef2f5; }.owner-tag { color: #50728c; background: #edf4f8; }.availability.is-error { color: #d94b4b; background: #ffeded; }.availability.is-waiting { color: #b86c1f; background: #fff1dd; }
.link-table, .account-table { --el-table-header-bg-color: #f7f9fb; }.link-table :deep(th.el-table__cell), .account-table :deep(th.el-table__cell) { color: #67768a; font-weight: 500; }.member-cell strong, .member-cell small { display: block; }.member-cell small { margin-top: 3px; color: #8d98a7; font-size: 10px; }.muted { color: #9aa4b0; }
.legacy-sync-alert { flex: 0 0 auto; }.member-table-area { min-height: 0; flex: 1 1 auto; overflow: hidden; }.link-table, .account-table { --el-table-header-bg-color: #f7f9fb; }.link-table :deep(th.el-table__cell), .account-table :deep(th.el-table__cell) { color: #67768a; font-weight: 500; }.member-cell strong, .member-cell small { display: block; }.member-cell small { margin-top: 3px; color: #8d98a7; font-size: 10px; }.muted { color: #9aa4b0; }
.remote-id { display: block; overflow: hidden; color: #68778b; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
.link-id-cell { display: grid; gap: 4px; min-width: 0; }
.wecom-url { display: block; overflow: hidden; color: #148f83; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 11px; line-height: 1.4; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; }
@@ -1368,9 +1616,15 @@ h1, h2, h3, p { margin: 0; }
.access-pool-preview p { margin-top: 7px; }
.pool-form-scroll { max-height: 70vh; overflow-y: auto; overflow-x: hidden; padding: 0 10px 6px 2px; }
.pool-form-error { margin-bottom: 18px; }
.batch-config-error { margin-top: 14px; }
.batch-pool-summary { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-top: 14px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: #f8fafb; }
.batch-pool-summary strong { margin-right: 3px; font-size: 12px; }.batch-pool-summary span { padding: 4px 8px; border-radius: 12px; color: #426078; background: #eaf1f5; font-size: 10px; }.batch-pool-summary small { color: #8491a2; font-size: 10px; }
.batch-config-form { margin-top: 16px; }.batch-config-section { margin-bottom: 18px; padding: 16px; border: 1px solid var(--line); border-radius: 9px; background: #fff; }.batch-config-section > h3 { margin: 0 0 14px; color: #303b4d; font-size: 14px; }
.batch-field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }.batch-field { padding: 12px; border: 1px solid #dfe6ec; border-radius: 8px; transition: opacity .2s ease, background .2s ease; }.batch-field.is-disabled { opacity: .58; background: #f7f8fa; }.batch-field :deep(.el-form-item) { margin: 10px 0 0; }
.batch-section-tip { margin: -6px 0 12px; color: #8491a2; font-size: 11px; line-height: 1.6; }.batch-section-selectors { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }.batch-section-selectors :deep(.el-checkbox) { margin-right: 0; }.automation-batch-section :deep(.automation-note) { margin-top: 14px; }
.uploading-save-tip { color: #b88230; font-size: 12px; margin-right: 16px; }
.sync-retry-tip { color: #b88230; font-size: 10px; line-height: 1.6; margin-top: 4px; }
:global(.promotion-pool-dialog) { max-width: calc(100vw - 32px); margin-top: 5vh; }
@media (max-width: 1100px) { .heading-actions { flex-wrap: wrap; justify-content: flex-end; }.metric-grid, .customer-metric-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }.pool-layout { grid-template-columns: 210px minmax(0,1fr); }.pool-toolbar { align-items: flex-start; flex-direction: column; } }
@media (max-width: 760px) { .promotion-page { padding: 10px; }.page-header, .section-heading { align-items: flex-start; flex-direction: column; }.section-heading-actions { width: 100%; justify-content: flex-start; }.update-time { display: none; }.metric-grid, .customer-metric-grid, .form-grid, .rule-form-grid { grid-template-columns: 1fr; }.tab-nav { overflow-x: auto; }.tab-nav button { min-width: 112px; }.tab-content { padding: 14px; }.pool-layout { grid-template-columns: 1fr; }.pool-sidebar { display: flex; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--line); }.pool-select-row { min-width: 220px; }.pool-item { min-width: 190px; }.callback-list > div { grid-template-columns: 1fr 48px; padding: 8px 0; }.callback-list dt { grid-column: 1 / -1; }.install-pool-select { width: 100%; }.customer-heading-actions { width: 100%; justify-content: flex-end; }.customer-filter-bar :deep(.el-form-item) { width: 100%; margin-right: 0; }.customer-filter-bar :deep(.el-form-item__content), .customer-filter-bar .el-select { width: 100%; }.customer-filter-bar .filter-actions :deep(.el-form-item__content) { justify-content: flex-end; }.customer-pagination { align-items: flex-start; flex-direction: column; }.customer-pagination :deep(.el-pagination) { max-width: 100%; flex-wrap: wrap; justify-content: flex-start; }.access-pool-preview > div { grid-template-columns: 1fr; gap: 3px; } }
@media (max-width: 760px) { .promotion-page { padding: 10px; }.page-header, .section-heading { align-items: flex-start; flex-direction: column; }.section-heading-actions { width: 100%; justify-content: flex-start; }.update-time { display: none; }.metric-grid, .customer-metric-grid, .form-grid, .rule-form-grid, .batch-field-grid { grid-template-columns: 1fr; }.tab-nav { overflow-x: auto; }.tab-nav button { min-width: 112px; }.tab-content { padding: 14px; }.pool-layout { height: auto; min-height: 0; grid-template-columns: 1fr; }.pool-sidebar { display: flex; overflow-x: auto; overflow-y: hidden; border-right: 0; border-bottom: 1px solid var(--line); scrollbar-gutter: auto; }.pool-main { overflow: visible; }.member-table-area { height: 420px; min-height: 320px; flex: none; }.pool-select-row { min-width: 220px; }.pool-item { min-width: 190px; }.callback-list > div { grid-template-columns: 1fr 48px; padding: 8px 0; }.callback-list dt { grid-column: 1 / -1; }.install-pool-select { width: 100%; }.customer-heading-actions { width: 100%; justify-content: flex-end; }.customer-filter-bar :deep(.el-form-item) { width: 100%; margin-right: 0; }.customer-filter-bar :deep(.el-form-item__content), .customer-filter-bar .el-select { width: 100%; }.customer-filter-bar .filter-actions :deep(.el-form-item__content) { justify-content: flex-end; }.customer-pagination { align-items: flex-start; flex-direction: column; }.customer-pagination :deep(.el-pagination) { max-width: 100%; flex-wrap: wrap; justify-content: flex-start; }.access-pool-preview > div { grid-template-columns: 1fr; gap: 3px; } }
</style>
+89 -5
View File
@@ -677,6 +677,32 @@
<el-option label="驼奶费用" :value="8" />
</el-select>
</el-form-item>
<el-form-item
v-if="canEditOrderTime && isEditPaymentTimeEditable"
label="支付时间"
prop="payment_time"
>
<el-date-picker
v-model="editOrderForm.payment_time"
type="datetime"
placeholder="请选择支付时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
:clearable="false"
class="w-full"
/>
</el-form-item>
<el-form-item v-if="canEditOrderTime" label="创建时间" prop="create_time">
<el-date-picker
v-model="editOrderForm.create_time"
type="datetime"
placeholder="请选择创建时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
:clearable="false"
class="w-full"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editOrderDialogVisible = false">取消</el-button>
@@ -782,6 +808,7 @@
<script setup lang="ts" name="orderList">
import { computed } from 'vue'
import { usePaging } from '@/hooks/usePaging'
import { hasPermission } from '@/utils/perm'
import {
orderLists,
orderDetail,
@@ -1015,9 +1042,33 @@ const editOrderFormRef = ref()
const editOrderLoading = ref(false)
const editPatientLoading = ref(false)
const editPatientList = ref<any[]>([])
const editOrderForm = ref<{ id: number; patient_id: number | null; order_type: number } | null>(null)
type EditOrderForm = {
id: number
patient_id: number | null
order_type: number
status: number
payment_time: string
create_time: string
}
const editOrderForm = ref<EditOrderForm | null>(null)
const canEditOrderTime = computed(() => hasPermission(['order.order/editTime']))
const isEditPaymentTimeEditable = computed(() => [2, 4].includes(editOrderForm.value?.status ?? 0))
const editOrderRules = {
order_type: [{ required: true, message: '请选择订单类型', trigger: 'change' }]
order_type: [{ required: true, message: '请选择订单类型', trigger: 'change' }],
payment_time: [
{
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (isEditPaymentTimeEditable.value && !value) {
callback(new Error('请选择支付时间'))
return
}
callback()
},
trigger: 'change'
}
],
create_time: [{ required: true, message: '请选择创建时间', trigger: 'change' }]
}
// 搜索患者
@@ -1289,11 +1340,37 @@ const getCreateTypeText = (row: any) => {
}
// 编辑订单
const normalizeOrderDateTime = (value: unknown) => {
if (value === null || value === undefined || value === '' || value === '-') return ''
const raw = String(value).trim()
const canonicalDateTime = raw.match(/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}/)?.[0]
if (canonicalDateTime) return canonicalDateTime.replace('T', ' ')
const numericTimestamp = /^\d{10,13}$/.test(raw) ? Number(raw) : 0
const parsed = new Date(
numericTimestamp
? numericTimestamp < 1_000_000_000_000
? numericTimestamp * 1000
: numericTimestamp
: raw
)
if (Number.isNaN(parsed.getTime())) return ''
const pad = (part: number) => String(part).padStart(2, '0')
return `${parsed.getFullYear()}-${pad(parsed.getMonth() + 1)}-${pad(parsed.getDate())} ${pad(
parsed.getHours()
)}:${pad(parsed.getMinutes())}:${pad(parsed.getSeconds())}`
}
const handleEditOrder = (row: any) => {
editOrderForm.value = {
id: row.id,
patient_id: row.patient_id || null,
order_type: row.order_type
order_type: row.order_type,
status: Number(row.status),
payment_time: [2, 4].includes(Number(row.status)) ? normalizeOrderDateTime(row.payment_time) : '',
create_time: normalizeOrderDateTime(row.create_time)
}
editPatientList.value = row.patient ? [row.patient] : []
editOrderDialogVisible.value = true
@@ -1320,11 +1397,18 @@ const submitEditOrder = async () => {
try {
await editOrderFormRef.value?.validate()
editOrderLoading.value = true
await orderEdit({
const payload: Record<string, unknown> = {
id: editOrderForm.value.id,
patient_id: editOrderForm.value.patient_id ?? 0,
order_type: editOrderForm.value.order_type
})
}
if (canEditOrderTime.value) {
payload.create_time = editOrderForm.value.create_time
if (isEditPaymentTimeEditable.value) {
payload.payment_time = editOrderForm.value.payment_time
}
}
await orderEdit(payload)
feedback.msgSuccess('保存成功')
editOrderDialogVisible.value = false
getLists()
@@ -16,7 +16,8 @@
<code>一键打包</code>
产物一致的安装包并填入打包目录中的 SHA-256Windows 推荐使用
Setup.exe用户点击立即更新后会自动安装并重启macOS 继续使用 ZIP
安装包通常超过 200MB优先传到对象存储 / CDN 后粘贴地址
安装包通常超过 200MB本页上传按钮会直传到已配置的腾讯云 COS也可以
自行上传到其他对象存储 / CDN 后粘贴地址
</div>
</el-alert>
<div class="text-xl font-medium mb-[20px]">升级策略</div>
@@ -125,7 +126,9 @@
<el-form-item label="上传安装包">
<div>
<upload
type="file"
v-perms="['setting.desktop_workstation/setConfig']"
type="desktop_package"
direct
:limit="1"
:multiple="false"
:show-progress="true"
@@ -136,8 +139,8 @@
<el-button type="primary" plain>选择安装包并上传</el-button>
</upload>
<div class="form-tips">
仅建议上传较小的包大文件请先传到对象存储再把地址和 SHA-256
填到上方Windows 自动安装程序必须使用 HTTPS 地址并开启证书校验
安装包将分片直传腾讯云 COS不经过业务服务器支持 EXE / ZIP最大
2GBWindows 自动安装程序必须使用 HTTPS 地址并开启证书校验
</div>
</div>
</el-form-item>
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -3,9 +3,9 @@
__all__ = ["DEBUG_MODE", "ONLINE_API_BASE_URL", "__version__"]
# Single source of truth for runtime, package, installer, and executable versions.
__version__ = "1.2.0"
__version__ = "1.4.1"
# 调试模式开启时,登录页显示“演示模式”和“服务器设置”。
# 正式发布请保持 False;此时程序只使用下面配置的线上域名。
DEBUG_MODE = True
DEBUG_MODE = False
ONLINE_API_BASE_URL = "https://admin.zhenyangtang.com.cn"
@@ -758,9 +758,11 @@ class DiagnosisDialog(QDialog):
parent: QWidget | None = None,
*,
permissions: Any = None,
embedded: bool = False,
) -> None:
super().__init__(parent)
self.repository = repository
self._embedded = bool(embedded)
self.permissions = (
permissions
if permissions is not None
@@ -882,11 +884,17 @@ class DiagnosisDialog(QDialog):
)
self.setObjectName("DiagnosisDialogRoot")
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
if self._embedded:
self.setWindowFlags(Qt.WindowType.Widget)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
self.setMinimumSize(0, 0)
self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
else:
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setMinimumSize(760, 520)
self.resize(1024, 640)
self.setWindowTitle("患者信息详情")
self.setMinimumSize(760, 520)
self.resize(1024, 640)
self.setStyleSheet(DIAGNOSIS_QSS)
self.view_stack = QStackedLayout(self)
@@ -911,16 +919,25 @@ class DiagnosisDialog(QDialog):
root = QVBoxLayout(page)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(0)
self.readonly_header = QWidget(page)
self.readonly_header.setObjectName("DiagnosisReadonlyHeader")
header_layout = QVBoxLayout(self.readonly_header)
header_layout.setContentsMargins(16, 16, 16, 0)
header_layout.setSpacing(0)
self.readonly_hero = self._build_readonly_hero()
header_layout.addWidget(self.readonly_hero)
root.addWidget(self.readonly_header)
self.readonly_scroll = QScrollArea()
self.readonly_scroll.setObjectName("DiagnosisReadonlyScroll")
self.readonly_scroll.setWidgetResizable(True)
self.readonly_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.readonly_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.readonly_scroll.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.readonly_scroll.verticalScrollBar().setSingleStep(28)
content = QWidget()
self.readonly_content_layout = QVBoxLayout(content)
self.readonly_content_layout.setContentsMargins(16, 16, 16, 16)
self.readonly_content_layout.setSpacing(16)
self.readonly_hero = self._build_readonly_hero()
self.readonly_content_layout.addWidget(self.readonly_hero)
self.readonly_error = QFrame()
self.readonly_error.setObjectName("DiagnosisReadonlyErrorCard")
self.readonly_error.setProperty("diagnosisReadonlyCard", True)
@@ -981,16 +998,16 @@ class DiagnosisDialog(QDialog):
left_layout = QHBoxLayout(self.readonly_hero_left)
left_layout.setContentsMargins(0, 0, 0, 0)
left_layout.setSpacing(12)
back = QPushButton("← 返回")
back.setObjectName("DiagnosisReadonlyBack")
back.setCursor(Qt.CursorShape.PointingHandCursor)
back.setStyleSheet(
self.readonly_back_button = QPushButton(" 收起资料" if self._embedded else " 返回")
self.readonly_back_button.setObjectName("DiagnosisReadonlyBack")
self.readonly_back_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.readonly_back_button.setStyleSheet(
"QPushButton{height:32px;padding:0 8px;border:0;background:transparent;"
"color:#5265F6;font-size:13px;font-weight:500;}"
"QPushButton:hover,QPushButton:focus{background:#F0F2FF;border-radius:6px;}"
)
back.clicked.connect(self.reject)
left_layout.addWidget(back)
self.readonly_back_button.clicked.connect(self.reject)
left_layout.addWidget(self.readonly_back_button)
title = QLabel("患者信息详情")
title.setObjectName("DiagnosisReadonlyTitle")
left_layout.addWidget(title)
@@ -1009,6 +1026,13 @@ class DiagnosisDialog(QDialog):
self.readonly_status.setObjectName("DiagnosisReadonlyStatus")
self.readonly_status.setProperty("severity", "neutral")
right_layout.addWidget(self.readonly_status)
self.readonly_close_button = QPushButton("×")
self.readonly_close_button.setObjectName("DiagnosisCloseButton")
self.readonly_close_button.setToolTip("关闭诊单详情")
self.readonly_close_button.setAccessibleName("关闭诊单详情")
self.readonly_close_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.readonly_close_button.clicked.connect(self.reject)
right_layout.addWidget(self.readonly_close_button)
layout.addWidget(self.readonly_hero_left, 0, 0)
layout.addWidget(self.readonly_hero_right, 0, 1, Qt.AlignmentFlag.AlignRight)
layout.setColumnStretch(0, 1)
@@ -1790,6 +1814,8 @@ class DiagnosisDialog(QDialog):
label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
def _sync_host_geometry(self) -> None:
if self._embedded:
return
owner = self._owner
if owner is None:
if self.width() < 760 or self.height() < 520:
@@ -1823,6 +1849,8 @@ class DiagnosisDialog(QDialog):
layout.addWidget(self.readonly_hero_right, 0, 1, Qt.AlignmentFlag.AlignRight)
def _install_owner_filter(self) -> None:
if self._embedded:
return
if self._owner is not None and not self._owner_filter_installed:
self._owner.installEventFilter(self)
self._owner_filter_installed = True
@@ -1830,6 +1858,10 @@ class DiagnosisDialog(QDialog):
def _rebind_owner(self) -> None:
"""Resolve the live Shell window for every open/show cycle."""
if self._embedded:
self._owner = None
self._owner_filter_installed = False
return
parent = self.parentWidget()
candidate = parent.window() if parent is not None else None
if candidate is self:
@@ -1852,8 +1884,9 @@ class DiagnosisDialog(QDialog):
super().resizeEvent(event)
def showEvent(self, event: Any) -> None:
self._rebind_owner()
self._sync_host_geometry()
if not self._embedded:
self._rebind_owner()
self._sync_host_geometry()
self._update_drawer_geometry()
self._reflow_readonly_hero()
super().showEvent(event)
@@ -1938,10 +1971,12 @@ class DiagnosisDialog(QDialog):
*,
editable: bool = False,
seed: Any = None,
authoritative_detail: Any = None,
view_only: bool = False,
modeless: bool = False,
auto_show: bool = True,
) -> None:
"""Open immediately, then replace the seed with authoritative server data."""
"""Prepare a diagnosis view, optionally showing it immediately."""
self._rebind_owner()
for player in list(self._recording_players):
@@ -1986,13 +2021,13 @@ class DiagnosisDialog(QDialog):
self._daily_todo_status = None
self._orders_page = 1
self._orders_total = 0
self._detail = seed
self._detail = authoritative_detail if authoritative_detail is not None else seed
self.save_button.set_state("idle")
self.refresh_permissions()
self.view_stack.setCurrentWidget(
self.readonly_page if self._standalone_readonly else self.drawer_overlay
)
self.setModal(not modeless and not self._standalone_readonly)
self.setModal(False if self._embedded else not modeless and not self._standalone_readonly)
self.setWindowTitle(
"患者信息详情"
if self._standalone_readonly
@@ -2018,15 +2053,38 @@ class DiagnosisDialog(QDialog):
)
self._clear_tables()
self._clear_message()
if seed is not None:
self._render(seed, [], [])
if self._detail is not None:
self._render(self._detail, [], [])
self._sync_form_interactivity()
self._sync_save_button()
self._sync_host_geometry()
if auto_show:
self.show()
if not self._embedded:
self.raise_()
if authoritative_detail is not None:
diagnosis = get_value(authoritative_detail, "diagnosis", None) or authoritative_detail
patient = get_value(authoritative_detail, "patient", None) or {}
self._patient_id = _int(
first_value(
diagnosis,
"patient_id",
"source_patient_id",
default=first_value(patient, "patient_id", "id", default=0),
),
0,
)
self._authoritative_detail_loaded = True
self._show_authoritative_content(True)
self._clear_message()
self._set_loading(False)
if self._standalone_readonly:
self._load_visible_readonly_sections()
else:
self._ensure_tab_loaded(self._current_tab_key())
return
self._show_message("正在加载权威诊单详情…", "info")
self._set_loading(True)
self._sync_host_geometry()
self.show()
self.raise_()
self._start_detail_load()
def _start_detail_load(self) -> None:
@@ -16,7 +16,7 @@ import re
import sys
import tempfile
from collections.abc import Iterable, Mapping, Sequence
from datetime import date, datetime
from datetime import date
from pathlib import Path
from time import monotonic
from typing import Any
@@ -80,6 +80,7 @@ from PySide6.QtWidgets import (
QScrollArea,
QSizePolicy,
QSpinBox,
QSplitter,
QTableWidget,
QTableWidgetItem,
QTabWidget,
@@ -104,6 +105,7 @@ from ..widgets import (
run_async,
show_toast,
)
from .diagnosis import DiagnosisDialog as _StructuredDiagnosisDialog
PRESCRIPTION_DRAWER_QSS = r"""
QFrame#PrescriptionDrawerSurface {
@@ -2697,6 +2699,7 @@ class PrescriptionEditorDialog(QDialog):
self._source = _mapping(prescription)
self._loading_data = False
self._linked_order_generation = 0
self._diagnosis_view: _StructuredDiagnosisDialog | None = None
self._prescribing_creator_id = _int(
first_value(
prescription,
@@ -2709,17 +2712,37 @@ class PrescriptionEditorDialog(QDialog):
self.setModal(True)
self.setMinimumSize(420, 600)
self.resize(self.DRAWER_WIDTH, 900)
root = QVBoxLayout(self)
root = QHBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(0)
self.workspace_splitter = QSplitter(Qt.Orientation.Horizontal, self)
self.workspace_splitter.setObjectName("PrescriptionWorkspaceSplitter")
self.workspace_splitter.setChildrenCollapsible(False)
self.workspace_splitter.setHandleWidth(1)
root.addWidget(self.workspace_splitter)
self.diagnosis_host = QFrame(self.workspace_splitter)
self.diagnosis_host.setObjectName("PrescriptionDiagnosisPane")
self.diagnosis_host.setMinimumWidth(300)
diagnosis_layout = QVBoxLayout(self.diagnosis_host)
diagnosis_layout.setContentsMargins(0, 0, 0, 0)
diagnosis_layout.setSpacing(0)
self.diagnosis_layout = diagnosis_layout
self.workspace_splitter.addWidget(self.diagnosis_host)
self.diagnosis_host.hide()
self.drawer_surface = QFrame()
self.drawer_surface.setObjectName("PrescriptionDrawerSurface")
self.drawer_surface.setStyleSheet(_prescription_drawer_qss())
self.drawer_surface.setMinimumWidth(self.minimumWidth())
self.drawer_surface.setMaximumWidth(self.DRAWER_WIDTH)
surface_layout = QVBoxLayout(self.drawer_surface)
surface_layout.setContentsMargins(0, 0, 0, 0)
surface_layout.setSpacing(0)
root.addWidget(self.drawer_surface)
self.workspace_splitter.addWidget(self.drawer_surface)
self.workspace_splitter.setStretchFactor(0, 1)
self.workspace_splitter.setStretchFactor(1, 0)
self.header = self._build_header()
surface_layout.addWidget(self.header)
@@ -2920,7 +2943,7 @@ class PrescriptionEditorDialog(QDialog):
self.diagnosis_button.setProperty("size", "small")
self.diagnosis_button.setVisible(diagnosis_id > 0)
self.diagnosis_button.clicked.connect(
lambda _checked=False, value=diagnosis_id: self.diagnosis_requested.emit(value)
lambda _checked=False, value=diagnosis_id: self._toggle_diagnosis_view(value)
)
diagnosis_layout.addWidget(self.diagnosis_button)
self.diagnosis_id_hint = QLabel(f"关联诊单 #{diagnosis_id}" if diagnosis_id > 0 else "")
@@ -3237,15 +3260,58 @@ class PrescriptionEditorDialog(QDialog):
target = targets[max(0, min(len(targets) - 1, index))]
self.body_scroll.ensureWidgetVisible(target, 0, 24)
def _toggle_diagnosis_view(self, diagnosis_id: int) -> None:
if self.diagnosis_host.isVisible():
if self._diagnosis_view is not None:
self._diagnosis_view.reject()
return
if diagnosis_id <= 0:
return
if not has_permission(self.permissions, "tcm.diagnosis/readonlyDetail", default=True):
self.context_banner.show_message("无权查看诊单详情。", "danger")
return
if self._diagnosis_view is None:
self._diagnosis_view = _StructuredDiagnosisDialog(
self.repository,
self.diagnosis_host,
permissions=self.permissions,
embedded=True,
)
self._diagnosis_view.finished.connect(
lambda _result, view=self._diagnosis_view: self._diagnosis_view_finished(view)
)
self.diagnosis_layout.addWidget(self._diagnosis_view)
self.diagnosis_host.show()
self.diagnosis_button.setText("收起患者诊单")
self._fit_drawer_geometry()
self._diagnosis_view.open_for(diagnosis_id, editable=False)
def _diagnosis_view_finished(self, view: _StructuredDiagnosisDialog) -> None:
if view is not self._diagnosis_view:
return
self.diagnosis_host.hide()
self.diagnosis_button.setText("查看患者诊单详情")
self._fit_drawer_geometry()
def _fit_drawer_geometry(self) -> None:
parent = self.parentWidget()
if parent is None:
return
anchor = parent.window()
origin = anchor.mapToGlobal(QPoint(0, 0))
width = min(self.DRAWER_WIDTH, max(self.minimumWidth(), anchor.width()))
expanded = self.diagnosis_host.isVisible()
width = (
anchor.width()
if expanded
else min(self.DRAWER_WIDTH, max(self.minimumWidth(), anchor.width()))
)
height = max(self.minimumHeight(), anchor.height())
self.setGeometry(origin.x() + anchor.width() - width, origin.y(), width, height)
if expanded:
editor_width = min(self.DRAWER_WIDTH, max(520, round(width * 0.55)))
self.workspace_splitter.setSizes([max(300, width - editor_width - 1), editor_width])
else:
self.workspace_splitter.setSizes([0, width])
def showEvent(self, event: Any) -> None:
super().showEvent(event)
@@ -3836,6 +3902,11 @@ class PrescriptionEditorDialog(QDialog):
self.validation.clear()
super().accept()
def done(self, result: int) -> None:
if self._diagnosis_view is not None and self._diagnosis_view.isVisible():
self._diagnosis_view.reject()
super().done(result)
class PatchPatientDialog(QDialog):
"""Narrow patient identity correction that preserves audit state."""
@@ -5102,8 +5173,8 @@ class PrescriptionDetailDialog(QDialog):
painter.end()
class DiagnosisDetailDialog(QDialog):
"""Read-only diagnosis view preserving the important admin tab boundaries."""
class DiagnosisDetailDialog(_StructuredDiagnosisDialog):
"""Compatibility entry that renders prescription-linked diagnoses with the shared UI."""
def __init__(
self,
@@ -5113,180 +5184,29 @@ class DiagnosisDetailDialog(QDialog):
repository: Any = None,
permissions: Any = None,
) -> None:
super().__init__(parent)
source = _mapping(diagnosis)
self.repository = repository
self.permissions = permissions
self._order_detail_generation = 0
self._order_detail_order_id = 0
self._order_detail_table: QTableWidget | None = None
self._order_detail_button: QPushButton | None = None
if repository is None:
raise ValueError("repository is required to show diagnosis details")
super().__init__(repository, parent, permissions=permissions)
self.setWindowTitle("诊单详情(只读)")
self.resize(880, 700)
root = QVBoxLayout(self)
tabs = QTabWidget()
groups = (
(
"病历",
(
"id",
"patient_id",
"patient_name",
"gender",
"age",
"phone",
"chief_complaint",
"present_illness",
"past_history",
"diagnosis",
"syndrome",
"treatment",
),
source = _mapping(diagnosis)
nested = _mapping(source.get("diagnosis"))
diagnosis_id = _int(
first_value(
nested,
"id",
"diagnosis_id",
default=first_value(source, "id", "diagnosis_id"),
),
("医生备注", ("doctor_notes", "doctor_note", "notes")),
("日常记录", ("daily_records", "blood_records")),
("处方", ("prescriptions", "case_records")),
("沟通与指派", ("call_records", "chat_records", "assign_logs", "appointments")),
0,
)
for title, keys in groups:
browser = QTextBrowser()
rows = []
for key in keys:
value = source.get(key)
if value in (None, "", [], {}):
continue
rendered = (
json.dumps(value, ensure_ascii=False, indent=2, default=str)
if isinstance(value, (Mapping, list, tuple))
else str(value)
)
rows.append(f"<h3>{html.escape(key)}</h3><pre>{html.escape(rendered)}</pre>")
browser.setHtml("".join(rows) or "<p>暂无数据</p>")
tabs.addTab(browser, title)
tabs.addTab(self._build_orders_tab(source), "业务订单")
root.addWidget(tabs, 1)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
buttons.rejected.connect(self.reject)
root.addWidget(buttons)
def _build_orders_tab(self, source: Mapping[str, Any]) -> QWidget:
host = QWidget()
layout = QVBoxLayout(host)
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(8)
rows: list[Any] = []
for key in ("prescription_orders", "orders"):
value = source.get(key)
if isinstance(value, list):
rows.extend(value)
latest = source.get("latest_prescription_order")
if isinstance(latest, Mapping) and latest:
latest_id = _int(first_value(latest, "id", "order_id"), 0)
if latest_id and not any(
_int(first_value(row, "id", "order_id"), 0) == latest_id for row in rows
):
rows.insert(0, latest)
if not rows:
empty = QLabel("暂无关联业务订单")
empty.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(empty, 1)
return host
table = QTableWidget(len(rows), 6)
table.setHorizontalHeaderLabels(
["订单号", "金额", "履约状态", "收货人", "手机", "创建时间"]
)
table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
table.verticalHeader().hide()
table.horizontalHeader().setStretchLastSection(True)
self._order_detail_table = table
for row_index, row in enumerate(rows):
values = (
first_value(row, "order_no", "sn", "id"),
first_value(row, "amount", "effective_amount"),
first_value(row, "fulfillment_status_text", "status_text", "status"),
first_value(row, "recipient_name", "patient_name"),
first_value(row, "recipient_phone", "phone"),
first_value(row, "create_time_text", "create_time"),
)
for column, value in enumerate(values):
item = QTableWidgetItem(display_text(value))
item.setData(Qt.ItemDataRole.UserRole, row)
table.setItem(row_index, column, item)
layout.addWidget(table, 1)
actions = QHBoxLayout()
view = QPushButton("查看订单详情")
view.setProperty("variant", "primary")
self._order_detail_button = view
view.clicked.connect(self._open_selected_order)
table.itemDoubleClicked.connect(lambda _item: self._open_selected_order())
actions.addWidget(view)
actions.addStretch(1)
layout.addLayout(actions)
return host
def _set_order_detail_loading(self, loading: bool) -> None:
if self._order_detail_table is not None:
self._order_detail_table.setEnabled(not loading)
if self._order_detail_button is not None:
self._order_detail_button.setEnabled(not loading)
def _open_selected_order(self) -> None:
table = self._order_detail_table
if table is None:
return
row = table.currentRow()
item = table.item(row, 0) if row >= 0 else None
order = item.data(Qt.ItemDataRole.UserRole) if item is not None else None
if order is None:
return
order_id = _int(first_value(order, "id", "order_id"), 0)
self._order_detail_generation += 1
generation = self._order_detail_generation
self._order_detail_order_id = order_id
getter = getattr(self.repository, "get_prescription_order", None)
if order_id <= 0 or not callable(getter):
self._set_order_detail_loading(False)
self._present_order_detail(order, order_id)
return
self._set_order_detail_loading(True)
run_async(
lambda: getter(order_id),
on_success=lambda result: self._order_detail_success(result, order_id, generation),
on_error=lambda error: self._order_detail_error(error, order, order_id, generation),
on_finished=lambda: self._order_detail_finished(order_id, generation),
)
def _order_detail_success(self, order: Any, order_id: int, generation: int) -> None:
if generation != self._order_detail_generation or order_id != self._order_detail_order_id:
return
self._present_order_detail(order, order_id)
def _order_detail_error(
self,
_error: Exception,
fallback_order: Any,
order_id: int,
generation: int,
) -> None:
if generation != self._order_detail_generation or order_id != self._order_detail_order_id:
return
self._present_order_detail(fallback_order, order_id)
def _order_detail_finished(self, order_id: int, generation: int) -> None:
if generation == self._order_detail_generation and order_id == self._order_detail_order_id:
self._set_order_detail_loading(False)
def _present_order_detail(self, order: Any, order_id: int) -> None:
from .diagnosis import present_order_detail
present_order_detail(
self.window() if self.window() is not None else self,
order,
order_id=order_id,
permissions=self.permissions,
exec_=True,
if diagnosis_id <= 0:
raise ValueError("diagnosis detail is missing a valid diagnosis id")
self.open_for(
diagnosis_id,
editable=False,
seed=source,
authoritative_detail=source,
auto_show=False,
)
+17
View File
@@ -639,6 +639,23 @@ def test_readonly_is_an_independent_vertical_page_flow(
application.processEvents()
def test_readonly_scroll_keeps_close_controls_reachable(application: QApplication) -> None:
dialog = _open_dialog(application, (760, 520), mode="readonly")
header_before = dialog.readonly_header.geometry()
scroll_bar = dialog.readonly_scroll.verticalScrollBar()
assert scroll_bar.maximum() > 0
assert dialog.readonly_close_button.isVisibleTo(dialog)
scroll_bar.setValue(scroll_bar.maximum())
application.processEvents()
assert dialog.readonly_header.geometry() == header_before
assert dialog.readonly_close_button.isVisibleTo(dialog)
dialog.readonly_close_button.click()
application.processEvents()
assert not dialog.isVisible()
@pytest.mark.parametrize("size", [(1024, 640), (1440, 900)])
@pytest.mark.parametrize("mode", ["edit", "viewOnly"])
def test_drawer_is_full_height_rtl_and_sixty_percent_wide(
+93 -94
View File
@@ -288,122 +288,121 @@ def test_paid_order_response_is_bound_to_active_diagnosis_and_blocks_save(
application.processEvents()
def test_diagnosis_order_detail_lookup_is_queued_before_repository_call(
def test_prescription_diagnosis_detail_uses_shared_structured_readonly_ui(
application: QApplication,
monkeypatch: pytest.MonkeyPatch,
) -> None:
queued: list[tuple[Any, dict[str, Any]]] = []
requested: list[int] = []
shown: list[tuple[int, str]] = []
class Repository:
def get_prescription_order(self, order_id: int) -> dict[str, Any]:
requested.append(order_id)
return {"id": order_id, "order_no": f"DETAIL-{order_id}"}
def get_doctor_notes(self, diagnosis_id: int) -> list[dict[str, Any]]:
assert diagnosis_id == 745
return []
def queue_async(function: Any, **options: Any) -> object:
queued.append((function, options))
return object()
def present_order_detail(
_host: Any,
order: dict[str, Any],
*,
order_id: int,
permissions: Any,
exec_: bool,
) -> None:
del permissions, exec_
shown.append((order_id, order["order_no"]))
monkeypatch.setattr(dialog_module, "run_async", queue_async)
monkeypatch.setattr(diagnosis_module, "present_order_detail", present_order_detail)
monkeypatch.setattr(diagnosis_module, "run_async", _immediate_async)
dialog = DiagnosisDetailDialog(
{"orders": [{"id": 17, "order_no": "ROW-17"}]},
{
"diagnosis": {
"id": 745,
"patient_id": 745,
"patient_name": "庄志芳",
"phone": "13823549442",
"id_card": "440305196701011234",
"gender": 0,
"age": 59,
"chief_complaint": "睡眠不好、出汗多",
"past_history": ["高血压", "高脂血症"],
},
"patient": {"id": 745},
},
repository=Repository(),
permissions=PermissionSet(["tcm.diagnosis/readonlyDetail"]),
)
table = dialog._order_detail_table
button = dialog._order_detail_button
assert table is not None
assert button is not None
table.setCurrentCell(0, 0)
button.click()
assert len(queued) == 1
assert requested == []
assert shown == []
assert not table.isEnabled()
assert not button.isEnabled()
function, options = queued[0]
options["on_success"](function())
options["on_finished"]()
assert requested == [17]
assert shown == [(17, "DETAIL-17")]
assert table.isEnabled()
assert button.isEnabled()
assert isinstance(dialog, DiagnosisDialog)
assert dialog.view_stack.currentWidget() is dialog.readonly_page
assert dialog.edit_fields["chief_complaint"].toPlainText() == "睡眠不好、出汗多"
assert dialog.summary_fields["phone"].text() == "138****9442"
assert dialog.case_grid.isVisibleTo(dialog)
assert not dialog.findChildren(dialog_module.QTextBrowser)
dialog.close()
application.processEvents()
def test_diagnosis_order_detail_ignores_stale_result_and_keeps_row_fallback(
@pytest.mark.parametrize("owner_width", [1024, 1440, 1710])
def test_prescription_editor_embeds_scrollable_diagnosis_beside_editable_form(
application: QApplication,
monkeypatch: pytest.MonkeyPatch,
owner_width: int,
) -> None:
queued: list[dict[str, Any]] = []
shown: list[tuple[int, str]] = []
class Repository:
def get_diagnosis_detail(
self, diagnosis_id: int, *, readonly: bool = False
) -> dict[str, Any]:
assert readonly
return {
"diagnosis": {
"id": diagnosis_id,
"patient_id": 745,
"patient_name": "庄志芳",
"chief_complaint": "睡眠不好、出汗多",
}
}
def queue_async(_function: Any, **options: Any) -> object:
queued.append(options)
return object()
def get_doctor_notes(self, _diagnosis_id: int) -> list[dict[str, Any]]:
return []
def present_order_detail(
_host: Any,
order: dict[str, Any],
*,
order_id: int,
permissions: Any,
exec_: bool,
) -> None:
del permissions, exec_
shown.append((order_id, order["order_no"]))
repository = SimpleNamespace(get_prescription_order=lambda order_id: {"id": order_id})
monkeypatch.setattr(dialog_module, "run_async", queue_async)
monkeypatch.setattr(diagnosis_module, "present_order_detail", present_order_detail)
dialog = DiagnosisDetailDialog(
{
"orders": [
{"id": 21, "order_no": "ROW-21"},
{"id": 22, "order_no": "ROW-22"},
]
},
repository=repository,
monkeypatch.setattr(diagnosis_module, "run_async", _immediate_async)
owner = QDialog()
owner.resize(owner_width, 720)
owner.show()
repository = Repository()
editor = PrescriptionEditorDialog(
repository,
{"diagnosis_id": 745, "patient_name": "庄志芳"},
mode="edit",
current_user=SimpleNamespace(id=9, name="周医生"),
permissions=PermissionSet(["tcm.diagnosis/readonlyDetail"]),
parent=owner,
)
table = dialog._order_detail_table
button = dialog._order_detail_button
assert table is not None
assert button is not None
editor.show()
application.processEvents()
assert QApplication.activeModalWidget() is editor
assert editor.width() == editor.DRAWER_WIDTH
table.setCurrentCell(0, 0)
dialog._open_selected_order()
table.setCurrentCell(1, 0)
dialog._open_selected_order()
assert len(queued) == 2
editor.diagnosis_button.click()
application.processEvents()
queued[0]["on_success"]({"id": 21, "order_no": "STALE-21"})
queued[0]["on_finished"]()
assert shown == []
assert not table.isEnabled()
assert not button.isEnabled()
detail = editor._diagnosis_view
assert detail is not None
assert QApplication.activeModalWidget() is editor
assert not detail.isWindow()
assert detail.parentWidget() is editor.diagnosis_host
assert editor.diagnosis_host.isVisibleTo(editor)
assert editor.drawer_surface.isVisibleTo(editor)
assert editor.width() == owner.width()
assert editor.diagnosis_host.geometry().right() < editor.drawer_surface.geometry().left()
scroll_bar = detail.readonly_scroll.verticalScrollBar()
assert scroll_bar.maximum() > 0
scroll_bar.setValue(scroll_bar.maximum())
assert scroll_bar.value() == scroll_bar.maximum()
queued[1]["on_error"](RuntimeError("detail unavailable"))
queued[1]["on_finished"]()
assert shown == [(22, "ROW-22")]
assert table.isEnabled()
assert button.isEnabled()
dialog.close()
editor.patient_name.setText("庄志芳(已核对)")
assert editor.patient_name.isEnabled()
assert editor.payload()["patient_name"] == "庄志芳(已核对)"
detail.readonly_close_button.click()
application.processEvents()
assert editor.isVisible()
assert not editor.diagnosis_host.isVisible()
assert editor.width() == editor.DRAWER_WIDTH
assert editor.patient_name.text() == "庄志芳(已核对)"
editor.diagnosis_button.click()
application.processEvents()
assert editor.diagnosis_host.isVisibleTo(editor)
editor.reject()
application.processEvents()
assert not editor.isVisible()
assert not detail.isVisible()
owner.close()
application.processEvents()
@@ -15,6 +15,7 @@
namespace app\adminapi\controller;
use app\common\cache\AdminAuthCache;
use app\common\service\DirectUploadService;
use app\common\service\UploadService;
use Exception;
@@ -86,7 +87,12 @@ class UploadController extends BaseAdminController
{
$type = trim((string)$this->request->post('type', 'video'));
try {
$result = DirectUploadService::issueCredentials($type);
$this->assertDirectUploadPermission($type);
$result = DirectUploadService::issueCredentials(
$type,
$this->adminId,
trim((string)$this->request->post('name', ''))
);
return $this->success('ok', $result);
} catch (Exception $e) {
return $this->fail($e->getMessage());
@@ -100,8 +106,10 @@ class UploadController extends BaseAdminController
public function ossConfirm()
{
try {
$type = trim((string)$this->request->post('type', 'video'));
$this->assertDirectUploadPermission($type);
$result = DirectUploadService::confirm([
'type' => trim((string)$this->request->post('type', 'video')),
'type' => $type,
'key' => trim((string)$this->request->post('key', '')),
'name' => trim((string)$this->request->post('name', '')),
'size' => (int)$this->request->post('size', 0),
@@ -115,4 +123,22 @@ class UploadController extends BaseAdminController
}
}
/**
* 安装包属于发布能力,不能沿用普通素材上传的“登录即放行”。
* @throws Exception
*/
private function assertDirectUploadPermission(string $type): void
{
if ($type !== DirectUploadService::TYPE_DESKTOP_PACKAGE
|| (int)($this->adminInfo['root'] ?? 0) === 1) {
return;
}
$permissions = (new AdminAuthCache($this->adminId))->getAdminUri() ?? [];
$permissions = array_map('strtolower', $permissions);
if (!in_array('setting.desktop_workstation/setconfig', $permissions, true)) {
throw new Exception('权限不足,无法上传医生工作站安装包');
}
}
}
@@ -78,6 +78,19 @@ class WecomPromotionController extends BaseAdminController
)));
}
public function batchUpdatePools()
{
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
return $this->run(fn () => $this->success('分流方案配置已批量更新', WecomPromotionLogic::batchUpdatePools(
$this->request->post(),
$this->adminId,
$this->adminInfo
)));
}
public function saveWidget()
{
if (!$this->hasPagePermission()) {
@@ -6,6 +6,7 @@ namespace app\adminapi\controller\order;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\lists\order\OrderLists;
use app\adminapi\logic\auth\AuthLogic;
use app\adminapi\logic\order\OrderActionLogLogic;
use app\adminapi\logic\order\OrderLogic;
use app\adminapi\validate\order\OrderValidate;
@@ -17,6 +18,8 @@ use app\adminapi\validate\order\OrderValidate;
*/
class OrderController extends BaseAdminController
{
private const EDIT_TIME_PERMISSION = 'order.order/editTime';
/**
* @notes 订单列表
* @return \think\response\Json
@@ -341,13 +344,21 @@ class OrderController extends BaseAdminController
}
/**
* @notes 编辑订单(关联患者、订单类型)
* @notes 编辑订单(关联患者、订单类型、支付时间、创建时间
* 权限:超管或指定角色组可修改任意订单;其他用户只能修改自己创建的订单
* @return \think\response\Json
*/
public function edit()
{
$params = (new OrderValidate())->post()->goCheck('edit');
$hasTimeParams = array_key_exists('payment_time', $params)
|| array_key_exists('create_time', $params);
if ($hasTimeParams) {
if (!$this->canEditOrderTime()) {
return $this->fail('无权限修改订单支付时间或创建时间');
}
$params = (new OrderValidate())->post()->goCheck('edit_time');
}
$orderId = (int)$params['id'];
$order = \app\common\model\Order::find($orderId);
if (!$order) {
@@ -357,11 +368,15 @@ class OrderController extends BaseAdminController
return $this->fail('无权限修改此订单');
}
$result = OrderLogic::edit($orderId, $params);
$result = OrderLogic::edit($orderId, $params, $hasTimeParams);
if (!$result) {
return $this->fail(OrderLogic::getError());
}
$this->logOrderAction($orderId, 'edit', '编辑患者/订单类型等');
$this->logOrderAction(
$orderId,
'edit',
$hasTimeParams ? '编辑患者/订单类型/支付时间/创建时间等' : '编辑患者/订单类型等'
);
return $this->success('编辑成功');
}
@@ -382,6 +397,18 @@ class OrderController extends BaseAdminController
return (int)$order->creator_id === $this->adminId;
}
/**
* @notes 是否拥有支付单时间修正权限
*/
private function canEditOrderTime(): bool
{
if (!empty($this->adminInfo['root']) && (int)$this->adminInfo['root'] === 1) {
return true;
}
return in_array(self::EDIT_TIME_PERMISSION, AuthLogic::getAuthByAdminId($this->adminId), true);
}
/**
* @notes 支付订单
* @return \think\response\Json
@@ -4,16 +4,19 @@ declare(strict_types=1);
namespace app\adminapi\controller\qywx;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\lists\qywx\CustomerLists;
use app\adminapi\logic\qywx\CustomerLogic;
use app\adminapi\validate\qywx\CustomerValidate;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\lists\qywx\CustomerLists;
use app\adminapi\logic\auth\AuthLogic;
use app\adminapi\logic\qywx\CustomerLogic;
use app\adminapi\validate\qywx\CustomerValidate;
/**
* 企业微信客户管理控制器
*/
class CustomerController extends BaseAdminController
{
class CustomerController extends BaseAdminController
{
private const DELETE_PERMISSION = 'qywx.customer/delete';
/**
* @notes 客户列表
*/
@@ -25,16 +28,34 @@ class CustomerController extends BaseAdminController
/**
* @notes 同步企业微信客户
*/
public function sync()
{
public function sync()
{
$result = CustomerLogic::triggerBackgroundSync();
if ($result === false) {
return $this->fail(CustomerLogic::getError());
}
$msg = is_array($result) && isset($result['message']) ? (string) $result['message'] : '已提交同步';
return $this->success($msg, $result);
}
return $this->success($msg, $result);
}
/**
* @notes 删除一条本地企业微信客户同步记录
*/
public function delete()
{
// 显式鉴权,避免权限菜单迁移漏执行时被通用中间件当成“未受控 URI”放行。
if (!$this->canDeleteCustomer()) {
return $this->fail('权限不足,无法删除企业微信客户');
}
$params = (new CustomerValidate())->post()->goCheck('delete');
if (!CustomerLogic::deleteCustomer((int) $params['id'])) {
return $this->fail(CustomerLogic::getError());
}
return $this->success('删除成功');
}
/**
* @notes 获取统计信息
@@ -84,13 +105,22 @@ class CustomerController extends BaseAdminController
/**
* @notes 保存同步设置
*/
public function saveSyncSettings()
public function saveSyncSettings()
{
$params = (new CustomerValidate())->post()->goCheck('syncSettings');
$result = CustomerLogic::saveSyncSettings($params);
if ($result === false) {
return $this->fail(CustomerLogic::getError());
}
return $this->success('保存成功');
}
}
return $this->success('保存成功');
}
private function canDeleteCustomer(): bool
{
if ((int) ($this->adminInfo['root'] ?? 0) === 1) {
return true;
}
return in_array(self::DELETE_PERMISSION, AuthLogic::getAuthByAdminId($this->adminId), true);
}
}
@@ -227,6 +227,7 @@ class AuthMiddleware
'firstvisit.wecompromotion/uploadwelcomemedia',
'firstvisit.wecompromotion/overview',
'firstvisit.wecompromotion/savepool',
'firstvisit.wecompromotion/batchupdatepools',
'firstvisit.wecompromotion/savewidget',
'firstvisit.wecompromotion/batchsetoperators',
'firstvisit.wecompromotion/deletepool',
+210 -18
View File
@@ -230,20 +230,49 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
$query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' <= ?', [$endTs]);
}
return false;
}
private function baseQuery()
return false;
}
/**
* 按客户当前跟进关系中的添加方式筛选。
*
* follow_users 由同步逻辑使用 json_encode 写入,匹配数字值及历史字符串值;
* 数字后必须紧跟逗号或对象结束符,避免 add_way=1 误命中 16
*/
private function applyAddWayFilter($query): void
{
if (!array_key_exists('add_way', $this->params) || $this->params['add_way'] === '') {
return;
}
$addWay = self::normalizeAddWay($this->params['add_way']);
if ($addWay === null) {
$query->whereRaw('1=0');
return;
}
$numberPrefix = '%"add_way":' . $addWay;
$stringPrefix = '%"add_way":"' . $addWay;
$query->where(function ($q) use ($numberPrefix, $stringPrefix) {
$q->where('follow_users', 'like', $numberPrefix . ',%')
->whereOr('follow_users', 'like', $numberPrefix . '}%')
->whereOr('follow_users', 'like', $stringPrefix . '",%')
->whereOr('follow_users', 'like', $stringPrefix . '"}%');
});
}
private function baseQuery()
{
$query = QywxExternalContact::where($this->searchWhere);
// 添加时间可能已按「跟进人+事件流水」收窄;此时不必再 LIKE follow_users
$followAlreadyScoped = $this->applyAddTimeFilter($query);
if (!$followAlreadyScoped) {
$this->applyFollowUserFilter($query);
}
// 标签筛选:JOIN 关系表按 tag_id 过滤;多个标签为 OR(命中任一即返回)。
if (!$followAlreadyScoped) {
$this->applyFollowUserFilter($query);
}
$this->applyAddWayFilter($query);
// 标签筛选:JOIN 关系表按 tag_id 过滤;多个标签为 OR(命中任一即返回)。
// 走 zyt_qywx_external_contact_tag.idx_tag 索引,比 LIKE follow_users 快得多
$tagIds = $this->normalizeTagIds();
if ($tagIds !== []) {
@@ -262,6 +291,154 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
return $query;
}
/**
* 批量读取当前页客户的加客渠道流水。
*
* 事件表是一对多关系,不能直接 JOIN 到分页主查询,否则会放大列表行数与 count。
* 同一客户可能被不同员工重复添加,因此保留所有不同的非空 state,并按最近事件排序。
*
* @param string[] $externalUserids
* @return array<string, array<int, array<string, mixed>>>
*/
private function loadAddChannelsByExternalUserid(array $externalUserids): array
{
$ids = [];
foreach ($externalUserids as $externalUserid) {
$externalUserid = trim((string) $externalUserid);
if ($externalUserid !== '') {
$ids[$externalUserid] = true;
}
}
$ids = array_keys($ids);
if ($ids === []) {
return [];
}
$events = Db::name('qywx_external_contact_event')
->where('change_type', 'add_external_contact')
->where('state', '<>', '')
->whereIn('external_userid', $ids)
->field(['id', 'external_userid', 'user_id', 'state', 'event_time'])
->order('event_time', 'desc')
->order('id', 'desc')
->select()
->toArray();
$poolIds = [];
foreach ($events as $event) {
$state = trim((string) ($event['state'] ?? ''));
if (preg_match('/^zyt_pool:([1-9]\d*)$/D', $state, $matches) === 1) {
$poolIds[(int) $matches[1]] = true;
}
}
$poolNamesById = [];
if ($poolIds !== []) {
// 历史渠道仍应显示已删除方案原来的名称,因此这里不限制 delete_time。
$poolNamesById = Db::name('qywx_promotion_pool')
->whereIn('id', array_keys($poolIds))
->column('name', 'id');
}
return self::projectAddChannelEvents($events, $poolNamesById);
}
/**
* @param array<int, array<string, mixed>> $events 已按 event_time DESC, id DESC 排序
* @param array<int|string, string> $poolNamesById
* @return array<string, array<int, array<string, mixed>>>
*/
private static function projectAddChannelEvents(array $events, array $poolNamesById): array
{
$channelsByExternalUserid = [];
$seen = [];
foreach ($events as $event) {
$externalUserid = trim((string) ($event['external_userid'] ?? ''));
$state = trim((string) ($event['state'] ?? ''));
if ($externalUserid === '' || $state === '' || isset($seen[$externalUserid][$state])) {
continue;
}
$seen[$externalUserid][$state] = true;
$poolId = 0;
if (preg_match('/^zyt_pool:([1-9]\d*)$/D', $state, $matches) === 1) {
$poolId = (int) $matches[1];
}
$poolName = $poolId > 0 ? trim((string) ($poolNamesById[$poolId] ?? '')) : '';
$channelsByExternalUserid[$externalUserid][] = [
'state' => $state,
'label' => $poolName !== ''
? $poolName
: ($poolId > 0 ? '获客助手方案 #' . $poolId : $state),
'source_type' => $poolId > 0 ? 'promotion_pool' : 'state',
'pool_id' => $poolId,
'user_id' => trim((string) ($event['user_id'] ?? '')),
'event_time' => (int) ($event['event_time'] ?? 0),
];
}
return $channelsByExternalUserid;
}
/**
* 企业微信客户详情 follow_user.add_way 的可读文案。
*
* add_way 是固定添加方式,state 是企业自定义渠道参数,两者不能混用。
* 未识别的新枚举保留原值,避免后续企微扩展时页面退化成“未记录”。
*/
private static function addWayLabel(int $addWay): string
{
$labels = [
0 => '未知添加方式',
1 => '通过扫描二维码添加',
2 => '通过搜索手机号添加',
3 => '通过名片分享添加',
4 => '通过群聊添加',
5 => '通过手机通讯录添加',
6 => '通过微信联系人添加',
8 => '安装第三方应用时自动添加',
9 => '通过搜索邮箱添加',
10 => '通过视频号添加',
11 => '通过日程参与人添加',
12 => '通过会议参与人添加',
13 => '通过微信好友添加',
14 => '通过智慧硬件专属客服添加',
15 => '通过上门服务客服添加',
16 => '通过获客链接添加',
17 => '通过定制开发添加',
18 => '通过需求回复添加',
21 => '通过第三方售前客服添加',
22 => '通过可能的商务伙伴添加',
24 => '通过接受微信好友申请添加',
201 => '通过内部成员共享添加',
202 => '通过管理员或负责人分配添加',
];
return $labels[$addWay] ?? '其他添加方式(' . $addWay . '';
}
/**
* @param mixed $value
*/
private static function normalizeAddWay($value): ?int
{
if (is_int($value)) {
return $value >= 0 ? $value : null;
}
if (!is_string($value)) {
return null;
}
$value = trim($value);
if ($value === '' || preg_match('/^\d+$/D', $value) !== 1) {
return null;
}
return (int) $value;
}
/**
* @notes 获取列表
*/
@@ -278,7 +455,12 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
->toArray();
$wxUserids = [];
$externalUserids = [];
foreach ($lists as $item) {
$externalUserid = trim((string) ($item['external_userid'] ?? ''));
if ($externalUserid !== '') {
$externalUserids[$externalUserid] = true;
}
$raw = json_decode($item['follow_users'] ?? '[]', true);
if (!is_array($raw)) {
continue;
@@ -298,19 +480,25 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
if ($wxUserids !== []) {
$adminNameByWx = Admin::whereIn('work_wechat_userid', $wxUserids)->column('name', 'work_wechat_userid');
}
$addChannelsByExternalUserid = $this->loadAddChannelsByExternalUserid(array_keys($externalUserids));
foreach ($lists as &$item) {
$followUsers = json_decode($item['follow_users'] ?? '[]', true);
$followUsers = is_array($followUsers) ? $followUsers : [];
foreach ($followUsers as &$fu) {
if (!is_array($fu)) {
continue;
}
$wx = trim((string) ($fu['userid'] ?? ''));
if ($wx !== '' && isset($adminNameByWx[$wx]) && $adminNameByWx[$wx] !== '') {
$fu['admin_name'] = $adminNameByWx[$wx];
}
}
foreach ($followUsers as &$fu) {
if (!is_array($fu)) {
continue;
}
$wx = trim((string) ($fu['userid'] ?? ''));
if ($wx !== '' && isset($adminNameByWx[$wx]) && $adminNameByWx[$wx] !== '') {
$fu['admin_name'] = $adminNameByWx[$wx];
}
$addWay = self::normalizeAddWay($fu['add_way'] ?? $fu['AddWay'] ?? null);
if ($addWay !== null) {
$fu['add_way'] = $addWay;
$fu['add_way_label'] = self::addWayLabel($addWay);
}
}
unset($fu);
$item['follow_users'] = $followUsers;
$followAdminIds = json_decode($item['follow_admin_ids'] ?? '[]', true);
@@ -320,6 +508,10 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
$tags = json_decode((string) ($item['tags'] ?? '[]'), true);
$item['tags'] = is_array($tags) ? $tags : [];
$externalUserid = trim((string) ($item['external_userid'] ?? ''));
$item['add_channels'] = $addChannelsByExternalUserid[$externalUserid] ?? [];
$item['add_channel_states'] = array_column($item['add_channels'], 'state');
$fromDb = (int) ($item['external_first_add_time'] ?? 0);
$fromJson = CustomerLogic::minFollowCreatetime($followUsers);
$item['external_first_add_time'] = $fromDb > 0 ? $fromDb : $fromJson;
@@ -225,7 +225,12 @@ class WecomPromotionLogic
];
}
public static function savePool(array $params, int $adminId, array $adminInfo): array
public static function savePool(
array $params,
int $adminId,
array $adminInfo,
bool $syncImmediately = true
): array
{
self::assertMemberDispatchSchema();
$id = max(0, (int) ($params['id'] ?? 0));
@@ -425,11 +430,13 @@ class WecomPromotionLogic
$syncError = '';
if (!$createdRemote) {
QywxPromotionMemberSchedulerService::requestPoolSync($id, $linkId);
try {
(new QywxPromotionRangeSyncService())->syncPool($id);
} catch (\Throwable $e) {
// 本地方案和成员规则已保存;后台分钟任务会继续重试最新完整范围。
$syncError = $e->getMessage();
if ($syncImmediately) {
try {
(new QywxPromotionRangeSyncService())->syncPool($id);
} catch (\Throwable $e) {
// 本地方案和成员规则已保存;后台分钟任务会继续重试最新完整范围。
$syncError = $e->getMessage();
}
}
}
$savedLink = Db::name('qywx_promotion_link')->where('id', $linkId)->find() ?: [];
@@ -448,6 +455,152 @@ class WecomPromotionLogic
// 前端据此确认标签、欢迎语等扩展配置已和方案一并提交并完成回读校验。
'automation_saved' => $automation !== null,
'sync_error' => $syncError,
'sync_queued' => !$createdRemote && !$syncImmediately,
];
}
/**
* 批量局部更新分流方案。changes 只覆盖显式传入的字段;每个方案仍复用
* savePool 的成员、自动化、素材和企业微信同步校验。
*
* @return array{pool_ids:list<int>,updated:int,failed:int,sync_error_count:int,sync_queued_count:int,results:list<array<string,mixed>>}
*/
public static function batchUpdatePools(array $params, int $adminId, array $adminInfo): array
{
self::assertMemberDispatchSchema();
self::assertBasePagePermission($adminId, $adminInfo);
$poolIds = self::normalizePositiveIds((array) ($params['pool_ids'] ?? []));
if ($poolIds === []) {
throw new RuntimeException('请至少选择一个分流方案');
}
if (count($poolIds) > 100) {
throw new RuntimeException('单次最多设置 100 个分流方案');
}
$changes = $params['changes'] ?? null;
if (!is_array($changes)) {
throw new RuntimeException('批量修改内容格式不正确');
}
$allowedFields = ['skip_verify', 'fallback_url', 'status', 'automation_config'];
$unknownFields = array_diff(array_keys($changes), $allowedFields);
if ($unknownFields !== []) {
throw new RuntimeException('批量修改包含不支持的字段');
}
if ($changes === []) {
throw new RuntimeException('请至少选择一项需要批量修改的配置');
}
if (array_key_exists('fallback_url', $changes) && !is_string($changes['fallback_url'])) {
throw new RuntimeException('兜底获客助手链接格式不正确');
}
$automationPatch = null;
if (array_key_exists('automation_config', $changes)) {
if (!is_array($changes['automation_config']) || $changes['automation_config'] === []) {
throw new RuntimeException('自动化配置格式不正确');
}
$allowedAutomationFields = [
'reception_mode', 'reception_schedule', 'backup_member_admin_ids',
'tags_enabled', 'tag_ids', 'remark_enabled', 'remark_template',
'description_enabled', 'description', 'welcome_mode', 'welcome',
'welcome_schedule_enabled', 'welcome_schedule',
];
if (array_diff(array_keys($changes['automation_config']), $allowedAutomationFields) !== []) {
throw new RuntimeException('自动化配置包含不支持的字段');
}
QywxPromotionConfig::assertInstalled();
$automationPatch = $changes['automation_config'];
}
// 必须在任何方案写入前完成整批权限校验,避免越权请求产生部分更新。
$pools = [];
foreach ($poolIds as $poolId) {
$pools[$poolId] = self::assertScopedRow(
'qywx_promotion_pool',
$poolId,
$adminId,
$adminInfo,
false
);
}
$results = [];
$updated = 0;
$failed = 0;
$syncErrorCount = 0;
$syncQueuedCount = 0;
foreach ($poolIds as $poolId) {
$pool = $pools[$poolId];
$currentAutomation = QywxPromotionConfig::forPool($poolId);
$memberAdminIds = self::normalizePositiveIds(Db::name('qywx_promotion_pool_member')
->where('pool_id', $poolId)
->whereNull('delete_time')
->column('admin_id'));
$primaryMemberAdminIds = array_values(array_diff(
$memberAdminIds,
self::normalizePositiveIds((array) ($currentAutomation['backup_member_admin_ids'] ?? []))
));
$officialLink = Db::name('qywx_promotion_link')
->where('pool_id', $poolId)
->whereNull('delete_time')
->where('remote_link_id', '<>', '')
->where('remote_status', '<>', 2)
->order('id', 'desc')
->find() ?: [];
$saveParams = [
'id' => $poolId,
'name' => (string) ($pool['name'] ?? ''),
'fallback_url' => array_key_exists('fallback_url', $changes)
? trim($changes['fallback_url'])
: (string) ($pool['fallback_url'] ?? ''),
'status' => array_key_exists('status', $changes)
? ((int) $changes['status'] === 1 ? 1 : 0)
: (int) ($pool['status'] ?? 0),
'member_admin_ids' => $primaryMemberAdminIds,
'skip_verify' => array_key_exists('skip_verify', $changes)
? ((int) $changes['skip_verify'] === 1 ? 1 : 0)
: (int) ($officialLink['skip_verify'] ?? 0),
];
if ($automationPatch !== null) {
$saveParams['automation_config'] = array_replace($currentAutomation, $automationPatch);
}
try {
// 批量操作只落本地并入同步队列,避免大量企微请求阻塞管理端 HTTP 请求。
$saved = self::savePool($saveParams, $adminId, $adminInfo, false);
$syncError = trim((string) ($saved['sync_error'] ?? ''));
$updated++;
if ($syncError !== '') {
$syncErrorCount++;
}
$syncQueued = !empty($saved['sync_queued']);
if ($syncQueued) {
$syncQueuedCount++;
}
$results[] = [
'id' => $poolId,
'name' => (string) ($pool['name'] ?? ''),
'success' => true,
'sync_error' => $syncError,
'sync_queued' => $syncQueued,
];
} catch (\Throwable $error) {
$failed++;
$results[] = [
'id' => $poolId,
'name' => (string) ($pool['name'] ?? ''),
'success' => false,
'error' => $error->getMessage(),
];
}
}
return [
'pool_ids' => $poolIds,
'updated' => $updated,
'failed' => $failed,
'sync_error_count' => $syncErrorCount,
'sync_queued_count' => $syncQueuedCount,
'results' => $results,
];
}
+41 -1
View File
@@ -357,9 +357,10 @@ class OrderLogic
* @notes 编辑订单
* @param int $id
* @param array $params
* @param bool $canEditTime 是否已通过订单时间修改权限校验
* @return bool
*/
public static function edit(int $id, array $params): bool
public static function edit(int $id, array $params, bool $canEditTime = false): bool
{
try {
$order = Order::find($id);
@@ -368,6 +369,13 @@ class OrderLogic
return false;
}
$hasTimeParams = array_key_exists('payment_time', $params)
|| array_key_exists('create_time', $params);
if ($hasTimeParams && !$canEditTime) {
self::setError('无权限修改订单支付时间或创建时间');
return false;
}
if (isset($params['remark'])) {
$order->remark = $params['remark'];
}
@@ -379,6 +387,26 @@ class OrderLogic
if (isset($params['order_type'])) {
$order->order_type = (int)$params['order_type'];
}
// 支付时间参与营业额等统计,仅已支付/已退款订单允许修正且不可清空
if (array_key_exists('payment_time', $params)) {
$paymentTime = trim((string)$params['payment_time']);
if (!in_array((int)$order->status, [2, 4], true)) {
self::setError('仅已支付或已退款订单可修改支付时间');
return false;
}
if ($paymentTime === '') {
self::setError('已支付或已退款订单的支付时间不能为空');
return false;
}
$order->payment_time = $paymentTime;
}
if (isset($params['create_time'])) {
$createTime = (string)$params['create_time'];
$order->create_time = self::normalizeEditedCreateTime(
$order->getData('create_time'),
$createTime
);
}
$order->save();
return true;
@@ -388,6 +416,18 @@ class OrderLogic
}
}
/**
* 兼容历史库中 create_time INT 时间戳的表结构
*/
private static function normalizeEditedCreateTime(mixed $storedValue, string $dateTime): int|string
{
if (is_int($storedValue) || (is_string($storedValue) && ctype_digit($storedValue))) {
return (int)strtotime($dateTime);
}
return $dateTime;
}
/**
* @notes 支付订单
* @param int $id
@@ -722,6 +722,73 @@ class CustomerLogic extends BaseLogic
}
}
/**
* 后台手工删除一条本地同步记录。
*
* 仅按列表行主键软删除,不调用企业微信删除客户关系;兼容历史库中可能存在的重复
* external_userid。只有该客户已无其他有效行时才清理共享的标签关系。
*/
public static function deleteCustomer(int $id): bool
{
if ($id <= 0) {
self::$error = '客户参数错误';
return false;
}
try {
$externalUserId = Db::transaction(static function () use ($id): string {
$row = Db::name('qywx_external_contact')
->where('id', $id)
->whereNull('delete_time')
->lock(true)
->find();
if (!$row) {
throw new \DomainException('客户不存在或已删除');
}
$now = time();
Db::name('qywx_external_contact')
->where('id', $id)
->whereNull('delete_time')
->update([
'delete_time' => $now,
'update_time' => $now,
]);
$externalUserId = trim((string) ($row['external_userid'] ?? ''));
if ($externalUserId !== '') {
$activeRows = (int) Db::name('qywx_external_contact')
->where('external_userid', $externalUserId)
->whereNull('delete_time')
->count();
if ($activeRows === 0) {
Db::name('qywx_external_contact_tag')
->where('external_userid', $externalUserId)
->delete();
}
}
return $externalUserId;
});
if ($externalUserId !== '') {
MediaChannelService::forgetCurrentTagCatalogCache();
}
return true;
} catch (\DomainException $e) {
self::$error = $e->getMessage();
return false;
} catch (\Throwable $e) {
Log::error('后台删除企业微信客户同步记录失败: ' . $e->getMessage());
self::$error = '删除失败,请稍后重试';
return false;
}
}
/**
* 客户联系「删除企业客户」等事件:本地软删除一行。
*/
@@ -28,6 +28,8 @@ class OrderValidate extends BaseValidate
'assistant_id' => 'require|integer|gt:0',
'amounts' => 'require|array',
'order_types' => 'require|array',
'payment_time' => 'dateFormat:Y-m-d H:i:s',
'create_time' => 'require|dateFormat:Y-m-d H:i:s',
];
protected $message = [
@@ -38,11 +40,15 @@ class OrderValidate extends BaseValidate
'status.require' => '订单状态必填',
'status.in' => '订单状态不正确',
'payment_method.in' => '支付方式不正确',
'payment_time.dateFormat' => '支付时间格式不正确',
'create_time.require' => '创建时间必填',
'create_time.dateFormat' => '创建时间格式不正确',
];
protected $scene = [
'create' => ['patient_id', 'order_type', 'amount'],
'edit' => ['id'],
'edit' => ['id', 'patient_id', 'order_type', 'remark'],
'edit_time' => ['id', 'payment_time', 'create_time'],
'detail' => ['id'],
'pay' => ['payment_method'],
'cancel' => ['id'],
@@ -12,11 +12,15 @@ use app\common\validate\BaseValidate;
class CustomerValidate extends BaseValidate
{
protected $rule = [
'id' => 'require|integer|gt:0',
'auto_sync' => 'require|boolean',
'interval' => 'require|integer|between:3600,86400',
];
protected $message = [
'id.require' => '请选择要删除的客户',
'id.integer' => '客户参数格式错误',
'id.gt' => '客户参数格式错误',
'auto_sync.require' => '请选择是否自动同步',
'auto_sync.boolean' => '自动同步参数格式错误',
'interval.require' => '请选择同步间隔',
@@ -31,4 +35,12 @@ class CustomerValidate extends BaseValidate
{
return $this->only(['auto_sync', 'interval']);
}
/**
* @notes 删除客户场景
*/
public function sceneDelete()
{
return $this->only(['id']);
}
}
@@ -20,6 +20,7 @@ class DirectUploadService
/** 视频允许的扩展名(沿用 config/project.file_video */
public const TYPE_VIDEO = 'video';
public const TYPE_VOICE = 'voice';
public const TYPE_DESKTOP_PACKAGE = 'desktop_package';
/** 默认凭证有效期 30 分钟 */
public const DEFAULT_DURATION = 1800;
@@ -28,6 +29,7 @@ class DirectUploadService
private const MAX_SIZE = [
self::TYPE_VIDEO => 2 * 1024 * 1024 * 1024, // 2GB
self::TYPE_VOICE => 500 * 1024 * 1024, // 500MB
self::TYPE_DESKTOP_PACKAGE => 2 * 1024 * 1024 * 1024, // 2GB
];
/**
@@ -36,7 +38,7 @@ class DirectUploadService
* @return array
* @throws Exception
*/
public static function issueCredentials(string $type): array
public static function issueCredentials(string $type, int $adminId = 0, string $name = ''): array
{
if (!isset(self::MAX_SIZE[$type])) {
throw new Exception('不支持的上传类型: ' . $type);
@@ -54,9 +56,27 @@ class DirectUploadService
throw new Exception('腾讯云 COS 配置不完整');
}
$keyPrefix = self::buildKeyPrefix($type);
$keyPrefix = self::buildKeyPrefix($type, $adminId);
$objectKey = '';
// 兼容前后端错峰发布:旧 uploader 只传 type,不传 name。
// 新 uploader 仍使用更严格的单对象授权;旧版则限制在当前管理员当天目录,
// 并在 confirm 阶段校验文件名、扩展名与实际对象。
if ($type === self::TYPE_DESKTOP_PACKAGE && trim($name) !== '') {
$extension = strtolower((string)pathinfo($name, PATHINFO_EXTENSION));
$objectKey = $keyPrefix
. (int)round(microtime(true) * 1000)
. '-'
. bin2hex(random_bytes(8))
. ($extension !== '' ? '.' . $extension : '');
self::validateFileExtension($type, $objectKey, $name);
}
$engine = new QcloudEngine($storageConfig);
$sts = $engine->getStsCredentials($keyPrefix, self::MAX_SIZE[$type], self::DEFAULT_DURATION);
$sts = $engine->getStsCredentials(
$objectKey !== '' ? $objectKey : $keyPrefix,
self::MAX_SIZE[$type],
self::DEFAULT_DURATION,
$objectKey !== ''
);
return [
'provider' => 'qcloud',
@@ -66,6 +86,7 @@ class DirectUploadService
'host' => $sts['host'],
'cdn_domain' => rtrim((string)($storageConfig['domain'] ?? ''), '/'),
'key_prefix' => $keyPrefix,
'object_key' => $objectKey,
'max_size' => self::MAX_SIZE[$type],
'duration' => self::DEFAULT_DURATION,
'expired_time' => $sts['expiredTime'],
@@ -93,8 +114,7 @@ class DirectUploadService
}
$key = ltrim((string)($params['key'] ?? ''), '/');
$allowedPrefix = self::buildKeyPrefix($type);
if ($key === '' || strpos($key, $allowedPrefix) !== 0) {
if (!self::isAllowedObjectKey($type, $key, (int)($params['admin_id'] ?? 0))) {
throw new Exception('对象 Key 非法');
}
@@ -112,6 +132,7 @@ class DirectUploadService
if ($name === '') {
$name = basename($key);
}
self::validateFileExtension($type, $key, $name);
if (strlen($name) > 128) {
$name = substr($name, 0, 123) . substr($name, -5);
}
@@ -137,9 +158,16 @@ class DirectUploadService
];
}
private static function buildKeyPrefix(string $type): string
private static function buildKeyPrefix(string $type, int $adminId = 0): string
{
return 'uploads/' . $type . '/' . date('Ymd') . '/';
$prefix = 'uploads/' . $type . '/';
if ($type === self::TYPE_DESKTOP_PACKAGE) {
if ($adminId <= 0) {
throw new Exception('安装包上传账号无效');
}
$prefix .= $adminId . '/';
}
return $prefix . date('Ymd') . '/';
}
private static function resolveFileType(string $type): int
@@ -150,4 +178,46 @@ class DirectUploadService
default => FileEnum::FILE_TYPE,
};
}
/**
* 桌面安装包是可执行文件,只允许发布流程所需的 EXE / ZIP。
*/
private static function validateFileExtension(string $type, string $key, string $name): void
{
if ($type !== self::TYPE_DESKTOP_PACKAGE) {
return;
}
$nameExtension = strtolower((string)pathinfo($name, PATHINFO_EXTENSION));
$keyExtension = strtolower((string)pathinfo($key, PATHINFO_EXTENSION));
$allowedExtensions = ['exe', 'zip'];
if (!in_array($nameExtension, $allowedExtensions, true)
|| $nameExtension !== $keyExtension) {
throw new Exception('桌面安装包仅支持 EXE 或 ZIP 文件');
}
}
/**
* 安装包 Key 绑定上传管理员,并兼容跨午夜完成的上传。
*/
private static function isAllowedObjectKey(string $type, string $key, int $adminId): bool
{
if ($key === '') {
return false;
}
if ($type !== self::TYPE_DESKTOP_PACKAGE) {
return strpos($key, self::buildKeyPrefix($type)) === 0;
}
if ($adminId <= 0) {
return false;
}
$ownerPrefix = 'uploads/' . self::TYPE_DESKTOP_PACKAGE . '/' . $adminId . '/';
if (strpos($key, $ownerPrefix) !== 0) {
return false;
}
$date = substr($key, strlen($ownerPrefix), 8);
return in_array($date, [date('Ymd'), date('Ymd', time() - 86400)], true)
&& substr($key, strlen($ownerPrefix) + 8, 1) === '/';
}
}
@@ -116,13 +116,19 @@ class Qcloud extends Server
/**
* @notes 获取 STS 临时凭证(用于浏览器直传)
* @param string $keyPrefix 资源前缀,如 uploads/video/20260508/
* @param string $keyScope 资源前缀或完整对象 Key
* @param int $maxSizeBytes 单文件大小上限(字节)
* @param int $durationSeconds 凭证有效期(秒)
* @param bool $exactObject 是否只授权单个对象 Key
* @return array {credentials, expiredTime, requestId}
* @throws Exception
*/
public function getStsCredentials(string $keyPrefix, int $maxSizeBytes, int $durationSeconds = 1800): array
public function getStsCredentials(
string $keyScope,
int $maxSizeBytes,
int $durationSeconds = 1800,
bool $exactObject = false
): array
{
$bucket = $this->config['bucket'];
// bucket 形如 likeadmin-1300000000appId 即末段
@@ -137,15 +143,19 @@ class Qcloud extends Server
$shortBucket = substr($bucket, 0, strrpos($bucket, '-'));
$region = $this->config['region'];
$prefix = ltrim($keyPrefix, '/');
if ($prefix === '' || substr($prefix, -1) !== '/') {
$prefix = $prefix . '/';
$scope = ltrim($keyScope, '/');
if ($scope === '') {
throw new Exception('COS 授权对象不能为空');
}
if (!$exactObject && substr($scope, -1) !== '/') {
$scope .= '/';
}
$duration = max(900, min($durationSeconds, 7200));
// 自行构造 policy:对象级写动作收紧 + bucket 级 ListMultipartUploadscos-js-sdk-v5 续传探测必需)
$objectArn = sprintf('qcs::cos:%s:uid/%s:%s/%s*', $region, $appId, $bucket, $prefix);
$objectResource = $exactObject ? $scope : $scope . '*';
$objectArn = sprintf('qcs::cos:%s:uid/%s:%s/%s', $region, $appId, $bucket, $objectResource);
$bucketArn = sprintf('qcs::cos:%s:uid/%s:%s/*', $region, $appId, $bucket);
$policy = [
@@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS `zyt_qywx_external_contact_event` (
PRIMARY KEY (`id`),
UNIQUE KEY `uk_change_user_ext_time` (`change_type`, `user_id`, `external_userid`, `event_time`),
KEY `idx_change_time` (`change_type`, `event_time`),
KEY `idx_change_ext_time` (`change_type`, `external_userid`, `event_time`, `id`),
KEY `idx_event_time` (`event_time`),
KEY `idx_state` (`state`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业微信外部联系人事件流水(用于进入计数)';
@@ -1 +1 @@
import t from"./error-CMCHDzcN.js";import{o,q as r,r as a,v as n,D as c,s}from"./.pnpm-BSw4l71J.js";import"./index-C7M1esnu.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};
import t from"./error-Cfsov5To.js";import{o,q as r,r as a,v as n,D as c,s}from"./.pnpm-BSw4l71J.js";import"./index-C5omO-nb.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};
@@ -1 +1 @@
import e from"./error-CMCHDzcN.js";import{o,q as r,r as t,v as s}from"./.pnpm-BSw4l71J.js";import"./index-C7M1esnu.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};
import e from"./error-Cfsov5To.js";import{o,q as r,r as t,v as s}from"./.pnpm-BSw4l71J.js";import"./index-C5omO-nb.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};
@@ -1 +1 @@
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-BSw4l71J.js";import{a as V}from"./doctor-CqioGk2D.js";import{m as A,_ as M}from"./index-C7M1esnu.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-4de87dfa"]]);export{Q as default};
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-BSw4l71J.js";import{a as V}from"./doctor-DWIjqKHf.js";import{m as A,_ as M}from"./index-C5omO-nb.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-4de87dfa"]]);export{Q as default};
@@ -1 +1 @@
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-BSw4l71J.js";import{af as V}from"./tcm-DE5Wfo6u.js";import{_ as q}from"./index-C7M1esnu.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-f670e3e6"]]);export{Y as default};
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-BSw4l71J.js";import{af as V}from"./tcm-bPbcXX_C.js";import{_ as q}from"./index-C5omO-nb.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-f670e3e6"]]);export{Y as default};
@@ -1 +1 @@
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,di as c}from"./.pnpm-BSw4l71J.js";import{ag as Y}from"./tcm-DE5Wfo6u.js";import{_ as q}from"./index-C7M1esnu.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(""),f=v("旁观视频通话"),n=new Map;let a=null,p=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!==c.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 d=document.createElement("div");d.className="watch-tile-view",o.appendChild(s),o.appendChild(d),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:d})}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(c.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.on(c.EVENT.REMOTE_VIDEO_UNAVAILABLE,V))}function k(){a&&(a.off(c.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.off(c.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=++p;if(await m(),!u.diagnosisId){i.value="诊单无效";return}l.value=!0,i.value="",f.value="旁观视频通话";try{const e=await Y({diagnosis_id:u.diagnosisId});if(t!==p)return;e.patientName&&(f.value=`旁观视频通话 · ${e.patientName}`),a=c.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!==p){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="",f.value="旁观视频通话",g("closed")}return D(()=>[u.modelValue,u.diagnosisId],([t,e])=>{if(!t){p++,m();return}e>0&&L()}),(t,e)=>{const o=$,s=H;return E(),O(s,{modelValue:y.value,"onUpdate:modelValue":e[1]||(e[1]=d=>y.value=d),title:f.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]=d=>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-7aff665d"]]);export{Z as default};
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,di as c}from"./.pnpm-BSw4l71J.js";import{ag as Y}from"./tcm-bPbcXX_C.js";import{_ as q}from"./index-C5omO-nb.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(""),f=v("旁观视频通话"),n=new Map;let a=null,p=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!==c.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 d=document.createElement("div");d.className="watch-tile-view",o.appendChild(s),o.appendChild(d),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:d})}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(c.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.on(c.EVENT.REMOTE_VIDEO_UNAVAILABLE,V))}function k(){a&&(a.off(c.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.off(c.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=++p;if(await m(),!u.diagnosisId){i.value="诊单无效";return}l.value=!0,i.value="",f.value="旁观视频通话";try{const e=await Y({diagnosis_id:u.diagnosisId});if(t!==p)return;e.patientName&&(f.value=`旁观视频通话 · ${e.patientName}`),a=c.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!==p){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="",f.value="旁观视频通话",g("closed")}return D(()=>[u.modelValue,u.diagnosisId],([t,e])=>{if(!t){p++,m();return}e>0&&L()}),(t,e)=>{const o=$,s=H;return E(),O(s,{modelValue:y.value,"onUpdate:modelValue":e[1]||(e[1]=d=>y.value=d),title:f.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]=d=>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-7aff665d"]]);export{Z as default};
@@ -1 +1 @@
import{o as N,dk 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-BSw4l71J.js";import j from"./RecordingPlaybackBlock-EOaWFCvB.js";import{U as k}from"./index-BLSe8mr1.js";import{i as c,_ as q}from"./index-C7M1esnu.js";import{ak as K,al as x,am as A}from"./tcm-DE5Wfo6u.js";import"./RecordingVideoPlayer-CBiSJJjw.js";import"./file-BSLutQ6Q.js";const F={class:"call-record-panel"},G={key:0,class:"call-record-toolbar"},H={class:"call-record-empty"},J={class:"call-record-empty__desc"},Q={key:0,class:"text-primary"},W={key:1,class:"text-gray-400"},X=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 A({diagnosis_id:r.diagnosisId});await x({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 x({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",F,[_.readOnly?h("",!0):(d(),f("div",G,[o(k,{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",H,[t[1]||(t[1]=y("div",{class:"call-record-empty__title"},"暂无通话记录",-1)),y("div",J,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",Q,u(s.room_id),1)):(d(),f("span",W,"—"))]),_: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(k,{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(X,[["__scopeId","data-v-78d5c9e4"]]);export{sa as default};
import{o as N,dk 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-BSw4l71J.js";import j from"./RecordingPlaybackBlock-CAt56ls3.js";import{U as k}from"./index-B_vRidw5.js";import{i as c,_ as q}from"./index-C5omO-nb.js";import{ak as K,al as x,am as A}from"./tcm-bPbcXX_C.js";import"./RecordingVideoPlayer-DVjRj1N7.js";import"./file--nSnYOw1.js";const F={class:"call-record-panel"},G={key:0,class:"call-record-toolbar"},H={class:"call-record-empty"},J={class:"call-record-empty__desc"},Q={key:0,class:"text-primary"},W={key:1,class:"text-gray-400"},X=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 A({diagnosis_id:r.diagnosisId});await x({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 x({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",F,[_.readOnly?h("",!0):(d(),f("div",G,[o(k,{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",H,[t[1]||(t[1]=y("div",{class:"call-record-empty__title"},"暂无通话记录",-1)),y("div",J,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",Q,u(s.room_id),1)):(d(),f("span",W,"—"))]),_: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(k,{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(X,[["__scopeId","data-v-78d5c9e4"]]);export{sa as default};
@@ -1 +1 @@
import{o as T,ap as V,R as I,q as o,r as l,v as a,K as N,D as i,L as c,P as k,ac as z,bi as M,u as p,O as f,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-BSw4l71J.js";import{an as q}from"./tcm-DE5Wfo6u.js";import{_ as H}from"./index-C7M1esnu.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,[a(h,{type:"primary",size:"small",onClick:$},{default:i(()=>[...e[0]||(e[0]=[c("开方",-1)])]),_:1})])),z((o(),f(E,{data:p(r),border:""},{default:i(()=>[a(n,{prop:"prescription_date",label:"就诊日期",width:"120"}),a(n,{prop:"visit_no",label:"门诊号",width:"120"}),a(n,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),a(n,{label:"处方摘要","min-width":"180"},{default:i(({row:s})=>[s.herbs&&s.herbs.length?(o(),l("span",G,_(s.herbs.slice(0,3).map(g=>`${g.name}${g.dosage}`).join("、"))+_(s.herbs.length>3?"...":""),1)):(o(),l("span",J,"—"))]),_:1}),a(n,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),a(n,{label:"状态",width:"140",align:"center"},{default:i(({row:s})=>[s.void_status===1?(o(),l(P,{key:0},[a(v,{type:"danger",size:"small"},{default:i(()=>[...e[1]||(e[1]=[c("已作废",-1)])]),_:1}),F("div",Q,_(s.void_by_name||"—")+" "+_(S(s.void_time)),1)],64)):(o(),f(v,{key:1,type:"success",size:"small"},{default:i(()=>[...e[2]||(e[2]=[c("正常",-1)])]),_:1}))]),_:1}),a(n,{label:"操作",width:"120",fixed:"right"},{default:i(({row:s})=>[a(h,{link:"",type:"primary",size:"small",onClick:g=>B(s)},{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(),f(D,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):k("",!0)])}}}),ee=H(U,[["__scopeId","data-v-043d2738"]]);export{ee as default};
import{o as T,ap as V,R as I,q as o,r as l,v as a,K as N,D as i,L as c,P as k,ac as z,bi as M,u as p,O as f,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-BSw4l71J.js";import{an as q}from"./tcm-bPbcXX_C.js";import{_ as H}from"./index-C5omO-nb.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,[a(h,{type:"primary",size:"small",onClick:$},{default:i(()=>[...e[0]||(e[0]=[c("开方",-1)])]),_:1})])),z((o(),f(E,{data:p(r),border:""},{default:i(()=>[a(n,{prop:"prescription_date",label:"就诊日期",width:"120"}),a(n,{prop:"visit_no",label:"门诊号",width:"120"}),a(n,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),a(n,{label:"处方摘要","min-width":"180"},{default:i(({row:s})=>[s.herbs&&s.herbs.length?(o(),l("span",G,_(s.herbs.slice(0,3).map(g=>`${g.name}${g.dosage}`).join("、"))+_(s.herbs.length>3?"...":""),1)):(o(),l("span",J,"—"))]),_:1}),a(n,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),a(n,{label:"状态",width:"140",align:"center"},{default:i(({row:s})=>[s.void_status===1?(o(),l(P,{key:0},[a(v,{type:"danger",size:"small"},{default:i(()=>[...e[1]||(e[1]=[c("已作废",-1)])]),_:1}),F("div",Q,_(s.void_by_name||"—")+" "+_(S(s.void_time)),1)],64)):(o(),f(v,{key:1,type:"success",size:"small"},{default:i(()=>[...e[2]||(e[2]=[c("正常",-1)])]),_:1}))]),_:1}),a(n,{label:"操作",width:"120",fixed:"right"},{default:i(({row:s})=>[a(h,{link:"",type:"primary",size:"small",onClick:g=>B(s)},{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(),f(D,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):k("",!0)])}}}),ee=H(U,[["__scopeId","data-v-043d2738"]]);export{ee as default};
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{_ as o}from"./GancaoSubmissionReconcileButton.vue_vue_type_script_setup_true_lang-3HAp0vZ1.js";import"./.pnpm-BSw4l71J.js";import"./tcm-DE5Wfo6u.js";import"./index-C7M1esnu.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./GancaoSubmissionReconcileButton.vue_vue_type_script_setup_true_lang-ChclpdH0.js";import"./.pnpm-BSw4l71J.js";import"./tcm-bPbcXX_C.js";import"./index-C5omO-nb.js";export{o as default};
@@ -1 +1 @@
import{o as U,be as F,q as f,r as k,F as M,ac as D,O as E,D as t,L as u,K as G,v as r,bt as B,bh as q,b9 as P,aw as A,b6 as L,bc as T,bf as K,b7 as z,P as g,p as W,M as v,ba as $}from"./.pnpm-BSw4l71J.js";import{p as j}from"./tcm-DE5Wfo6u.js";import{i as C}from"./index-C7M1esnu.js";const X=U({__name:"GancaoSubmissionReconcileButton",props:{order:{}},emits:["resolved"],setup(N,{emit:w}){const d=N,y=w,V=W(()=>{var m,p,a;const i=String(((m=d.order)==null?void 0:m.pharmacy_claim_target)||"").trim().toLowerCase(),e=String(((p=d.order)==null?void 0:p.pharmacy_claim_status)||"").trim().toUpperCase(),n=Number(((a=d.order)==null?void 0:a.pharmacy_claim_lease_expires_at)||0),c=e==="PENDING"&&n>0&&n<=Math.floor(Date.now()/1e3);return i==="gancao"&&(c||["UNKNOWN","PENDING_RECONCILE"].includes(e))}),s=v(!1),_=v(!1),o=$({resolution:"CONFIRM_SUCCESS",remote_order_no:"",note:""});function O(){o.resolution="CONFIRM_SUCCESS",o.remote_order_no="",o.note="",s.value=!0}async function b(){var e;const i=Number((e=d.order)==null?void 0:e.id);if(i){if(o.resolution==="CONFIRM_SUCCESS"&&!o.remote_order_no.trim()){C.msgError("请填写甘草药方单号");return}if(!o.note.trim()){C.msgError("请填写甘草后台核对依据");return}_.value=!0;try{await j({id:i,resolution:o.resolution,remote_order_no:o.resolution==="CONFIRM_SUCCESS"?o.remote_order_no.trim():"",note:o.note.trim()}),C.msgSuccess("甘草提交核对已记录"),s.value=!1,y("resolved")}catch{}finally{_.value=!1}}}return(i,e)=>{const n=G,c=q,m=K,p=T,a=L,S=z,R=P,x=B,I=F("perms");return V.value?(f(),k(M,{key:0},[D((f(),E(n,{type:"danger",size:"small",plain:"",onClick:O},{default:t(()=>[...e[5]||(e[5]=[u("核对甘草提交",-1)])]),_:1})),[[I,["tcm.prescriptionOrder/confirmGancaoSubmission"]]]),r(x,{modelValue:s.value,"onUpdate:modelValue":e[4]||(e[4]=l=>s.value=l),title:"人工核对甘草提交",width:"min(92vw, 520px)","append-to-body":"","destroy-on-close":"","close-on-click-modal":!1},{footer:t(()=>[r(n,{onClick:e[3]||(e[3]=l=>s.value=!1)},{default:t(()=>[...e[8]||(e[8]=[u("取消",-1)])]),_:1}),r(n,{type:"primary",loading:_.value,onClick:b},{default:t(()=>[...e[9]||(e[9]=[u("确认并记录",-1)])]),_:1},8,["loading"])]),default:t(()=>[r(c,{title:"请先在甘草后台核对。本操作会写入不可变更的审计记录。",type:"warning",closable:!1,"show-icon":"",class:"mb-4"}),r(R,{"label-width":"100px",onSubmit:A(b,["prevent"])},{default:t(()=>[r(a,{label:"核对结果",required:""},{default:t(()=>[r(p,{modelValue:o.resolution,"onUpdate:modelValue":e[0]||(e[0]=l=>o.resolution=l)},{default:t(()=>[r(m,{label:"CONFIRM_SUCCESS"},{default:t(()=>[...e[6]||(e[6]=[u("确认已创建",-1)])]),_:1}),r(m,{label:"CONFIRM_NOT_CREATED"},{default:t(()=>[...e[7]||(e[7]=[u("确认未创建",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),o.resolution==="CONFIRM_SUCCESS"?(f(),E(a,{key:0,label:"甘草单号",required:""},{default:t(()=>[r(S,{modelValue:o.remote_order_no,"onUpdate:modelValue":e[1]||(e[1]=l=>o.remote_order_no=l),maxlength:"64",clearable:""},null,8,["modelValue"])]),_:1})):g("",!0),r(a,{label:"核对依据",required:""},{default:t(()=>[r(S,{modelValue:o.note,"onUpdate:modelValue":e[2]||(e[2]=l=>o.note=l),type:"textarea",rows:3,maxlength:"1000","show-word-limit":"",placeholder:"例如:核对时间、甘草后台查询条件及结果"},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)):g("",!0)}}});export{X as _};
import{o as U,be as F,q as f,r as k,F as M,ac as D,O as E,D as t,L as u,K as G,v as r,bt as B,bh as q,b9 as P,aw as A,b6 as L,bc as T,bf as K,b7 as z,P as g,p as W,M as v,ba as $}from"./.pnpm-BSw4l71J.js";import{p as j}from"./tcm-bPbcXX_C.js";import{i as C}from"./index-C5omO-nb.js";const X=U({__name:"GancaoSubmissionReconcileButton",props:{order:{}},emits:["resolved"],setup(N,{emit:w}){const d=N,y=w,V=W(()=>{var m,p,a;const i=String(((m=d.order)==null?void 0:m.pharmacy_claim_target)||"").trim().toLowerCase(),e=String(((p=d.order)==null?void 0:p.pharmacy_claim_status)||"").trim().toUpperCase(),n=Number(((a=d.order)==null?void 0:a.pharmacy_claim_lease_expires_at)||0),c=e==="PENDING"&&n>0&&n<=Math.floor(Date.now()/1e3);return i==="gancao"&&(c||["UNKNOWN","PENDING_RECONCILE"].includes(e))}),s=v(!1),_=v(!1),o=$({resolution:"CONFIRM_SUCCESS",remote_order_no:"",note:""});function O(){o.resolution="CONFIRM_SUCCESS",o.remote_order_no="",o.note="",s.value=!0}async function b(){var e;const i=Number((e=d.order)==null?void 0:e.id);if(i){if(o.resolution==="CONFIRM_SUCCESS"&&!o.remote_order_no.trim()){C.msgError("请填写甘草药方单号");return}if(!o.note.trim()){C.msgError("请填写甘草后台核对依据");return}_.value=!0;try{await j({id:i,resolution:o.resolution,remote_order_no:o.resolution==="CONFIRM_SUCCESS"?o.remote_order_no.trim():"",note:o.note.trim()}),C.msgSuccess("甘草提交核对已记录"),s.value=!1,y("resolved")}catch{}finally{_.value=!1}}}return(i,e)=>{const n=G,c=q,m=K,p=T,a=L,S=z,R=P,x=B,I=F("perms");return V.value?(f(),k(M,{key:0},[D((f(),E(n,{type:"danger",size:"small",plain:"",onClick:O},{default:t(()=>[...e[5]||(e[5]=[u("核对甘草提交",-1)])]),_:1})),[[I,["tcm.prescriptionOrder/confirmGancaoSubmission"]]]),r(x,{modelValue:s.value,"onUpdate:modelValue":e[4]||(e[4]=l=>s.value=l),title:"人工核对甘草提交",width:"min(92vw, 520px)","append-to-body":"","destroy-on-close":"","close-on-click-modal":!1},{footer:t(()=>[r(n,{onClick:e[3]||(e[3]=l=>s.value=!1)},{default:t(()=>[...e[8]||(e[8]=[u("取消",-1)])]),_:1}),r(n,{type:"primary",loading:_.value,onClick:b},{default:t(()=>[...e[9]||(e[9]=[u("确认并记录",-1)])]),_:1},8,["loading"])]),default:t(()=>[r(c,{title:"请先在甘草后台核对。本操作会写入不可变更的审计记录。",type:"warning",closable:!1,"show-icon":"",class:"mb-4"}),r(R,{"label-width":"100px",onSubmit:A(b,["prevent"])},{default:t(()=>[r(a,{label:"核对结果",required:""},{default:t(()=>[r(p,{modelValue:o.resolution,"onUpdate:modelValue":e[0]||(e[0]=l=>o.resolution=l)},{default:t(()=>[r(m,{label:"CONFIRM_SUCCESS"},{default:t(()=>[...e[6]||(e[6]=[u("确认已创建",-1)])]),_:1}),r(m,{label:"CONFIRM_NOT_CREATED"},{default:t(()=>[...e[7]||(e[7]=[u("确认未创建",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),o.resolution==="CONFIRM_SUCCESS"?(f(),E(a,{key:0,label:"甘草单号",required:""},{default:t(()=>[r(S,{modelValue:o.remote_order_no,"onUpdate:modelValue":e[1]||(e[1]=l=>o.remote_order_no=l),maxlength:"64",clearable:""},null,8,["modelValue"])]),_:1})):g("",!0),r(a,{label:"核对依据",required:""},{default:t(()=>[r(S,{modelValue:o.note,"onUpdate:modelValue":e[2]||(e[2]=l=>o.note=l),type:"textarea",rows:3,maxlength:"1000","show-word-limit":"",placeholder:"例如:核对时间、甘草后台查询条件及结果"},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)):g("",!0)}}});export{X as _};
@@ -1 +1 @@
import{o as F,R as q,q as t,r as n,v as i,D as c,s as o,L as _,bh as G,w as H,u as x,cX as $,K as j,by as J,ac as K,F as E,G as O,O as f,bB as W,P as v,bi as X,M as m,p as Q,ae as U,T as r,br as Z,aa as ee,bq as se,a8 as ae,E as C}from"./.pnpm-BSw4l71J.js";import{d as te}from"./dayjs-CVa8MSSA.js";import{as as ne,at as oe}from"./tcm-DE5Wfo6u.js";import{p as re}from"./im-business-message-parse-oYIP1khU.js";import{_ as le}from"./index-C7M1esnu.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"},be={key:3,class:"muted"},ke=F({__name:"ImChatRecordPanel",props:{diagnosisId:{}},setup(B,{expose:M}){const u=B,d=m(!1),p=m(!1),y=ae([]),L=m(""),g=m(""),D={text:"",image:"图片",file:"文件",sound:"语音",video:"视频",location:"位置",custom:"自定义",face:"表情",other:""},h=Q(()=>y.value.map(e=>{const a=P(e);let l="";return a!=null&&a.tag?l=a.tag:l=D[e.msg_type]||(e.msg_type!=="other"?e.msg_type:""),{raw:e,friendly:a,tag:l}}));function N(e){if(e==null||!e)return"—";const a=e>1e12?Math.floor(e/1e3):e;return te.unix(a).format("YYYY-MM-DD HH:mm:ss")}function P(e){const a=(e.text||"").trim();if(!a)return null;const l=a.startsWith("{")&&(/\bbusinessID\b/.test(a)||/\bcmd\b/.test(a));return e.msg_type==="custom"||l?re(a):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 b(){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 q(()=>u.diagnosisId,()=>{w()},{immediate:!0}),M({refresh:b}),(e,a)=>{const l=G,k=H,I=j,T=Z,Y=ee,V=se,z=W,A=X;return t(),n("div",ie,[i(l,{type:"info","show-icon":"",closable:!1,class:"mb-4",title:"说明"},{default:c(()=>[...a[0]||(a[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(k,{class:"mr-1"},{default:c(()=>[i(x($))]),_:1}),a[1]||(a[1]=_(" 同步最新(后台异步) ",-1))]),_:1},8,["loading"]),i(I,{type:"primary",link:"",loading:d.value,onClick:b},{default:c(()=>[i(k,{class:"mr-1"},{default:c(()=>[i(x(J))]),_:1}),a[2]||(a[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,s=>(t(),n("div",{key:s.raw.msg_id,class:U(["chat-row",s.raw.is_from_doctor?"from-doctor":"from-patient"])},[o("div",ue,[o("span",fe,r(R(s.raw)),1),o("span",me,r(N(s.raw.time)),1),s.tag?(t(),f(T,{key:0,size:"small",type:"info",class:"ml-2"},{default:c(()=>[_(r(s.tag),1)]),_:2},1024)):v("",!0)]),o("div",pe,[s.raw.msg_type==="image"&&s.raw.image_url?(t(),f(Y,{key:0,src:s.raw.image_url,"preview-src-list":[s.raw.image_url],fit:"contain",class:"chat-img"},null,8,["src","preview-src-list"])):(s.raw.msg_type==="file"||s.raw.msg_type==="sound"||s.raw.msg_type==="video")&&s.raw.file_url?(t(),f(V,{key:1,href:s.raw.file_url,target:"_blank",type:"primary"},{default:c(()=>[_(r(s.raw.file_name||"打开文件"),1)]),_:2},1032,["href"])):(t(),n(E,{key:2},[s.friendly?(t(),n("div",ye,[o("div",ge,r(s.friendly.main),1),s.friendly.sub?(t(),n("div",ve,r(s.friendly.sub),1)):v("",!0)])):s.raw.msg_type==="text"&&s.raw.text?(t(),n("div",he,r(s.raw.text),1)):s.raw.text?(t(),n("div",we,r(s.raw.text),1)):(t(),n("span",be,"(无法展示该消息类型)"))],64))])],2))),128))])):d.value?v("",!0):(t(),f(z,{key:1,description:"暂无 IM 聊天记录"}))])),[[A,d.value]])])}}}),Me=le(ke,[["__scopeId","data-v-58b699dd"]]);export{Me as default};
import{o as F,R as q,q as t,r as n,v as i,D as c,s as o,L as _,bh as G,w as H,u as x,cX as $,K as j,by as J,ac as K,F as E,G as O,O as f,bB as W,P as v,bi as X,M as m,p as Q,ae as U,T as r,br as Z,aa as ee,bq as se,a8 as ae,E as C}from"./.pnpm-BSw4l71J.js";import{d as te}from"./dayjs-CVa8MSSA.js";import{as as ne,at as oe}from"./tcm-bPbcXX_C.js";import{p as re}from"./im-business-message-parse-oYIP1khU.js";import{_ as le}from"./index-C5omO-nb.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"},be={key:3,class:"muted"},ke=F({__name:"ImChatRecordPanel",props:{diagnosisId:{}},setup(B,{expose:M}){const u=B,d=m(!1),p=m(!1),y=ae([]),L=m(""),g=m(""),D={text:"",image:"图片",file:"文件",sound:"语音",video:"视频",location:"位置",custom:"自定义",face:"表情",other:""},h=Q(()=>y.value.map(e=>{const a=P(e);let l="";return a!=null&&a.tag?l=a.tag:l=D[e.msg_type]||(e.msg_type!=="other"?e.msg_type:""),{raw:e,friendly:a,tag:l}}));function N(e){if(e==null||!e)return"—";const a=e>1e12?Math.floor(e/1e3):e;return te.unix(a).format("YYYY-MM-DD HH:mm:ss")}function P(e){const a=(e.text||"").trim();if(!a)return null;const l=a.startsWith("{")&&(/\bbusinessID\b/.test(a)||/\bcmd\b/.test(a));return e.msg_type==="custom"||l?re(a):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 b(){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 q(()=>u.diagnosisId,()=>{w()},{immediate:!0}),M({refresh:b}),(e,a)=>{const l=G,k=H,I=j,T=Z,Y=ee,V=se,z=W,A=X;return t(),n("div",ie,[i(l,{type:"info","show-icon":"",closable:!1,class:"mb-4",title:"说明"},{default:c(()=>[...a[0]||(a[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(k,{class:"mr-1"},{default:c(()=>[i(x($))]),_:1}),a[1]||(a[1]=_(" 同步最新(后台异步) ",-1))]),_:1},8,["loading"]),i(I,{type:"primary",link:"",loading:d.value,onClick:b},{default:c(()=>[i(k,{class:"mr-1"},{default:c(()=>[i(x(J))]),_:1}),a[2]||(a[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,s=>(t(),n("div",{key:s.raw.msg_id,class:U(["chat-row",s.raw.is_from_doctor?"from-doctor":"from-patient"])},[o("div",ue,[o("span",fe,r(R(s.raw)),1),o("span",me,r(N(s.raw.time)),1),s.tag?(t(),f(T,{key:0,size:"small",type:"info",class:"ml-2"},{default:c(()=>[_(r(s.tag),1)]),_:2},1024)):v("",!0)]),o("div",pe,[s.raw.msg_type==="image"&&s.raw.image_url?(t(),f(Y,{key:0,src:s.raw.image_url,"preview-src-list":[s.raw.image_url],fit:"contain",class:"chat-img"},null,8,["src","preview-src-list"])):(s.raw.msg_type==="file"||s.raw.msg_type==="sound"||s.raw.msg_type==="video")&&s.raw.file_url?(t(),f(V,{key:1,href:s.raw.file_url,target:"_blank",type:"primary"},{default:c(()=>[_(r(s.raw.file_name||"打开文件"),1)]),_:2},1032,["href"])):(t(),n(E,{key:2},[s.friendly?(t(),n("div",ye,[o("div",ge,r(s.friendly.main),1),s.friendly.sub?(t(),n("div",ve,r(s.friendly.sub),1)):v("",!0)])):s.raw.msg_type==="text"&&s.raw.text?(t(),n("div",he,r(s.raw.text),1)):s.raw.text?(t(),n("div",we,r(s.raw.text),1)):(t(),n("span",be,"(无法展示该消息类型)"))],64))])],2))),128))])):d.value?v("",!0):(t(),f(z,{key:1,description:"暂无 IM 聊天记录"}))])),[[A,d.value]])])}}}),Me=le(ke,[["__scopeId","data-v-58b699dd"]]);export{Me as default};
@@ -1 +1 @@
import{o as S,q as a,r as l,ae as x,v as r,D as m,L as d,T as i,a0 as D,s as n,O as g,br as G,P as B,aa as K,u as v,bE as L,w as q,bF as A,bG as H,bH as O,F as P,K as U,p as u}from"./.pnpm-BSw4l71J.js";import{t as j,_ as J}from"./index-C7M1esnu.js";const Q={class:"msg-body"},R={class:"msg-meta"},W={class:"msg-sender"},X={class:"msg-time"},Y={key:0,class:"content-text"},Z={key:2,class:"content-pending"},ee=["src"],se={key:4,class:"content-pending"},te=["src"],ae={key:6,class:"content-pending"},ne={key:7,class:"content-file"},le={class:"file-meta"},ie=["title"],oe={class:"file-info"},ce={key:8,class:"content-fallback"},re={class:"fallback-title"},me={class:"fallback-desc"},ue=S({__name:"MessageBubble",props:{message:{}},setup(s){const o=s,w=u(()=>o.message.from_is_staff),c=u(()=>(o.message.media||[]).find(t=>t.status===1)??null),_=u(()=>{const e=o.message.from_profile;return e&&e.name||o.message.from_user}),z=u(()=>{const e=o.message.from_profile;return(e==null?void 0:e.avatar)||""}),F=u(()=>(_.value||"").slice(-2)||"?"),E=u(()=>{const e=o.message.msgtype;return e==="image"||e==="video"||e==="voice"?"bubble-media":e==="file"?"bubble-file":"bubble-text"}),C=u(()=>{switch(o.message.msgtype){case"link":return"[链接]";case"weapp":return"[小程序]";case"chatrecord":return"[聊天记录]";case"location":return"[位置]";case"card":return"[名片]";case"meeting":return"[会议]";case"docmsg":return"[文档]";case"mixed":return"[混合消息]";case"emotion":return"[表情]";default:return`[${o.message.msgtype}]`}});function M(e){return e?j(e*1e3,"mm-dd hh:MM"):""}function N(e){return e?e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:e<1024*1024*1024?`${(e/1024/1024).toFixed(2)} MB`:`${(e/1024/1024/1024).toFixed(2)} GB`:""}function $(e){window.open(e,"_blank","noopener")}return(e,t)=>{var k,y,p,h;const T=D,b=G,V=K,f=q,I=U;return a(),l("div",{class:x(["msg-bubble",{"is-staff":w.value}])},[r(T,{class:"msg-avatar",size:36,src:z.value},{default:m(()=>[d(i(F.value),1)]),_:1},8,["src"]),n("div",Q,[n("div",R,[n("span",W,i(_.value),1),n("span",X,i(M(s.message.send_time)),1),s.message.action==="recall"?(a(),g(b,{key:0,size:"small",type:"info"},{default:m(()=>[...t[1]||(t[1]=[d(" 已撤回 ",-1)])]),_:1})):B("",!0)]),n("div",{class:x(["msg-content",E.value])},[s.message.msgtype==="text"||s.message.msgtype==="markdown"?(a(),l("div",Y,i(s.message.content),1)):s.message.msgtype==="image"&&((k=c.value)!=null&&k.file_url)?(a(),g(V,{key:1,src:c.value.file_url,"preview-src-list":[c.value.file_url],fit:"contain",class:"content-image","hide-on-click-modal":""},null,8,["src","preview-src-list"])):s.message.msgtype==="image"?(a(),l("div",Z,[r(f,null,{default:m(()=>[r(v(L))]),_:1}),t[2]||(t[2]=n("span",null,"图片下载中…",-1))])):s.message.msgtype==="video"&&((y=c.value)!=null&&y.file_url)?(a(),l("video",{key:3,src:c.value.file_url,controls:"",class:"content-video"},null,8,ee)):s.message.msgtype==="video"?(a(),l("div",se,[r(f,null,{default:m(()=>[r(v(A))]),_:1}),t[3]||(t[3]=n("span",null,"视频下载中…",-1))])):s.message.msgtype==="voice"&&((p=c.value)!=null&&p.file_url)?(a(),l("audio",{key:5,src:c.value.file_url,controls:"",class:"content-audio"},null,8,te)):s.message.msgtype==="voice"?(a(),l("div",ae,[r(f,null,{default:m(()=>[r(v(H))]),_:1}),n("span",null,"语音下载中…("+i(s.message.play_length)+"s",1)])):s.message.msgtype==="file"?(a(),l("div",ne,[r(f,{size:24},{default:m(()=>[r(v(O))]),_:1}),n("div",le,[n("div",{class:"file-name",title:s.message.file_name},i(s.message.file_name||"未命名文件"),9,ie),n("div",oe,[d(i(N(s.message.file_size))+" ",1),s.message.file_ext?(a(),l(P,{key:0},[d("· "+i(s.message.file_ext),1)],64)):B("",!0)])]),(h=c.value)!=null&&h.file_url?(a(),g(I,{key:0,size:"small",type:"primary",link:"",onClick:t[0]||(t[0]=de=>$(c.value.file_url))},{default:m(()=>[...t[4]||(t[4]=[d(" 下载 ",-1)])]),_:1})):(a(),g(b,{key:1,size:"small",type:"info"},{default:m(()=>[...t[5]||(t[5]=[d("下载中…",-1)])]),_:1}))])):(a(),l("div",ce,[n("div",re,i(C.value),1),n("div",me,i(s.message.content),1)]))],2)])],2)}}}),ve=J(ue,[["__scopeId","data-v-b5ff1168"]]);export{ve as default};
import{o as S,q as a,r as l,ae as x,v as r,D as m,L as d,T as i,a0 as D,s as n,O as g,br as G,P as B,aa as K,u as v,bE as L,w as q,bF as A,bG as H,bH as O,F as P,K as U,p as u}from"./.pnpm-BSw4l71J.js";import{t as j,_ as J}from"./index-C5omO-nb.js";const Q={class:"msg-body"},R={class:"msg-meta"},W={class:"msg-sender"},X={class:"msg-time"},Y={key:0,class:"content-text"},Z={key:2,class:"content-pending"},ee=["src"],se={key:4,class:"content-pending"},te=["src"],ae={key:6,class:"content-pending"},ne={key:7,class:"content-file"},le={class:"file-meta"},ie=["title"],oe={class:"file-info"},ce={key:8,class:"content-fallback"},re={class:"fallback-title"},me={class:"fallback-desc"},ue=S({__name:"MessageBubble",props:{message:{}},setup(s){const o=s,w=u(()=>o.message.from_is_staff),c=u(()=>(o.message.media||[]).find(t=>t.status===1)??null),_=u(()=>{const e=o.message.from_profile;return e&&e.name||o.message.from_user}),z=u(()=>{const e=o.message.from_profile;return(e==null?void 0:e.avatar)||""}),F=u(()=>(_.value||"").slice(-2)||"?"),E=u(()=>{const e=o.message.msgtype;return e==="image"||e==="video"||e==="voice"?"bubble-media":e==="file"?"bubble-file":"bubble-text"}),C=u(()=>{switch(o.message.msgtype){case"link":return"[链接]";case"weapp":return"[小程序]";case"chatrecord":return"[聊天记录]";case"location":return"[位置]";case"card":return"[名片]";case"meeting":return"[会议]";case"docmsg":return"[文档]";case"mixed":return"[混合消息]";case"emotion":return"[表情]";default:return`[${o.message.msgtype}]`}});function M(e){return e?j(e*1e3,"mm-dd hh:MM"):""}function N(e){return e?e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:e<1024*1024*1024?`${(e/1024/1024).toFixed(2)} MB`:`${(e/1024/1024/1024).toFixed(2)} GB`:""}function $(e){window.open(e,"_blank","noopener")}return(e,t)=>{var k,y,p,h;const T=D,b=G,V=K,f=q,I=U;return a(),l("div",{class:x(["msg-bubble",{"is-staff":w.value}])},[r(T,{class:"msg-avatar",size:36,src:z.value},{default:m(()=>[d(i(F.value),1)]),_:1},8,["src"]),n("div",Q,[n("div",R,[n("span",W,i(_.value),1),n("span",X,i(M(s.message.send_time)),1),s.message.action==="recall"?(a(),g(b,{key:0,size:"small",type:"info"},{default:m(()=>[...t[1]||(t[1]=[d(" 已撤回 ",-1)])]),_:1})):B("",!0)]),n("div",{class:x(["msg-content",E.value])},[s.message.msgtype==="text"||s.message.msgtype==="markdown"?(a(),l("div",Y,i(s.message.content),1)):s.message.msgtype==="image"&&((k=c.value)!=null&&k.file_url)?(a(),g(V,{key:1,src:c.value.file_url,"preview-src-list":[c.value.file_url],fit:"contain",class:"content-image","hide-on-click-modal":""},null,8,["src","preview-src-list"])):s.message.msgtype==="image"?(a(),l("div",Z,[r(f,null,{default:m(()=>[r(v(L))]),_:1}),t[2]||(t[2]=n("span",null,"图片下载中…",-1))])):s.message.msgtype==="video"&&((y=c.value)!=null&&y.file_url)?(a(),l("video",{key:3,src:c.value.file_url,controls:"",class:"content-video"},null,8,ee)):s.message.msgtype==="video"?(a(),l("div",se,[r(f,null,{default:m(()=>[r(v(A))]),_:1}),t[3]||(t[3]=n("span",null,"视频下载中…",-1))])):s.message.msgtype==="voice"&&((p=c.value)!=null&&p.file_url)?(a(),l("audio",{key:5,src:c.value.file_url,controls:"",class:"content-audio"},null,8,te)):s.message.msgtype==="voice"?(a(),l("div",ae,[r(f,null,{default:m(()=>[r(v(H))]),_:1}),n("span",null,"语音下载中…("+i(s.message.play_length)+"s",1)])):s.message.msgtype==="file"?(a(),l("div",ne,[r(f,{size:24},{default:m(()=>[r(v(O))]),_:1}),n("div",le,[n("div",{class:"file-name",title:s.message.file_name},i(s.message.file_name||"未命名文件"),9,ie),n("div",oe,[d(i(N(s.message.file_size))+" ",1),s.message.file_ext?(a(),l(P,{key:0},[d("· "+i(s.message.file_ext),1)],64)):B("",!0)])]),(h=c.value)!=null&&h.file_url?(a(),g(I,{key:0,size:"small",type:"primary",link:"",onClick:t[0]||(t[0]=de=>$(c.value.file_url))},{default:m(()=>[...t[4]||(t[4]=[d(" 下载 ",-1)])]),_:1})):(a(),g(b,{key:1,size:"small",type:"info"},{default:m(()=>[...t[5]||(t[5]=[d("下载中…",-1)])]),_:1}))])):(a(),l("div",ce,[n("div",re,i(C.value),1),n("div",me,i(s.message.content),1)]))],2)])],2)}}}),ve=J(ue,[["__scopeId","data-v-b5ff1168"]]);export{ve as default};
@@ -1,2 +1,2 @@
import{o as ie,R as oe,q as n,r as o,O as k,K as le,D as r,L as P,P as m,v as l,s as d,F as w,G as E,bB as ae,b7 as re,bt as de,p as ue,M as g,T as U,aa as me,u as V,d9 as z,aw as M,w as ce,bH as pe,e as ge}from"./.pnpm-BSw4l71J.js";import{_ as fe}from"./picker-BAOfQDdS.js";import{e as ve,c as _e,i as f,_ as ye}from"./index-C7M1esnu.js";import{a as T,d as he}from"./patient-gF122Y6k.js";import{h as ke}from"./perm-CZKo0Mmn.js";import"./index-BI2XUhVK.js";import"./index-Ho7YRaEv.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./index-CDzrObNE.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./usePaging-BeGcb2kN.js";const we={class:"note-timeline-wrap"},Ie={key:0,class:"timeline-actions"},Ce={class:"upload-trigger"},be={class:"upload-trigger"},xe={key:1,class:"note-timeline"},Ee={class:"timeline-date"},Ve={class:"timeline-body"},Ne={key:0,class:"timeline-content"},Se={key:1,class:"timeline-images"},De={key:2,class:"timeline-images"},Be={key:0,class:"thumb-wrap"},Ae={key:1,class:"file-wrap"},Pe=["href","title"],Ue={class:"file-name"},W=8e3,ze=ie({__name:"NoteTimeline",props:{notes:{},diagnosisId:{},readonly:{type:Boolean}},emits:["refresh"],setup(u,{emit:j}){const c=u,I=j,X=ue(()=>ke(["doctor.appointment/addDoctorNote"])),v=g(!1),C=g(""),N=g(!1),q=ve(),_=t=>q.getImageUrl(t),S=g([]),D=g([]),y=g(0),h=g(0),H=["jpg","jpeg","png","gif","bmp","webp","svg"],B=t=>{var i;const e=((i=t.split(".").pop())==null?void 0:i.toLowerCase().split("?")[0])||"";return H.includes(e)},$=t=>{var i;const e=t.split("/");return decodeURIComponent(((i=e[e.length-1])==null?void 0:i.split("?")[0])||"文件")},K=t=>t.filter(B).map(_),Z=(t,e)=>{const i=t.filter(B),b=t[e];return i.indexOf(b)},J=t=>t?t.split(`
import{o as ie,R as oe,q as n,r as o,O as k,K as le,D as r,L as P,P as m,v as l,s as d,F as w,G as E,bB as ae,b7 as re,bt as de,p as ue,M as g,T as U,aa as me,u as V,d9 as z,aw as M,w as ce,bH as pe,e as ge}from"./.pnpm-BSw4l71J.js";import{_ as fe}from"./picker-AxnNHltM.js";import{e as ve,c as _e,i as f,_ as ye}from"./index-C5omO-nb.js";import{a as T,d as he}from"./patient-fCgMLzW1.js";import{h as ke}from"./perm-Dzy61UcU.js";import"./index-DWDHA_E8.js";import"./index-e9VVOPkd.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./index-AZbkqFiT.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./usePaging-BeGcb2kN.js";const we={class:"note-timeline-wrap"},Ie={key:0,class:"timeline-actions"},Ce={class:"upload-trigger"},be={class:"upload-trigger"},xe={key:1,class:"note-timeline"},Ee={class:"timeline-date"},Ve={class:"timeline-body"},Ne={key:0,class:"timeline-content"},Se={key:1,class:"timeline-images"},De={key:2,class:"timeline-images"},Be={key:0,class:"thumb-wrap"},Ae={key:1,class:"file-wrap"},Pe=["href","title"],Ue={class:"file-name"},W=8e3,ze=ie({__name:"NoteTimeline",props:{notes:{},diagnosisId:{},readonly:{type:Boolean}},emits:["refresh"],setup(u,{emit:j}){const c=u,I=j,X=ue(()=>ke(["doctor.appointment/addDoctorNote"])),v=g(!1),C=g(""),N=g(!1),q=ve(),_=t=>q.getImageUrl(t),S=g([]),D=g([]),y=g(0),h=g(0),H=["jpg","jpeg","png","gif","bmp","webp","svg"],B=t=>{var i;const e=((i=t.split(".").pop())==null?void 0:i.toLowerCase().split("?")[0])||"";return H.includes(e)},$=t=>{var i;const e=t.split("/");return decodeURIComponent(((i=e[e.length-1])==null?void 0:i.split("?")[0])||"文件")},K=t=>t.filter(B).map(_),Z=(t,e)=>{const i=t.filter(B),b=t[e];return i.indexOf(b)},J=t=>t?t.split(`
`).filter(Boolean):[],Q=t=>{if(!c.diagnosisId)return;const e=Array.isArray(t)?t:[t];if(e.length<=y.value){y.value=e.length;return}const i=e.slice(y.value);y.value=e.length,i.length>0&&T({diagnosis_id:c.diagnosisId,tongue_images:i}).then(()=>{f.msgSuccess("舌苔照片已添加"),I("refresh")})},Y=t=>{if(!c.diagnosisId)return;const e=Array.isArray(t)?t:[t];if(e.length<=h.value){h.value=e.length;return}const i=e.slice(h.value);h.value=e.length,i.length>0&&T({diagnosis_id:c.diagnosisId,report_files:i}).then(()=>{f.msgSuccess("检查报告已添加"),I("refresh")})};oe(()=>c.notes,()=>{S.value=[],D.value=[],y.value=0,h.value=0});const ee=async()=>{if(!c.diagnosisId){f.msgWarning("当前无诊单,无法添加备注");return}const t=C.value.trim();if(!t){f.msgWarning("请输入备注内容");return}N.value=!0;try{await T({diagnosis_id:c.diagnosisId,content:t}),f.msgSuccess("备注保存成功"),C.value="",v.value=!1,I("refresh")}catch(e){f.msgError((e==null?void 0:e.message)||"保存失败")}finally{N.value=!1}},A=async(t,e,i)=>{try{await ge.confirm("确认删除?","提示",{type:"warning"})}catch{return}await he({note_id:t,image_type:e,image_path:i}),f.msgSuccess("已删除"),I("refresh")};return(t,e)=>{const i=le,b=_e,F=fe,L=me,x=ce,te=ae,se=re,ne=de;return n(),o("div",we,[!u.readonly&&u.diagnosisId?(n(),o("div",Ie,[X.value?(n(),k(i,{key:0,type:"primary",plain:"",size:"small",onClick:e[0]||(e[0]=s=>v.value=!0)},{default:r(()=>[...e[6]||(e[6]=[P(" 添加备注 ",-1)])]),_:1})):m("",!0),l(F,{modelValue:S.value,"onUpdate:modelValue":e[1]||(e[1]=s=>S.value=s),limit:99,type:"image","exclude-domain":!0,onChange:Q},{upload:r(()=>[d("div",Ce,[l(b,{size:20,name:"el-icon-Plus"}),e[7]||(e[7]=d("span",null,"舌苔照片",-1))])]),_:1},8,["modelValue"]),l(F,{modelValue:D.value,"onUpdate:modelValue":e[2]||(e[2]=s=>D.value=s),limit:99,type:"file","exclude-domain":!0,onChange:Y},{upload:r(()=>[d("div",be,[l(b,{size:20,name:"el-icon-Plus"}),e[8]||(e[8]=d("span",null,"检查报告",-1))])]),_:1},8,["modelValue"])])):m("",!0),u.notes.length?(n(),o("div",xe,[(n(!0),o(w,null,E(u.notes,s=>{var R,G;return n(),o("div",{key:s.id,class:"timeline-node"},[e[11]||(e[11]=d("div",{class:"timeline-dot"},null,-1)),d("div",Ee,U(s.note_date),1),d("div",Ve,[s.content?(n(),o("div",Ne,[(n(!0),o(w,null,E(J(s.content),(a,p)=>(n(),o("div",{key:p,class:"content-line"},U(a),1))),128))])):m("",!0),(R=s.tongue_images)!=null&&R.length?(n(),o("div",Se,[e[9]||(e[9]=d("span",{class:"images-label"},"舌苔照片",-1)),(n(!0),o(w,null,E(s.tongue_images,(a,p)=>(n(),o("div",{key:p,class:"thumb-wrap"},[l(L,{src:_(a),"preview-src-list":s.tongue_images.map(_),"initial-index":p,"z-index":W,fit:"cover",class:"timeline-thumb","preview-teleported":""},null,8,["src","preview-src-list","initial-index"]),u.readonly?m("",!0):(n(),k(x,{key:0,class:"thumb-delete",onClick:M(O=>A(s.id,"tongue_images",a),["stop"])},{default:r(()=>[l(V(z))]),_:1},8,["onClick"]))]))),128))])):m("",!0),(G=s.report_files)!=null&&G.length?(n(),o("div",De,[e[10]||(e[10]=d("span",{class:"images-label"},"检查报告",-1)),(n(!0),o(w,null,E(s.report_files,(a,p)=>(n(),o(w,{key:p},[B(a)?(n(),o("div",Be,[l(L,{src:_(a),"preview-src-list":K(s.report_files),"initial-index":Z(s.report_files,p),"z-index":W,fit:"cover",class:"timeline-thumb","preview-teleported":""},null,8,["src","preview-src-list","initial-index"]),u.readonly?m("",!0):(n(),k(x,{key:0,class:"thumb-delete",onClick:M(O=>A(s.id,"report_files",a),["stop"])},{default:r(()=>[l(V(z))]),_:1},8,["onClick"]))])):(n(),o("div",Ae,[d("a",{href:_(a),target:"_blank",class:"file-link",title:$(a)},[l(x,{size:20},{default:r(()=>[l(V(pe))]),_:1}),d("span",Ue,U($(a)),1)],8,Pe),u.readonly?m("",!0):(n(),k(x,{key:0,class:"file-delete",onClick:M(O=>A(s.id,"report_files",a),["stop"])},{default:r(()=>[l(V(z))]),_:1},8,["onClick"]))]))],64))),128))])):m("",!0)])])}),128))])):m("",!0),!u.notes.length&&u.readonly?(n(),k(te,{key:2,description:"暂无备注","image-size":48})):m("",!0),l(ne,{modelValue:v.value,"onUpdate:modelValue":e[5]||(e[5]=s=>v.value=s),title:"添加备注",width:"480px","append-to-body":""},{footer:r(()=>[l(i,{onClick:e[4]||(e[4]=s=>v.value=!1)},{default:r(()=>[...e[12]||(e[12]=[P("取消",-1)])]),_:1}),l(i,{type:"primary",loading:N.value,onClick:ee},{default:r(()=>[...e[13]||(e[13]=[P("保存",-1)])]),_:1},8,["loading"])]),default:r(()=>[l(se,{modelValue:C.value,"onUpdate:modelValue":e[3]||(e[3]=s=>C.value=s),type:"textarea",rows:4,placeholder:"输入今日备注...",maxlength:"500","show-word-limit":""},null,8,["modelValue"])]),_:1},8,["modelValue"])])}}}),Ke=ye(ze,[["__scopeId","data-v-530ad386"]]);export{Ke as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{m as I,f as p,a as w}from"./diag-display-DCz_VAqj.js";import{o as y,q as x,r as B,s as e,T as a,u as i,P as N}from"./.pnpm-BSw4l71J.js";import{_ as V}from"./index-C7M1esnu.js";const b={class:"card patient-card"},q={class:"patient-hero"},D={class:"patient-name"},E={class:"patient-meta"},G={key:0},S=y({__name:"PatientInfoCard",props:{apt:{default:()=>({})},diag:{default:()=>({})}},setup(t){return(T,n)=>{var d,s,o,c,m,l,r,g,u,f,h,v,k,P,C;return x(),B("div",b,[n[0]||(n[0]=e("div",{class:"card-title"},"患者信息",-1)),e("div",q,[e("div",D,a(((d=t.apt)==null?void 0:d.patient_name)||"—"),1),e("div",E,[e("div",null,a(i(I)((s=t.apt)==null?void 0:s.patient_phone))+" · "+a(i(p)((o=t.diag)==null?void 0:o.gender))+" · "+a(((c=t.diag)==null?void 0:c.age)!=null?t.diag.age+"岁":"—"),1),e("div",null,a((m=t.diag)!=null&&m.height?t.diag.height+"cm":"—")+" / "+a((l=t.diag)!=null&&l.weight?t.diag.weight+"kg":"—")+" · "+a(((r=t.diag)==null?void 0:r.region)||"—"),1),e("div",null," 预约:"+a((g=t.apt)==null?void 0:g.appointment_date)+" "+a((u=t.apt)==null?void 0:u.appointment_time)+" · "+a(i(w)((f=t.apt)==null?void 0:f.period)),1),e("div",null,"医生:"+a(((h=t.apt)==null?void 0:h.doctor_name)||"—")+" 客服:"+a(((v=t.apt)==null?void 0:v.assistant_name)||"—"),1),e("div",null," 状态:"+a(((k=t.apt)==null?void 0:k.status_desc)||"—")+" · "+a((P=t.apt)!=null&&P.has_prescription?"已开方":"未开方"),1),(C=t.apt)!=null&&C.remark?(x(),B("div",G,"备注:"+a(t.apt.remark),1)):N("",!0)])])])}}}),F=V(S,[["__scopeId","data-v-1b0de09c"]]);export{F as default};
import{m as I,f as p,a as w}from"./diag-display-DCz_VAqj.js";import{o as y,q as x,r as B,s as e,T as a,u as i,P as N}from"./.pnpm-BSw4l71J.js";import{_ as V}from"./index-C5omO-nb.js";const b={class:"card patient-card"},q={class:"patient-hero"},D={class:"patient-name"},E={class:"patient-meta"},G={key:0},S=y({__name:"PatientInfoCard",props:{apt:{default:()=>({})},diag:{default:()=>({})}},setup(t){return(T,n)=>{var d,s,o,c,m,l,r,g,u,f,h,v,k,P,C;return x(),B("div",b,[n[0]||(n[0]=e("div",{class:"card-title"},"患者信息",-1)),e("div",q,[e("div",D,a(((d=t.apt)==null?void 0:d.patient_name)||"—"),1),e("div",E,[e("div",null,a(i(I)((s=t.apt)==null?void 0:s.patient_phone))+" · "+a(i(p)((o=t.diag)==null?void 0:o.gender))+" · "+a(((c=t.diag)==null?void 0:c.age)!=null?t.diag.age+"岁":"—"),1),e("div",null,a((m=t.diag)!=null&&m.height?t.diag.height+"cm":"—")+" / "+a((l=t.diag)!=null&&l.weight?t.diag.weight+"kg":"—")+" · "+a(((r=t.diag)==null?void 0:r.region)||"—"),1),e("div",null," 预约:"+a((g=t.apt)==null?void 0:g.appointment_date)+" "+a((u=t.apt)==null?void 0:u.appointment_time)+" · "+a(i(w)((f=t.apt)==null?void 0:f.period)),1),e("div",null,"医生:"+a(((h=t.apt)==null?void 0:h.doctor_name)||"—")+" 客服:"+a(((v=t.apt)==null?void 0:v.assistant_name)||"—"),1),e("div",null," 状态:"+a(((k=t.apt)==null?void 0:k.status_desc)||"—")+" · "+a((P=t.apt)!=null&&P.has_prescription?"已开方":"未开方"),1),(C=t.apt)!=null&&C.remark?(x(),B("div",G,"备注:"+a(t.apt.remark),1)):N("",!0)])])])}}}),F=V(S,[["__scopeId","data-v-1b0de09c"]]);export{F as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{o as L,q as i,r as c,s as d,F as v,O as m,bq as q,D as _,L as k,P as y,G as B,p as f,T as w}from"./.pnpm-BSw4l71J.js";import H from"./RecordingVideoPlayer-CBiSJJjw.js";import{e as I,_ as P}from"./index-C7M1esnu.js";const R={key:0,class:"recording-list"},C={class:"recording-item"},V={key:1,class:"recording-alternates"},N={class:"recording-alternates__links"},D={key:1,class:"text-gray-400"},E=L({__name:"RecordingPlaybackBlock",props:{recordId:{},urls:{}},setup(l){const $=l,h=I(),u=f(()=>{const e=$.urls||[],t=new Set,r=[];for(const o of e){const s=String(o??"").trim();!s||t.has(s)||(t.add(s),r.push(s))}return r}),a=f(()=>{const e=u.value;if(!e.length)return null;const t=e.find(n=>/\.mp4(\?|#|$)/i.test(n));if(t)return t;const r=e.find(n=>/\.m3u8(\?|#|$)/i.test(n)&&/vod-qcloud\.com/i.test(n));if(r)return r;const o=e.find(n=>/\.m3u8(\?|#|$)/i.test(n)&&/\.cos\.[^/]+\.myqcloud\.com/i.test(n));if(o)return o;const s=e.find(n=>/\.m3u8(\?|#|$)/i.test(n));return s||e[0]}),p=f(()=>{const e=u.value,t=a.value;return t?e.filter(r=>r!==t):e.slice(1)});function b(e){if(!e||typeof e!="string")return!1;const t=e.trim();return/^https?:\/\//i.test(t)?/\.(mp4|webm|ogg|mov|mkv|m4v|m3u8)(\?|#|$)/i.test(t):/\.(mp4|webm|ogg|mov|mkv|m4v|m3u8)(\?|#|$)/i.test(t)||t.startsWith("/uploads/")}function g(e){return h.getImageUrl(String(e||"").trim())}function S(e,t){const r=e.trim();return/\.mp4(\?|#|$)/i.test(r)?`MP4 ${t+1}`:/vod-qcloud\.com/i.test(r)&&/\.m3u8/i.test(r)?`点播 ${t+1}`:/\.cos\.[^/]+\.myqcloud\.com/i.test(r)&&/\.m3u8/i.test(r)?`COS HLS ${t+1}`:/\.m3u8/i.test(r)?`HLS ${t+1}`:`链接 ${t+1}`}return(e,t)=>{const r=q;return u.value.length?(i(),c("div",R,[d("div",C,[a.value?(i(),c(v,{key:0},[b(a.value)?(i(),m(H,{key:`${l.recordId}-${a.value}`,src:a.value},null,8,["src"])):(i(),m(r,{key:1,href:g(a.value),target:"_blank",type:"primary"},{default:_(()=>[...t[0]||(t[0]=[k(" 打开回放 ",-1)])]),_:1},8,["href"]))],64)):y("",!0),p.value.length?(i(),c("div",V,[t[1]||(t[1]=d("span",{class:"recording-alternates__label"},"备用地址",-1)),d("div",N,[(i(!0),c(v,null,B(p.value,(o,s)=>(i(),m(r,{key:`${l.recordId}-alt-${s}-${o.slice(-32)}`,href:g(o),target:"_blank",type:"primary",class:"recording-alternates__link"},{default:_(()=>[k(w(S(o,s)),1)]),_:2},1032,["href"]))),128))])])):y("",!0)])])):(i(),c("span",D,"暂无"))}}}),x=P(E,[["__scopeId","data-v-d67b2e91"]]);export{x as default};
import{o as L,q as i,r as c,s as d,F as v,O as m,bq as q,D as _,L as k,P as y,G as B,p as f,T as w}from"./.pnpm-BSw4l71J.js";import H from"./RecordingVideoPlayer-DVjRj1N7.js";import{e as I,_ as P}from"./index-C5omO-nb.js";const R={key:0,class:"recording-list"},C={class:"recording-item"},V={key:1,class:"recording-alternates"},N={class:"recording-alternates__links"},D={key:1,class:"text-gray-400"},E=L({__name:"RecordingPlaybackBlock",props:{recordId:{},urls:{}},setup(l){const $=l,h=I(),u=f(()=>{const e=$.urls||[],t=new Set,r=[];for(const o of e){const s=String(o??"").trim();!s||t.has(s)||(t.add(s),r.push(s))}return r}),a=f(()=>{const e=u.value;if(!e.length)return null;const t=e.find(n=>/\.mp4(\?|#|$)/i.test(n));if(t)return t;const r=e.find(n=>/\.m3u8(\?|#|$)/i.test(n)&&/vod-qcloud\.com/i.test(n));if(r)return r;const o=e.find(n=>/\.m3u8(\?|#|$)/i.test(n)&&/\.cos\.[^/]+\.myqcloud\.com/i.test(n));if(o)return o;const s=e.find(n=>/\.m3u8(\?|#|$)/i.test(n));return s||e[0]}),p=f(()=>{const e=u.value,t=a.value;return t?e.filter(r=>r!==t):e.slice(1)});function b(e){if(!e||typeof e!="string")return!1;const t=e.trim();return/^https?:\/\//i.test(t)?/\.(mp4|webm|ogg|mov|mkv|m4v|m3u8)(\?|#|$)/i.test(t):/\.(mp4|webm|ogg|mov|mkv|m4v|m3u8)(\?|#|$)/i.test(t)||t.startsWith("/uploads/")}function g(e){return h.getImageUrl(String(e||"").trim())}function S(e,t){const r=e.trim();return/\.mp4(\?|#|$)/i.test(r)?`MP4 ${t+1}`:/vod-qcloud\.com/i.test(r)&&/\.m3u8/i.test(r)?`点播 ${t+1}`:/\.cos\.[^/]+\.myqcloud\.com/i.test(r)&&/\.m3u8/i.test(r)?`COS HLS ${t+1}`:/\.m3u8/i.test(r)?`HLS ${t+1}`:`链接 ${t+1}`}return(e,t)=>{const r=q;return u.value.length?(i(),c("div",R,[d("div",C,[a.value?(i(),c(v,{key:0},[b(a.value)?(i(),m(H,{key:`${l.recordId}-${a.value}`,src:a.value},null,8,["src"])):(i(),m(r,{key:1,href:g(a.value),target:"_blank",type:"primary"},{default:_(()=>[...t[0]||(t[0]=[k(" 打开回放 ",-1)])]),_:1},8,["href"]))],64)):y("",!0),p.value.length?(i(),c("div",V,[t[1]||(t[1]=d("span",{class:"recording-alternates__label"},"备用地址",-1)),d("div",N,[(i(!0),c(v,null,B(p.value,(o,s)=>(i(),m(r,{key:`${l.recordId}-alt-${s}-${o.slice(-32)}`,href:g(o),target:"_blank",type:"primary",class:"recording-alternates__link"},{default:_(()=>[k(w(S(o,s)),1)]),_:2},1032,["href"]))),128))])])):y("",!0)])])):(i(),c("span",D,"暂无"))}}}),x=P(E,[["__scopeId","data-v-d67b2e91"]]);export{x as default};
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/.pnpm-BSw4l71J.js","assets/.pnpm-B3v8nGpq.css"])))=>i.map(i=>d[i]);
import{o as K,ap as z,n as S,R as H,aq as G,q as v,r as p,s as f,ac as O,ad as W,b8 as $,aw as j,F as w,v as k,D as I,u as J,b5 as Q,w as X,P,L as Y,bq as Z,M as _,p as E,al as ee}from"./.pnpm-BSw4l71J.js";import{e as ae,_ as ne}from"./index-C7M1esnu.js";const te={class:"recording-playback__shell"},re=["preload"],oe=["onKeydown"],le=K({__name:"RecordingVideoPlayer",props:{src:{}},setup(B){const V=B,A=ae(),b=_(null),m=_(null),c=_(!1),s=_(!1),t=_(!1);let d=null,r=null,l=0,u=null;function i(){u!==null&&(clearTimeout(u),u=null)}const y=E(()=>{const e=String(V.src||"").trim();return e?U(A.getImageUrl(e)):""}),D=E(()=>{if(!s.value)return"none";const e=y.value;return e&&!L(e)?"metadata":"none"}),q=E(()=>!s.value||t.value);function U(e){if(!e||!/^https?:\/\//i.test(e))return e;try{const a=new URL(e),n=a.hostname.toLowerCase();if(n.includes("myqcloud.com")||n.includes("vod-qcloud.com")||n.includes("vod-qcloud"))return a.pathname.includes("+")&&(a.pathname=a.pathname.replace(/\+/g,"%2B")),a.href}catch{}return e}function L(e){return/\.m3u8(\?|#|$)/i.test(e)}function C(){var n;t.value=!1,i();const e=m.value,a=(n=e==null?void 0:e.error)==null?void 0:n.code;a===MediaError.MEDIA_ERR_ABORTED||a===1||(c.value=!0)}function h(){d&&(d.destroy(),d=null);const e=m.value;e&&(e.pause(),e.removeAttribute("src"),e.load())}function R(e,a){let n=!1;const o=()=>{n||a!==l||(n=!0,i(),t.value=!1)};e.addEventListener("loadedmetadata",()=>o(),{once:!0}),e.addEventListener("loadeddata",()=>o(),{once:!0}),e.addEventListener("canplay",()=>o(),{once:!0}),e.addEventListener("playing",()=>o(),{once:!0}),e.addEventListener("progress",()=>{try{e.buffered.length>0&&e.buffered.end(0)>0&&o("progress-buffered")}catch{}},{passive:!0})}async function g(){if(!s.value)return;await S();const e=m.value,a=y.value;if(!e)return;if(!a){t.value=!1,i();return}const n=++l;if(t.value=!0,i(),h(),c.value=!1,L(a)){try{const o=await ee(()=>import("./.pnpm-BSw4l71J.js").then(M=>M.dP),__vite__mapDeps([0,1])),x=o.default;if(x.isSupported()){d=new x({enableWorker:!0,lowLatencyMode:!1,maxBufferLength:20,maxMaxBufferLength:120}),d.on(o.Events.MANIFEST_PARSED,()=>{n===l&&(t.value=!1,i())}),d.on(o.Events.ERROR,(M,F)=>{if(F.fatal){if(n!==l)return;t.value=!1,i(),c.value=!0,h()}}),d.loadSource(a),d.attachMedia(e),u=setTimeout(()=>{u=null,n===l&&t.value&&(t.value=!1)},45e3);return}if(e.canPlayType("application/vnd.apple.mpegurl")){R(e,n),e.src=a,u=setTimeout(()=>{u=null,n===l&&t.value&&(t.value=!1)},45e3);return}}catch{if(n!==l)return;t.value=!1,i(),c.value=!0;return}if(n!==l)return;t.value=!1,i(),c.value=!0;return}R(e,n),e.src=a,u=setTimeout(()=>{u=null,n===l&&t.value&&(t.value=!1)},25e3)}function N(){const e=b.value;if(!e||typeof IntersectionObserver>"u"){s.value=!0,g();return}r=new IntersectionObserver(a=>{for(const n of a)if(n.isIntersecting){r==null||r.disconnect(),r=null,s.value=!0,g();break}},{root:null,rootMargin:"240px 0px",threshold:0}),r.observe(e)}function T(){r==null||r.disconnect(),r=null,s.value||(s.value=!0),g()}return z(()=>{S(()=>{N()})}),H(y,()=>{s.value&&g()}),G(()=>{r==null||r.disconnect(),r=null,i(),l++,h()}),(e,a)=>{const n=X,o=Z;return v(),p("div",{ref_key:"wrapRef",ref:b,class:"recording-playback"},[f("div",te,[O(f("video",{ref_key:"videoRef",ref:m,class:"recording-video",controls:"",playsinline:"","webkit-playsinline":"",preload:D.value,onError:C},null,40,re),[[W,!c.value]]),!c.value&&q.value?(v(),p("div",{key:0,class:"recording-playback__overlay",role:"button",tabindex:"0",onClick:T,onKeydown:$(j(T,["prevent"]),["enter"])},[s.value?(v(),p(w,{key:1},[k(n,{class:"recording-playback__spin"},{default:I(()=>[k(J(Q))]),_:1}),a[2]||(a[2]=f("span",null,"正在加载…",-1))],64)):(v(),p(w,{key:0},[a[0]||(a[0]=f("span",{class:"recording-playback__overlay-title"},"预览待加载",-1)),a[1]||(a[1]=f("span",{class:"recording-playback__overlay-desc"},"滚动至此处将自动加载(减轻并发卡顿),也可点击立即加载",-1))],64))],40,oe)):P("",!0)]),c.value?(v(),p(w,{key:0},[k(o,{href:y.value||"#",target:"_blank",type:"primary"},{default:I(()=>[...a[3]||(a[3]=[Y("在新窗口打开",-1)])]),_:1},8,["href"]),a[4]||(a[4]=f("div",{class:"recording-playback__hint"}," 内嵌播放失败(编码不支持、跨域或私有存储等)时可尝试在新窗口打开。 ",-1))],64)):P("",!0)],512)}}}),ie=ne(le,[["__scopeId","data-v-749b0199"]]);export{ie as default};
import{o as K,ap as z,n as S,R as H,aq as G,q as v,r as p,s as f,ac as O,ad as W,b8 as $,aw as j,F as w,v as k,D as I,u as J,b5 as Q,w as X,P,L as Y,bq as Z,M as _,p as E,al as ee}from"./.pnpm-BSw4l71J.js";import{e as ae,_ as ne}from"./index-C5omO-nb.js";const te={class:"recording-playback__shell"},re=["preload"],oe=["onKeydown"],le=K({__name:"RecordingVideoPlayer",props:{src:{}},setup(B){const V=B,A=ae(),b=_(null),m=_(null),c=_(!1),s=_(!1),t=_(!1);let d=null,r=null,l=0,u=null;function i(){u!==null&&(clearTimeout(u),u=null)}const y=E(()=>{const e=String(V.src||"").trim();return e?U(A.getImageUrl(e)):""}),D=E(()=>{if(!s.value)return"none";const e=y.value;return e&&!L(e)?"metadata":"none"}),q=E(()=>!s.value||t.value);function U(e){if(!e||!/^https?:\/\//i.test(e))return e;try{const a=new URL(e),n=a.hostname.toLowerCase();if(n.includes("myqcloud.com")||n.includes("vod-qcloud.com")||n.includes("vod-qcloud"))return a.pathname.includes("+")&&(a.pathname=a.pathname.replace(/\+/g,"%2B")),a.href}catch{}return e}function L(e){return/\.m3u8(\?|#|$)/i.test(e)}function C(){var n;t.value=!1,i();const e=m.value,a=(n=e==null?void 0:e.error)==null?void 0:n.code;a===MediaError.MEDIA_ERR_ABORTED||a===1||(c.value=!0)}function h(){d&&(d.destroy(),d=null);const e=m.value;e&&(e.pause(),e.removeAttribute("src"),e.load())}function R(e,a){let n=!1;const o=()=>{n||a!==l||(n=!0,i(),t.value=!1)};e.addEventListener("loadedmetadata",()=>o(),{once:!0}),e.addEventListener("loadeddata",()=>o(),{once:!0}),e.addEventListener("canplay",()=>o(),{once:!0}),e.addEventListener("playing",()=>o(),{once:!0}),e.addEventListener("progress",()=>{try{e.buffered.length>0&&e.buffered.end(0)>0&&o("progress-buffered")}catch{}},{passive:!0})}async function g(){if(!s.value)return;await S();const e=m.value,a=y.value;if(!e)return;if(!a){t.value=!1,i();return}const n=++l;if(t.value=!0,i(),h(),c.value=!1,L(a)){try{const o=await ee(()=>import("./.pnpm-BSw4l71J.js").then(M=>M.dP),__vite__mapDeps([0,1])),x=o.default;if(x.isSupported()){d=new x({enableWorker:!0,lowLatencyMode:!1,maxBufferLength:20,maxMaxBufferLength:120}),d.on(o.Events.MANIFEST_PARSED,()=>{n===l&&(t.value=!1,i())}),d.on(o.Events.ERROR,(M,F)=>{if(F.fatal){if(n!==l)return;t.value=!1,i(),c.value=!0,h()}}),d.loadSource(a),d.attachMedia(e),u=setTimeout(()=>{u=null,n===l&&t.value&&(t.value=!1)},45e3);return}if(e.canPlayType("application/vnd.apple.mpegurl")){R(e,n),e.src=a,u=setTimeout(()=>{u=null,n===l&&t.value&&(t.value=!1)},45e3);return}}catch{if(n!==l)return;t.value=!1,i(),c.value=!0;return}if(n!==l)return;t.value=!1,i(),c.value=!0;return}R(e,n),e.src=a,u=setTimeout(()=>{u=null,n===l&&t.value&&(t.value=!1)},25e3)}function N(){const e=b.value;if(!e||typeof IntersectionObserver>"u"){s.value=!0,g();return}r=new IntersectionObserver(a=>{for(const n of a)if(n.isIntersecting){r==null||r.disconnect(),r=null,s.value=!0,g();break}},{root:null,rootMargin:"240px 0px",threshold:0}),r.observe(e)}function T(){r==null||r.disconnect(),r=null,s.value||(s.value=!0),g()}return z(()=>{S(()=>{N()})}),H(y,()=>{s.value&&g()}),G(()=>{r==null||r.disconnect(),r=null,i(),l++,h()}),(e,a)=>{const n=X,o=Z;return v(),p("div",{ref_key:"wrapRef",ref:b,class:"recording-playback"},[f("div",te,[O(f("video",{ref_key:"videoRef",ref:m,class:"recording-video",controls:"",playsinline:"","webkit-playsinline":"",preload:D.value,onError:C},null,40,re),[[W,!c.value]]),!c.value&&q.value?(v(),p("div",{key:0,class:"recording-playback__overlay",role:"button",tabindex:"0",onClick:T,onKeydown:$(j(T,["prevent"]),["enter"])},[s.value?(v(),p(w,{key:1},[k(n,{class:"recording-playback__spin"},{default:I(()=>[k(J(Q))]),_:1}),a[2]||(a[2]=f("span",null,"正在加载…",-1))],64)):(v(),p(w,{key:0},[a[0]||(a[0]=f("span",{class:"recording-playback__overlay-title"},"预览待加载",-1)),a[1]||(a[1]=f("span",{class:"recording-playback__overlay-desc"},"滚动至此处将自动加载(减轻并发卡顿),也可点击立即加载",-1))],64))],40,oe)):P("",!0)]),c.value?(v(),p(w,{key:0},[k(o,{href:y.value||"#",target:"_blank",type:"primary"},{default:I(()=>[...a[3]||(a[3]=[Y("在新窗口打开",-1)])]),_:1},8,["href"]),a[4]||(a[4]=f("div",{class:"recording-playback__hint"}," 内嵌播放失败(编码不支持、跨域或私有存储等)时可尝试在新窗口打开。 ",-1))],64)):P("",!0)],512)}}}),ie=ne(le,[["__scopeId","data-v-749b0199"]]);export{ie as default};
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
import{o as T,q as e,r as t,v as m,b7 as V,s as d,D as w,L as I,K as E,P as r,F as u,G as v,T as g,O as C,bB as z,M as p}from"./.pnpm-BSw4l71J.js";import{a5 as L}from"./tcm-DE5Wfo6u.js";import{i as M,_ as S}from"./index-C7M1esnu.js";/* empty css */const D={class:"tracking-timeline-wrap"},F={key:0,class:"timeline-input"},H={class:"input-actions"},q={key:1,class:"tracking-timeline"},A={class:"timeline-date"},G={class:"timeline-body"},K={key:0,class:"timeline-content"},O=T({__name:"TrackingNoteTimeline",props:{notes:{},diagnosisId:{},readonly:{type:Boolean}},emits:["refresh"],setup(n,{emit:f}){const c=n,y=f,o=p(""),l=p(!1),_=s=>s?s.split(`
import{o as T,q as e,r as t,v as m,b7 as V,s as d,D as w,L as I,K as E,P as r,F as u,G as v,T as g,O as C,bB as z,M as p}from"./.pnpm-BSw4l71J.js";import{a5 as L}from"./tcm-bPbcXX_C.js";import{i as M,_ as S}from"./index-C5omO-nb.js";/* empty css */const D={class:"tracking-timeline-wrap"},F={key:0,class:"timeline-input"},H={class:"input-actions"},q={key:1,class:"tracking-timeline"},A={class:"timeline-date"},G={class:"timeline-body"},K={key:0,class:"timeline-content"},O=T({__name:"TrackingNoteTimeline",props:{notes:{},diagnosisId:{},readonly:{type:Boolean}},emits:["refresh"],setup(n,{emit:f}){const c=n,y=f,o=p(""),l=p(!1),_=s=>s?s.split(`
`).filter(Boolean):[],k=async()=>{if(!c.diagnosisId)return;const s=o.value.trim();if(s){l.value=!0;try{await L({diagnosis_id:c.diagnosisId,tracking_content:s}),M.msgSuccess("已添加"),o.value="",y("refresh")}finally{l.value=!1}}};return(s,i)=>{const b=V,h=E,B=z;return e(),t("div",D,[!n.readonly&&n.diagnosisId?(e(),t("div",F,[m(b,{modelValue:o.value,"onUpdate:modelValue":i[0]||(i[0]=a=>o.value=a),type:"textarea",rows:2,placeholder:"输入跟踪备注,回车换行;保存后将以「[HH:MM] 内容」追加到当天记录",maxlength:"1000","show-word-limit":"",resize:"none",disabled:l.value},null,8,["modelValue","disabled"]),d("div",H,[m(h,{type:"primary",size:"small",loading:l.value,disabled:!o.value.trim(),onClick:k},{default:w(()=>[...i[1]||(i[1]=[I(" 添加 ",-1)])]),_:1},8,["loading","disabled"])])])):r("",!0),n.notes.length?(e(),t("div",q,[(e(!0),t(u,null,v(n.notes,a=>(e(),t("div",{key:a.id,class:"timeline-node"},[i[2]||(i[2]=d("div",{class:"timeline-dot"},null,-1)),d("div",A,g(a.note_date),1),d("div",G,[a.content?(e(),t("div",K,[(e(!0),t(u,null,v(_(a.content),(x,N)=>(e(),t("div",{key:N,class:"content-line"},g(x),1))),128))])):r("",!0)])]))),128))])):r("",!0),!n.notes.length&&n.readonly?(e(),C(B,{key:2,description:"暂无跟踪备注","image-size":48})):r("",!0)])}}}),Q=S(O,[["__scopeId","data-v-82b635bd"]]);export{Q as default};
@@ -1 +0,0 @@
import{_ as o}from"./account-adjust.vue_vue_type_script_setup_true_lang-1tNuVJCo.js";import"./.pnpm-BSw4l71J.js";import"./index-BI2XUhVK.js";import"./index-C7M1esnu.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./account-adjust.vue_vue_type_script_setup_true_lang-vEz-DJgz.js";import"./.pnpm-BSw4l71J.js";import"./index-DWDHA_E8.js";import"./index-C5omO-nb.js";export{o as default};
@@ -1 +1 @@
import{o as h,R as v,q,O as B,D as l,s as D,v as t,b9 as F,u as n,b6 as I,L as u,T as w,bc as j,bf as S,b7 as T,a8 as y,ba as U,p as G}from"./.pnpm-BSw4l71J.js";import{_ as L}from"./index-BI2XUhVK.js";import{i as V}from"./index-C7M1esnu.js";const M={class:"pr-8"},H=h({__name:"account-adjust",props:{show:{type:Boolean,required:!0},value:{type:[Number,String],required:!0}},emits:["update:show","confirm"],setup(d,{emit:k}){const s=y(),i=d,f=k,o=U({action:1,num:"",remark:""}),m=y(),c=G(()=>Number(i.value)+Number(o.num)*(o.action==1?1:-1)),R={num:[{required:!0,message:"请输入调整的金额"}]},g=e=>{if(e.includes("-"))return V.msgError("请输入正整数");o.num=e},x=async()=>{var e;await((e=s.value)==null?void 0:e.validate()),f("confirm",o)},E=()=>{var e;f("update:show",!1),(e=s.value)==null||e.resetFields()};return v(()=>i.show,e=>{var a,r;e?(a=m.value)==null||a.open():(r=m.value)==null||r.close()}),v(c,e=>{e<0&&(V.msgError("调整后余额需大于0"),o.num="")}),(e,a)=>{const r=I,_=S,C=j,b=T,N=F;return q(),B(L,{ref_key:"popupRef",ref:m,title:"余额调整",width:"500px",onConfirm:x,async:!0,onClose:E},{default:l(()=>[D("div",M,[t(N,{ref_key:"formRef",ref:s,model:n(o),"label-width":"120px",rules:R},{default:l(()=>[t(r,{label:"当前余额"},{default:l(()=>[u("¥ "+w(d.value),1)]),_:1}),t(r,{label:"余额增减",required:"",prop:"action"},{default:l(()=>[t(C,{modelValue:n(o).action,"onUpdate:modelValue":a[0]||(a[0]=p=>n(o).action=p)},{default:l(()=>[t(_,{value:1},{default:l(()=>[...a[2]||(a[2]=[u("增加余额",-1)])]),_:1}),t(_,{value:2},{default:l(()=>[...a[3]||(a[3]=[u("扣减余额",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),t(r,{label:"调整余额",prop:"num"},{default:l(()=>[t(b,{"model-value":n(o).num,placeholder:"请输入调整的金额",type:"number",onInput:g},null,8,["model-value"])]),_:1}),t(r,{label:"调整后余额"},{default:l(()=>[u(" ¥ "+w(n(c)),1)]),_:1}),t(r,{label:"备注",prop:"remark"},{default:l(()=>[t(b,{modelValue:n(o).remark,"onUpdate:modelValue":a[1]||(a[1]=p=>n(o).remark=p),type:"textarea",rows:4},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])])]),_:1},512)}}});export{H as _};
import{o as h,R as v,q,O as B,D as l,s as D,v as t,b9 as F,u as n,b6 as I,L as u,T as w,bc as j,bf as S,b7 as T,a8 as y,ba as U,p as G}from"./.pnpm-BSw4l71J.js";import{_ as L}from"./index-DWDHA_E8.js";import{i as V}from"./index-C5omO-nb.js";const M={class:"pr-8"},H=h({__name:"account-adjust",props:{show:{type:Boolean,required:!0},value:{type:[Number,String],required:!0}},emits:["update:show","confirm"],setup(d,{emit:k}){const s=y(),i=d,f=k,o=U({action:1,num:"",remark:""}),m=y(),c=G(()=>Number(i.value)+Number(o.num)*(o.action==1?1:-1)),R={num:[{required:!0,message:"请输入调整的金额"}]},g=e=>{if(e.includes("-"))return V.msgError("请输入正整数");o.num=e},x=async()=>{var e;await((e=s.value)==null?void 0:e.validate()),f("confirm",o)},E=()=>{var e;f("update:show",!1),(e=s.value)==null||e.resetFields()};return v(()=>i.show,e=>{var a,r;e?(a=m.value)==null||a.open():(r=m.value)==null||r.close()}),v(c,e=>{e<0&&(V.msgError("调整后余额需大于0"),o.num="")}),(e,a)=>{const r=I,_=S,C=j,b=T,N=F;return q(),B(L,{ref_key:"popupRef",ref:m,title:"余额调整",width:"500px",onConfirm:x,async:!0,onClose:E},{default:l(()=>[D("div",M,[t(N,{ref_key:"formRef",ref:s,model:n(o),"label-width":"120px",rules:R},{default:l(()=>[t(r,{label:"当前余额"},{default:l(()=>[u("¥ "+w(d.value),1)]),_:1}),t(r,{label:"余额增减",required:"",prop:"action"},{default:l(()=>[t(C,{modelValue:n(o).action,"onUpdate:modelValue":a[0]||(a[0]=p=>n(o).action=p)},{default:l(()=>[t(_,{value:1},{default:l(()=>[...a[2]||(a[2]=[u("增加余额",-1)])]),_:1}),t(_,{value:2},{default:l(()=>[...a[3]||(a[3]=[u("扣减余额",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),t(r,{label:"调整余额",prop:"num"},{default:l(()=>[t(b,{"model-value":n(o).num,placeholder:"请输入调整的金额",type:"number",onInput:g},null,8,["model-value"])]),_:1}),t(r,{label:"调整后余额"},{default:l(()=>[u(" ¥ "+w(n(c)),1)]),_:1}),t(r,{label:"备注",prop:"remark"},{default:l(()=>[t(b,{modelValue:n(o).remark,"onUpdate:modelValue":a[1]||(a[1]=p=>n(o).remark=p),type:"textarea",rows:4},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])])]),_:1},512)}}});export{H as _};
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{_ as o}from"./add-nav.vue_vue_type_script_setup_true_lang-DOm0U6LY.js";import"./.pnpm-BSw4l71J.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./add-nav.vue_vue_type_script_setup_true_lang-CWR6_Pss.js";import"./.pnpm-BSw4l71J.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -1 +1 @@
import{o as E,q as p,r as C,s as l,v as a,u as c,bQ as B,C as N,D as d,O as $,b7 as z,b6 as D,I,K as A,L,p as R}from"./.pnpm-BSw4l71J.js";import{_ as q}from"./index-CDzrObNE.js";import{_ as F}from"./picker-BbcA6kuc.js";import{_ as K}from"./picker-BAOfQDdS.js";import{c as O,i as r}from"./index-C7M1esnu.js";const P={class:"bg-fill-light flex items-center w-full p-4 mb-4"},Q={class:"upload-btn w-[60px] h-[60px]"},S={class:"ml-3 flex-1"},T={class:"flex items-center"},j={class:"flex items-center mt-[18px]"},G={class:"flex-1 flex items-center"},H={class:"drag-move cursor-move ml-auto"},Z=E({__name:"add-nav",props:{modelValue:{type:Array,default:()=>[]},max:{type:Number,default:100},min:{type:Number,default:1}},emits:["update:modelValue"],setup(_,{emit:f}){const t=_,V=f,m=R({get(){return t.modelValue},set(s){V("update:modelValue",s)}}),x=()=>{var s;((s=t.modelValue)==null?void 0:s.length)<t.max?m.value.push({image:"",name:"导航名称",link:{},is_show:"1"}):r.msgError(`最多添加${t.max}`)},v=s=>{var e;if(((e=t.modelValue)==null?void 0:e.length)<=t.min)return r.msgError(`最少保留${t.min}`);m.value.splice(s,1)};return(s,e)=>{const u=O,g=K,b=z,h=F,k=I,w=D,y=q,U=A;return p(),C("div",null,[l("div",null,[a(c(B),{class:"draggable",modelValue:c(m),"onUpdate:modelValue":e[0]||(e[0]=o=>N(m)?m.value=o:null),animation:"300",handle:".drag-move","item-key":"index"},{item:d(({element:o,index:i})=>[(p(),$(y,{class:"w-[467px]",key:i,onClose:n=>v(i)},{default:d(()=>[l("div",P,[a(g,{modelValue:o.image,"onUpdate:modelValue":n=>o.image=n,"upload-class":"bg-body",size:"60px","exclude-domain":""},{upload:d(()=>[l("div",Q,[a(u,{name:"el-icon-Plus",size:20})])]),_:1},8,["modelValue","onUpdate:modelValue"]),l("div",S,[l("div",T,[e[1]||(e[1]=l("span",{class:"text-tx-regular flex-none mr-3"},"名称",-1)),a(b,{modelValue:o.name,"onUpdate:modelValue":n=>o.name=n,placeholder:"请输入名称"},null,8,["modelValue","onUpdate:modelValue"])]),l("div",j,[e[2]||(e[2]=l("span",{class:"text-tx-regular flex-none mr-3"},"链接",-1)),a(h,{modelValue:o.link,"onUpdate:modelValue":n=>o.link=n},null,8,["modelValue","onUpdate:modelValue"])]),a(w,{label:"是否显示",class:"mt-[18px]"},{default:d(()=>[l("div",G,[a(k,{modelValue:o.is_show,"onUpdate:modelValue":n=>o.is_show=n,"active-value":"1","inactive-value":"0"},null,8,["modelValue","onUpdate:modelValue"]),l("div",H,[a(u,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])])]),_:2},1032,["onClose"]))]),_:1},8,["modelValue"])]),l("div",null,[a(U,{type:"primary",onClick:x},{default:d(()=>[...e[3]||(e[3]=[L("添加",-1)])]),_:1})])])}}});export{Z as _};
import{o as E,q as p,r as C,s as l,v as a,u as c,bQ as B,C as N,D as d,O as $,b7 as z,b6 as D,I,K as A,L,p as R}from"./.pnpm-BSw4l71J.js";import{_ as q}from"./index-AZbkqFiT.js";import{_ as F}from"./picker-DlgaaioO.js";import{_ as K}from"./picker-AxnNHltM.js";import{c as O,i as r}from"./index-C5omO-nb.js";const P={class:"bg-fill-light flex items-center w-full p-4 mb-4"},Q={class:"upload-btn w-[60px] h-[60px]"},S={class:"ml-3 flex-1"},T={class:"flex items-center"},j={class:"flex items-center mt-[18px]"},G={class:"flex-1 flex items-center"},H={class:"drag-move cursor-move ml-auto"},Z=E({__name:"add-nav",props:{modelValue:{type:Array,default:()=>[]},max:{type:Number,default:100},min:{type:Number,default:1}},emits:["update:modelValue"],setup(_,{emit:f}){const t=_,V=f,m=R({get(){return t.modelValue},set(s){V("update:modelValue",s)}}),x=()=>{var s;((s=t.modelValue)==null?void 0:s.length)<t.max?m.value.push({image:"",name:"导航名称",link:{},is_show:"1"}):r.msgError(`最多添加${t.max}`)},v=s=>{var e;if(((e=t.modelValue)==null?void 0:e.length)<=t.min)return r.msgError(`最少保留${t.min}`);m.value.splice(s,1)};return(s,e)=>{const u=O,g=K,b=z,h=F,k=I,w=D,y=q,U=A;return p(),C("div",null,[l("div",null,[a(c(B),{class:"draggable",modelValue:c(m),"onUpdate:modelValue":e[0]||(e[0]=o=>N(m)?m.value=o:null),animation:"300",handle:".drag-move","item-key":"index"},{item:d(({element:o,index:i})=>[(p(),$(y,{class:"w-[467px]",key:i,onClose:n=>v(i)},{default:d(()=>[l("div",P,[a(g,{modelValue:o.image,"onUpdate:modelValue":n=>o.image=n,"upload-class":"bg-body",size:"60px","exclude-domain":""},{upload:d(()=>[l("div",Q,[a(u,{name:"el-icon-Plus",size:20})])]),_:1},8,["modelValue","onUpdate:modelValue"]),l("div",S,[l("div",T,[e[1]||(e[1]=l("span",{class:"text-tx-regular flex-none mr-3"},"名称",-1)),a(b,{modelValue:o.name,"onUpdate:modelValue":n=>o.name=n,placeholder:"请输入名称"},null,8,["modelValue","onUpdate:modelValue"])]),l("div",j,[e[2]||(e[2]=l("span",{class:"text-tx-regular flex-none mr-3"},"链接",-1)),a(h,{modelValue:o.link,"onUpdate:modelValue":n=>o.link=n},null,8,["modelValue","onUpdate:modelValue"])]),a(w,{label:"是否显示",class:"mt-[18px]"},{default:d(()=>[l("div",G,[a(k,{modelValue:o.is_show,"onUpdate:modelValue":n=>o.is_show=n,"active-value":"1","inactive-value":"0"},null,8,["modelValue","onUpdate:modelValue"]),l("div",H,[a(u,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])])]),_:2},1032,["onClose"]))]),_:1},8,["modelValue"])]),l("div",null,[a(U,{type:"primary",onClick:x},{default:d(()=>[...e[3]||(e[3]=[L("添加",-1)])]),_:1})])])}}});export{Z as _};
@@ -1 +1 @@
import{r as n}from"./index-C7M1esnu.js";function e(t){return n.get({url:"/auth.admin/lists",params:t},{ignoreCancelToken:!0})}function i(t){return n.post({url:"/auth.admin/add",params:t})}function r(t){return n.post({url:"/auth.admin/edit",params:t})}function u(t){return n.post({url:"/auth.admin/delete",params:t})}function d(t){return n.get({url:"/auth.admin/detail",params:t})}export{e as a,r as b,u as c,i as d,d as e};
import{r as n}from"./index-C5omO-nb.js";function e(t){return n.get({url:"/auth.admin/lists",params:t},{ignoreCancelToken:!0})}function i(t){return n.post({url:"/auth.admin/add",params:t})}function r(t){return n.post({url:"/auth.admin/edit",params:t})}function u(t){return n.post({url:"/auth.admin/delete",params:t})}function d(t){return n.get({url:"/auth.admin/detail",params:t})}export{e as a,r as b,u as c,i as d,d as e};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{r as e}from"./index-C7M1esnu.js";function a(t){return e.get({url:"/article.articleCate/lists",params:t})}function l(t){return e.get({url:"/article.articleCate/all",params:t})}function i(t){return e.post({url:"/article.articleCate/add",params:t})}function c(t){return e.post({url:"/article.articleCate/edit",params:t})}function u(t){return e.post({url:"/article.articleCate/delete",params:t})}function n(t){return e.get({url:"/article.articleCate/detail",params:t})}function s(t){return e.post({url:"/article.articleCate/updateStatus",params:t})}function o(t){return e.get({url:"/article.article/lists",params:t})}function d(t){return e.post({url:"/article.article/add",params:t})}function f(t){return e.post({url:"/article.article/edit",params:t})}function C(t){return e.post({url:"/article.article/delete",params:t})}function p(t){return e.get({url:"/article.article/detail",params:t})}function g(t){return e.post({url:"/article.article/updateStatus",params:t})}export{a,u as b,s as c,c as d,i as e,n as f,p as g,f as h,d as i,l as j,o as k,g as l,C as m};
import{r as e}from"./index-C5omO-nb.js";function a(t){return e.get({url:"/article.articleCate/lists",params:t})}function l(t){return e.get({url:"/article.articleCate/all",params:t})}function i(t){return e.post({url:"/article.articleCate/add",params:t})}function c(t){return e.post({url:"/article.articleCate/edit",params:t})}function u(t){return e.post({url:"/article.articleCate/delete",params:t})}function n(t){return e.get({url:"/article.articleCate/detail",params:t})}function s(t){return e.post({url:"/article.articleCate/updateStatus",params:t})}function o(t){return e.get({url:"/article.article/lists",params:t})}function d(t){return e.post({url:"/article.article/add",params:t})}function f(t){return e.post({url:"/article.article/edit",params:t})}function C(t){return e.post({url:"/article.article/delete",params:t})}function p(t){return e.get({url:"/article.article/detail",params:t})}function g(t){return e.post({url:"/article.article/updateStatus",params:t})}export{a,u as b,s as c,c as d,i as e,n as f,p as g,f as h,d as i,l as j,o as k,g as l,C as m};
@@ -1 +1 @@
import{r as e}from"./index-C7M1esnu.js";function r(s){return e.get({url:"/asset.AssetUser/lists",params:s})}function u(s){return e.post({url:"/asset.AssetUser/add",params:s})}function a(s){return e.post({url:"/asset.AssetUser/edit",params:s})}function i(s){return e.post({url:"/asset.AssetUser/delete",params:s})}function o(s){return e.get({url:"/asset.AssetResource/lists",params:s})}function n(s){return e.post({url:"/asset.AssetResource/add",params:s})}function A(s){return e.post({url:"/asset.AssetResource/edit",params:s})}function c(s){return e.post({url:"/asset.AssetResource/delete",params:s})}export{o as a,c as b,A as c,n as d,r as e,a as f,i as g,u as h};
import{r as e}from"./index-C5omO-nb.js";function r(s){return e.get({url:"/asset.AssetUser/lists",params:s})}function u(s){return e.post({url:"/asset.AssetUser/add",params:s})}function a(s){return e.post({url:"/asset.AssetUser/edit",params:s})}function i(s){return e.post({url:"/asset.AssetUser/delete",params:s})}function o(s){return e.get({url:"/asset.AssetResource/lists",params:s})}function n(s){return e.post({url:"/asset.AssetResource/add",params:s})}function A(s){return e.post({url:"/asset.AssetResource/edit",params:s})}function c(s){return e.post({url:"/asset.AssetResource/delete",params:s})}export{o as a,c as b,A as c,n as d,r as e,a as f,i as g,u as h};
@@ -0,0 +1 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-7JKPE08H.js";import"./.pnpm-BSw4l71J.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-RGK71_GJ.js";import"./.pnpm-BSw4l71J.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -1 +1 @@
import{o as b,q as c,r as V,s as v,v as t,D as a,Y as x,X as E,u as l,b9 as g,F as k,p as w}from"./.pnpm-BSw4l71J.js";import{_ as p}from"./menu-set.vue_vue_type_script_setup_true_lang-B3X7Pnx0.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";const $=b({__name:"attr",props:{modelValue:{type:Object,default:()=>({nav:[],menu:{}})}},emits:["update:modelValue"],setup(s,{emit:u}){const d=s,i=u,o=w({get(){return d.modelValue},set(n){i("update:modelValue",n)}});return(n,e)=>{const r=E,f=x,_=g;return c(),V(k,null,[e[2]||(e[2]=v("div",{class:"title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2"}," pc导航设置 ",-1)),t(_,{class:"mt-4","label-width":"70px"},{default:a(()=>[t(f,{"model-value":"nav"},{default:a(()=>[t(r,{label:"主导航设置",name:"nav"},{default:a(()=>[t(p,{modelValue:l(o).nav,"onUpdate:modelValue":e[0]||(e[0]=m=>l(o).nav=m)},null,8,["modelValue"])]),_:1}),t(r,{label:"菜单设置",name:"menu"},{default:a(()=>[t(p,{modelValue:l(o).menu,"onUpdate:modelValue":e[1]||(e[1]=m=>l(o).menu=m)},null,8,["modelValue"])]),_:1})]),_:1})]),_:1})],64)}}});export{$ as default};
import{o as b,q as c,r as V,s as v,v as t,D as a,Y as x,X as E,u as l,b9 as g,F as k,p as w}from"./.pnpm-BSw4l71J.js";import{_ as p}from"./menu-set.vue_vue_type_script_setup_true_lang-D9u_vSDT.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";const $=b({__name:"attr",props:{modelValue:{type:Object,default:()=>({nav:[],menu:{}})}},emits:["update:modelValue"],setup(s,{emit:u}){const d=s,i=u,o=w({get(){return d.modelValue},set(n){i("update:modelValue",n)}});return(n,e)=>{const r=E,f=x,_=g;return c(),V(k,null,[e[2]||(e[2]=v("div",{class:"title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2"}," pc导航设置 ",-1)),t(_,{class:"mt-4","label-width":"70px"},{default:a(()=>[t(f,{"model-value":"nav"},{default:a(()=>[t(r,{label:"主导航设置",name:"nav"},{default:a(()=>[t(p,{modelValue:l(o).nav,"onUpdate:modelValue":e[0]||(e[0]=m=>l(o).nav=m)},null,8,["modelValue"])]),_:1}),t(r,{label:"菜单设置",name:"menu"},{default:a(()=>[t(p,{modelValue:l(o).menu,"onUpdate:modelValue":e[1]||(e[1]=m=>l(o).menu=m)},null,8,["modelValue"])]),_:1})]),_:1})]),_:1})],64)}}});export{$ as default};
@@ -0,0 +1 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-CSUpGKjS.js";import"./.pnpm-BSw4l71J.js";import"./add-nav.vue_vue_type_script_setup_true_lang-DOm0U6LY.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-Ckj9-7HG.js";import"./.pnpm-BSw4l71J.js";import"./picker-AxnNHltM.js";import"./index-DWDHA_E8.js";import"./index-C5omO-nb.js";import"./index-e9VVOPkd.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./index-AZbkqFiT.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./usePaging-BeGcb2kN.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-B3OEm7iz.js";import"./.pnpm-BSw4l71J.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-CdxoGwgl.js";import"./.pnpm-BSw4l71J.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-Cg44HydJ.js";import"./.pnpm-BSw4l71J.js";import"./add-nav.vue_vue_type_script_setup_true_lang-CWR6_Pss.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-Bp-mV46k.js";import"./.pnpm-BSw4l71J.js";import"./picker-BAOfQDdS.js";import"./index-BI2XUhVK.js";import"./index-C7M1esnu.js";import"./index-Ho7YRaEv.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./index-CDzrObNE.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./usePaging-BeGcb2kN.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-CZGVQFl4.js";import"./.pnpm-BSw4l71J.js";import"./add-nav.vue_vue_type_script_setup_true_lang-DOm0U6LY.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-DusxDWh7.js";import"./.pnpm-BSw4l71J.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-ByQVNxh0.js";import"./.pnpm-BSw4l71J.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-CEM-Ivpx.js";import"./.pnpm-BSw4l71J.js";import"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";import"./picker-AxnNHltM.js";import"./index-DWDHA_E8.js";import"./index-C5omO-nb.js";import"./index-e9VVOPkd.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./index-AZbkqFiT.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./usePaging-BeGcb2kN.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-CPzfaJDP.js";import"./.pnpm-BSw4l71J.js";import"./add-nav.vue_vue_type_script_setup_true_lang-CWR6_Pss.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./attr.vue_vue_type_script_setup_true_lang-u4o_m7kt.js";import"./.pnpm-BSw4l71J.js";import"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";import"./picker-BAOfQDdS.js";import"./index-BI2XUhVK.js";import"./index-C7M1esnu.js";import"./index-Ho7YRaEv.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./index-CDzrObNE.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./usePaging-BeGcb2kN.js";export{o as default};
@@ -0,0 +1 @@
import{_ as o}from"./attr-setting.vue_vue_type_script_setup_true_lang-lNK-LpEi.js";import"./.pnpm-BSw4l71J.js";import"./index-DFxUT3gf.js";import"./attr-BWNjEjhA.js";import"./index-AZbkqFiT.js";import"./index-C5omO-nb.js";import"./picker-DlgaaioO.js";import"./index-DWDHA_E8.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-C_6xWlmg.js";import"./usePaging-BeGcb2kN.js";import"./picker-AxnNHltM.js";import"./index-e9VVOPkd.js";import"./index-B_vRidw5.js";import"./file--nSnYOw1.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./content.vue_vue_type_script_setup_true_lang-_vS3MJo9.js";import"./decoration-img-DqLRWfe3.js";import"./attr.vue_vue_type_script_setup_true_lang-Ckj9-7HG.js";import"./content-DsoM-KpA.js";import"./attr.vue_vue_type_script_setup_true_lang-ByQVNxh0.js";import"./content.vue_vue_type_script_setup_true_lang-klSVHmu-.js";import"./attr.vue_vue_type_script_setup_true_lang-CSUpGKjS.js";import"./add-nav.vue_vue_type_script_setup_true_lang-DOm0U6LY.js";import"./content-gaKNTinM.js";import"./attr.vue_vue_type_script_setup_true_lang-CZGVQFl4.js";import"./content.vue_vue_type_script_setup_true_lang-CSAGFZwX.js";import"./attr.vue_vue_type_script_setup_true_lang-BX4gdclK.js";import"./content-BWJ76O3h.js";import"./decoration-C5OnlSsy.js";import"./attr.vue_vue_type_script_setup_true_lang-CEM-Ivpx.js";import"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";import"./content-CQXvIovx.js";import"./content.vue_vue_type_script_setup_true_lang-DD8pLpjQ.js";import"./attr.vue_vue_type_script_setup_true_lang-CdNAifO0.js";import"./content-54B3MRFo.js";import"./attr.vue_vue_type_script_setup_true_lang-7JKPE08H.js";import"./content.vue_vue_type_script_setup_true_lang-BGSCElBg.js";import"./attr.vue_vue_type_script_setup_true_lang-Cxhd7Qdg.js";import"./content-BbXNcVm3.js";export{o as default};
@@ -1 +0,0 @@
import{_ as o}from"./attr-setting.vue_vue_type_script_setup_true_lang-BV5Kr0G_.js";import"./.pnpm-BSw4l71J.js";import"./index-CK4OHxfK.js";import"./attr-rlv01xAn.js";import"./index-CDzrObNE.js";import"./index-C7M1esnu.js";import"./picker-BbcA6kuc.js";import"./index-BI2XUhVK.js";import"./index.vue_vue_type_script_setup_true_lang-BOJpu9c9.js";import"./article-Cu5yzLRi.js";import"./usePaging-BeGcb2kN.js";import"./picker-BAOfQDdS.js";import"./index-Ho7YRaEv.js";import"./index-BLSe8mr1.js";import"./file-BSLutQ6Q.js";import"./index.vue_vue_type_script_setup_true_lang-D3ZtXZaD.js";import"./content.vue_vue_type_script_setup_true_lang-uKSsEXIj.js";import"./decoration-img-jpQ962yE.js";import"./attr.vue_vue_type_script_setup_true_lang-Bp-mV46k.js";import"./content-CPAKwgni.js";import"./attr.vue_vue_type_script_setup_true_lang-RGK71_GJ.js";import"./content.vue_vue_type_script_setup_true_lang-DVY7e7kv.js";import"./attr.vue_vue_type_script_setup_true_lang-CPzfaJDP.js";import"./add-nav.vue_vue_type_script_setup_true_lang-CWR6_Pss.js";import"./content-D1UpIOe_.js";import"./attr.vue_vue_type_script_setup_true_lang-Cg44HydJ.js";import"./content.vue_vue_type_script_setup_true_lang-CkqaiKg0.js";import"./attr.vue_vue_type_script_setup_true_lang-BX4gdclK.js";import"./content-CFWygn2e.js";import"./decoration-WmZwV1l6.js";import"./attr.vue_vue_type_script_setup_true_lang-u4o_m7kt.js";import"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";import"./content-B7cQuIwl.js";import"./content.vue_vue_type_script_setup_true_lang-BWyBDECY.js";import"./attr.vue_vue_type_script_setup_true_lang-CdNAifO0.js";import"./content-C87VwXzh.js";import"./attr.vue_vue_type_script_setup_true_lang-CdxoGwgl.js";import"./content.vue_vue_type_script_setup_true_lang-foMFOzn5.js";import"./attr.vue_vue_type_script_setup_true_lang-Cxhd7Qdg.js";import"./content-CqjH9rbN.js";export{o as default};
@@ -1 +1 @@
import{o as g,q as a,r as b,v as c,bg as y,D as r,s as x,T as _,a2 as h,O as i,a3 as w,a4 as v,u as C}from"./.pnpm-BSw4l71J.js";import{e as k}from"./index-CK4OHxfK.js";const B={class:"pages-setting"},D={class:"title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2 text-xl font-medium"},O=g({__name:"attr-setting",props:{widget:{type:Object,default:()=>({})},type:{type:String,default:"mobile"}},emits:["update:content"],setup(e,{emit:d}){const m=d,p=n=>{m("update:content",n)};return(n,E)=>{const f=y,u=h;return a(),b("div",B,[c(f,{shadow:"never",class:"!border-none flex"},{default:r(()=>{var t;return[x("div",D,_((t=e.widget)==null?void 0:t.title),1)]}),_:1}),c(u,{class:"w-full",style:{height:"calc(100% - 60px)"}},{default:r(()=>{var t,s,o,l;return[(a(),i(w,null,[(a(),i(v((s=C(k)[(t=e.widget)==null?void 0:t.name])==null?void 0:s.attr),{content:(o=e.widget)==null?void 0:o.content,styles:(l=e.widget)==null?void 0:l.styles,type:e.type,"onUpdate:content":p},null,40,["content","styles","type"]))],1024))]}),_:1})])}}});export{O as _};
import{o as g,q as a,r as b,v as c,bg as y,D as r,s as x,T as _,a2 as h,O as i,a3 as w,a4 as v,u as C}from"./.pnpm-BSw4l71J.js";import{e as k}from"./index-DFxUT3gf.js";const B={class:"pages-setting"},D={class:"title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2 text-xl font-medium"},O=g({__name:"attr-setting",props:{widget:{type:Object,default:()=>({})},type:{type:String,default:"mobile"}},emits:["update:content"],setup(e,{emit:d}){const m=d,p=n=>{m("update:content",n)};return(n,E)=>{const f=y,u=h;return a(),b("div",B,[c(f,{shadow:"never",class:"!border-none flex"},{default:r(()=>{var t;return[x("div",D,_((t=e.widget)==null?void 0:t.title),1)]}),_:1}),c(u,{class:"w-full",style:{height:"calc(100% - 60px)"}},{default:r(()=>{var t,s,o,l;return[(a(),i(w,null,[(a(),i(v((s=C(k)[(t=e.widget)==null?void 0:t.name])==null?void 0:s.attr),{content:(o=e.widget)==null?void 0:o.content,styles:(l=e.widget)==null?void 0:l.styles,type:e.type,"onUpdate:content":p},null,40,["content","styles","type"]))],1024))]}),_:1})])}}});export{O as _};
@@ -1 +1 @@
import{o as I,q as i,r as g,v as t,D as l,bg as O,s,u,bQ as j,O as F,b6 as q,b7 as z,I as A,K,L,P,b9 as Q,p as R,cm as b}from"./.pnpm-BSw4l71J.js";import{_ as S}from"./index-CDzrObNE.js";import{c as T,i as v}from"./index-C7M1esnu.js";import{_ as G}from"./picker-BbcA6kuc.js";import{_ as H}from"./picker-BAOfQDdS.js";const J={class:"bg-fill-light flex items-center w-full p-4 mt-4"},M={class:"ml-3 flex-1"},W={class:"flex-1 flex items-center"},X={class:"drag-move cursor-move ml-auto"},Y={key:0,class:"mt-4"},p=5,le=I({__name:"attr",props:{content:{type:Object,default:()=>({})},styles:{type:Object,default:()=>({})}},emits:["update:content"],setup(r,{emit:h}){const m=h,c=r,f=R({get:()=>c.content,set:a=>{m("update:content",a)}}),k=()=>{var a;if(((a=c.content.data)==null?void 0:a.length)<p){const e=b(c.content);e.data.push({is_show:"1",image:"",name:"",link:{}}),m("update:content",e)}else v.msgError(`最多添加${p}张图片`)},w=a=>{var d;if(((d=c.content.data)==null?void 0:d.length)<=1)return v.msgError("最少保留一张图片");const e=b(c.content);e.data.splice(a,1),m("update:content",e)};return(a,e)=>{const d=H,y=z,_=q,E=G,U=A,C=T,B=S,D=K,N=O,$=Q;return i(),g("div",null,[t($,{"label-width":"70px"},{default:l(()=>[t(N,{shadow:"never",class:"!border-none flex mt-2"},{default:l(()=>{var x;return[e[2]||(e[2]=s("div",{class:"flex items-end mb-4"},[s("div",{class:"text-base text-[#101010] font-medium"},"菜单"),s("div",{class:"text-xs text-tx-secondary ml-2"}," 最多添加5张,建议图片尺寸:750px*200px ")],-1)),t(u(j),{class:"draggable",modelValue:u(f).data,"onUpdate:modelValue":e[0]||(e[0]=o=>u(f).data=o),animation:"300",handle:".drag-move","item-key":"index"},{item:l(({element:o,index:V})=>[(i(),F(B,{key:V,onClose:n=>w(V),class:"w-[467px]"},{default:l(()=>[s("div",J,[t(d,{modelValue:o.image,"onUpdate:modelValue":n=>o.image=n,"upload-class":"bg-body","exclude-domain":""},null,8,["modelValue","onUpdate:modelValue"]),s("div",M,[t(_,{label:"图片名称"},{default:l(()=>[t(y,{modelValue:o.name,"onUpdate:modelValue":n=>o.name=n,placeholder:"请输入名称"},null,8,["modelValue","onUpdate:modelValue"])]),_:2},1024),t(_,{class:"mt-[18px]",label:"图片链接"},{default:l(()=>[t(E,{modelValue:o.link,"onUpdate:modelValue":n=>o.link=n},null,8,["modelValue","onUpdate:modelValue"])]),_:2},1024),t(_,{label:"是否显示",class:"mt-[18px]"},{default:l(()=>[s("div",W,[t(U,{modelValue:o.is_show,"onUpdate:modelValue":n=>o.is_show=n,"active-value":"1","inactive-value":"0"},null,8,["modelValue","onUpdate:modelValue"]),s("div",X,[t(C,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])])]),_:2},1032,["onClose"]))]),_:1},8,["modelValue"]),((x=r.content.data)==null?void 0:x.length)<p?(i(),g("div",Y,[t(D,{class:"w-full",type:"primary",onClick:k},{default:l(()=>[...e[1]||(e[1]=[L("添加图片",-1)])]),_:1})])):P("",!0)]}),_:1})]),_:1})])}}});export{le as _};
import{o as I,q as i,r as g,v as t,D as l,bg as O,s,u,bQ as j,O as F,b6 as q,b7 as z,I as A,K,L,P,b9 as Q,p as R,cm as b}from"./.pnpm-BSw4l71J.js";import{_ as S}from"./index-AZbkqFiT.js";import{c as T,i as v}from"./index-C5omO-nb.js";import{_ as G}from"./picker-DlgaaioO.js";import{_ as H}from"./picker-AxnNHltM.js";const J={class:"bg-fill-light flex items-center w-full p-4 mt-4"},M={class:"ml-3 flex-1"},W={class:"flex-1 flex items-center"},X={class:"drag-move cursor-move ml-auto"},Y={key:0,class:"mt-4"},p=5,le=I({__name:"attr",props:{content:{type:Object,default:()=>({})},styles:{type:Object,default:()=>({})}},emits:["update:content"],setup(r,{emit:h}){const m=h,c=r,f=R({get:()=>c.content,set:a=>{m("update:content",a)}}),k=()=>{var a;if(((a=c.content.data)==null?void 0:a.length)<p){const e=b(c.content);e.data.push({is_show:"1",image:"",name:"",link:{}}),m("update:content",e)}else v.msgError(`最多添加${p}张图片`)},w=a=>{var d;if(((d=c.content.data)==null?void 0:d.length)<=1)return v.msgError("最少保留一张图片");const e=b(c.content);e.data.splice(a,1),m("update:content",e)};return(a,e)=>{const d=H,y=z,_=q,E=G,U=A,C=T,B=S,D=K,N=O,$=Q;return i(),g("div",null,[t($,{"label-width":"70px"},{default:l(()=>[t(N,{shadow:"never",class:"!border-none flex mt-2"},{default:l(()=>{var x;return[e[2]||(e[2]=s("div",{class:"flex items-end mb-4"},[s("div",{class:"text-base text-[#101010] font-medium"},"菜单"),s("div",{class:"text-xs text-tx-secondary ml-2"}," 最多添加5张,建议图片尺寸:750px*200px ")],-1)),t(u(j),{class:"draggable",modelValue:u(f).data,"onUpdate:modelValue":e[0]||(e[0]=o=>u(f).data=o),animation:"300",handle:".drag-move","item-key":"index"},{item:l(({element:o,index:V})=>[(i(),F(B,{key:V,onClose:n=>w(V),class:"w-[467px]"},{default:l(()=>[s("div",J,[t(d,{modelValue:o.image,"onUpdate:modelValue":n=>o.image=n,"upload-class":"bg-body","exclude-domain":""},null,8,["modelValue","onUpdate:modelValue"]),s("div",M,[t(_,{label:"图片名称"},{default:l(()=>[t(y,{modelValue:o.name,"onUpdate:modelValue":n=>o.name=n,placeholder:"请输入名称"},null,8,["modelValue","onUpdate:modelValue"])]),_:2},1024),t(_,{class:"mt-[18px]",label:"图片链接"},{default:l(()=>[t(E,{modelValue:o.link,"onUpdate:modelValue":n=>o.link=n},null,8,["modelValue","onUpdate:modelValue"])]),_:2},1024),t(_,{label:"是否显示",class:"mt-[18px]"},{default:l(()=>[s("div",W,[t(U,{modelValue:o.is_show,"onUpdate:modelValue":n=>o.is_show=n,"active-value":"1","inactive-value":"0"},null,8,["modelValue","onUpdate:modelValue"]),s("div",X,[t(C,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])])]),_:2},1032,["onClose"]))]),_:1},8,["modelValue"]),((x=r.content.data)==null?void 0:x.length)<p?(i(),g("div",Y,[t(D,{class:"w-full",type:"primary",onClick:k},{default:l(()=>[...e[1]||(e[1]=[L("添加图片",-1)])]),_:1})])):P("",!0)]}),_:1})]),_:1})])}}});export{le as _};
@@ -1 +1 @@
import{o as j,q as d,r as b,v as o,D as s,bg as F,s as n,u as p,bQ as S,O as _,b6 as q,P as r,b7 as z,I as A,K,L,b9 as P,p as Q,cm as v}from"./.pnpm-BSw4l71J.js";import{_ as R}from"./index-CDzrObNE.js";import{c as T,i as k}from"./index-C7M1esnu.js";import{_ as G}from"./picker-BbcA6kuc.js";import{_ as H}from"./picker-BAOfQDdS.js";const J={class:"flex-1"},M={class:"bg-fill-light w-full p-4 mt-4"},W={class:"flex-1"},X={class:"flex-1 flex items-center"},Y={class:"drag-move cursor-move ml-auto"},Z={key:0,class:"mt-4"},f=5,ae=j({__name:"attr",props:{content:{type:Object,default:()=>({})},styles:{type:Object,default:()=>({})},type:{type:String,default:"mobile"}},emits:["update:content"],setup(m,{emit:y}){const u=y,c=m,g=Q({get:()=>c.content,set:a=>{u("update:content",a)}}),w=()=>{var a;if(((a=c.content.data)==null?void 0:a.length)<f){const e=v(c.content);e.data.push({is_show:"1",image:"",name:"",link:{}}),u("update:content",e)}else k.msgError(`最多添加${f}张图片`)},E=a=>{var i;if(((i=c.content.data)==null?void 0:i.length)<=1)return k.msgError("最少保留一张图片");const e=v(c.content);e.data.splice(a,1),u("update:content",e)};return(a,e)=>{const i=H,U=G,C=z,h=q,B=A,D=T,N=R,$=K,I=F,O=P;return d(),b("div",null,[o(O,{"label-width":"70px"},{default:s(()=>[o(I,{shadow:"never",class:"!border-none flex mt-2"},{default:s(()=>{var x;return[e[2]||(e[2]=n("div",{class:"flex items-end"},[n("div",{class:"text-base text-[#101010] font-medium"},"图片设置"),n("div",{class:"text-xs text-tx-secondary ml-2"}," 最多添加5张,建议图片尺寸:750px*200px ")],-1)),n("div",J,[o(p(S),{class:"draggable",modelValue:p(g).data,"onUpdate:modelValue":e[0]||(e[0]=t=>p(g).data=t),animation:"300",handle:".drag-move"},{item:s(({element:t,index:V})=>[(d(),_(N,{key:V,onClose:l=>E(V),class:"w-full"},{default:s(()=>[n("div",M,[o(i,{width:"396px",height:"196px",modelValue:t.image,"onUpdate:modelValue":l=>t.image=l,"upload-class":"bg-body","exclude-domain":""},null,8,["modelValue","onUpdate:modelValue"]),n("div",W,[o(h,{class:"mt-[18px]",label:"图片链接"},{default:s(()=>[m.type=="mobile"?(d(),_(U,{key:0,modelValue:t.link,"onUpdate:modelValue":l=>t.link=l},null,8,["modelValue","onUpdate:modelValue"])):r("",!0),m.type=="pc"?(d(),_(C,{key:1,placeholder:"请输入链接",modelValue:t.link.path,"onUpdate:modelValue":l=>t.link.path=l},null,8,["modelValue","onUpdate:modelValue"])):r("",!0)]),_:2},1024),o(h,{label:"是否显示",class:"mt-[18px] !mb-0"},{default:s(()=>[n("div",X,[o(B,{modelValue:t.is_show,"onUpdate:modelValue":l=>t.is_show=l,"active-value":"1","inactive-value":"0"},null,8,["modelValue","onUpdate:modelValue"]),n("div",Y,[o(D,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])])]),_:2},1032,["onClose"]))]),_:1},8,["modelValue"])]),((x=m.content.data)==null?void 0:x.length)<f?(d(),b("div",Z,[o($,{class:"w-full",type:"primary",onClick:w},{default:s(()=>[...e[1]||(e[1]=[L("添加图片",-1)])]),_:1})])):r("",!0)]}),_:1})]),_:1})])}}});export{ae as _};
import{o as j,q as d,r as b,v as o,D as s,bg as F,s as n,u as p,bQ as S,O as _,b6 as q,P as r,b7 as z,I as A,K,L,b9 as P,p as Q,cm as v}from"./.pnpm-BSw4l71J.js";import{_ as R}from"./index-AZbkqFiT.js";import{c as T,i as k}from"./index-C5omO-nb.js";import{_ as G}from"./picker-DlgaaioO.js";import{_ as H}from"./picker-AxnNHltM.js";const J={class:"flex-1"},M={class:"bg-fill-light w-full p-4 mt-4"},W={class:"flex-1"},X={class:"flex-1 flex items-center"},Y={class:"drag-move cursor-move ml-auto"},Z={key:0,class:"mt-4"},f=5,ae=j({__name:"attr",props:{content:{type:Object,default:()=>({})},styles:{type:Object,default:()=>({})},type:{type:String,default:"mobile"}},emits:["update:content"],setup(m,{emit:y}){const u=y,c=m,g=Q({get:()=>c.content,set:a=>{u("update:content",a)}}),w=()=>{var a;if(((a=c.content.data)==null?void 0:a.length)<f){const e=v(c.content);e.data.push({is_show:"1",image:"",name:"",link:{}}),u("update:content",e)}else k.msgError(`最多添加${f}张图片`)},E=a=>{var i;if(((i=c.content.data)==null?void 0:i.length)<=1)return k.msgError("最少保留一张图片");const e=v(c.content);e.data.splice(a,1),u("update:content",e)};return(a,e)=>{const i=H,U=G,C=z,h=q,B=A,D=T,N=R,$=K,I=F,O=P;return d(),b("div",null,[o(O,{"label-width":"70px"},{default:s(()=>[o(I,{shadow:"never",class:"!border-none flex mt-2"},{default:s(()=>{var x;return[e[2]||(e[2]=n("div",{class:"flex items-end"},[n("div",{class:"text-base text-[#101010] font-medium"},"图片设置"),n("div",{class:"text-xs text-tx-secondary ml-2"}," 最多添加5张,建议图片尺寸:750px*200px ")],-1)),n("div",J,[o(p(S),{class:"draggable",modelValue:p(g).data,"onUpdate:modelValue":e[0]||(e[0]=t=>p(g).data=t),animation:"300",handle:".drag-move"},{item:s(({element:t,index:V})=>[(d(),_(N,{key:V,onClose:l=>E(V),class:"w-full"},{default:s(()=>[n("div",M,[o(i,{width:"396px",height:"196px",modelValue:t.image,"onUpdate:modelValue":l=>t.image=l,"upload-class":"bg-body","exclude-domain":""},null,8,["modelValue","onUpdate:modelValue"]),n("div",W,[o(h,{class:"mt-[18px]",label:"图片链接"},{default:s(()=>[m.type=="mobile"?(d(),_(U,{key:0,modelValue:t.link,"onUpdate:modelValue":l=>t.link=l},null,8,["modelValue","onUpdate:modelValue"])):r("",!0),m.type=="pc"?(d(),_(C,{key:1,placeholder:"请输入链接",modelValue:t.link.path,"onUpdate:modelValue":l=>t.link.path=l},null,8,["modelValue","onUpdate:modelValue"])):r("",!0)]),_:2},1024),o(h,{label:"是否显示",class:"mt-[18px] !mb-0"},{default:s(()=>[n("div",X,[o(B,{modelValue:t.is_show,"onUpdate:modelValue":l=>t.is_show=l,"active-value":"1","inactive-value":"0"},null,8,["modelValue","onUpdate:modelValue"]),n("div",Y,[o(D,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])])]),_:2},1032,["onClose"]))]),_:1},8,["modelValue"])]),((x=m.content.data)==null?void 0:x.length)<f?(d(),b("div",Z,[o($,{class:"w-full",type:"primary",onClick:w},{default:s(()=>[...e[1]||(e[1]=[L("添加图片",-1)])]),_:1})])):r("",!0)]}),_:1})]),_:1})])}}});export{ae as _};
@@ -1 +1 @@
import{o as E,q as s,O as r,D as t,v as l,bg as F,b6 as C,bc as N,u as a,bf as z,L as p,b7 as B,P as i,s as b,b9 as O,p as j}from"./.pnpm-BSw4l71J.js";import{_ as D}from"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";import{_ as I}from"./picker-BAOfQDdS.js";const G=E({__name:"attr",props:{content:{type:Object,default:()=>({})},styles:{type:Object,default:()=>({})}},emits:["update:content"],setup(d,{emit:g}){const y=g,x=d,o=j({get:()=>x.content,set:f=>{y("update:content",f)}});return(f,e)=>{const m=z,_=N,u=C,v=B,V=I,k=D,U=F,w=O;return s(),r(w,{ref:"form","label-width":"80px",size:"large"},{default:t(()=>[l(U,{shadow:"never",class:"!border-none flex mt-2"},{default:t(()=>[l(u,{label:"页面标题"},{default:t(()=>[l(_,{modelValue:a(o).title_type,"onUpdate:modelValue":e[0]||(e[0]=n=>a(o).title_type=n)},{default:t(()=>[l(m,{value:"1"},{default:t(()=>[...e[7]||(e[7]=[p("文字",-1)])]),_:1}),l(m,{value:"2"},{default:t(()=>[...e[8]||(e[8]=[p("图片",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),d.content.title_type==1?(s(),r(u,{key:0},{default:t(()=>[l(v,{modelValue:a(o).title,"onUpdate:modelValue":e[1]||(e[1]=n=>a(o).title=n),maxlength:"8","show-word-limit":"",class:"w-[300px]",placeholder:"请输入页面标题"},null,8,["modelValue"])]),_:1})):i("",!0),d.content.title_type==2?(s(),r(u,{key:1},{default:t(()=>[l(V,{modelValue:a(o).title_img,"onUpdate:modelValue":e[2]||(e[2]=n=>a(o).title_img=n),limit:1,size:"100px"},null,8,["modelValue"]),e[9]||(e[9]=b("div",{class:"form-tips"},"建议图片尺寸:300px*40px",-1))]),_:1})):i("",!0),d.content.title_type==1?(s(),r(u,{key:2,label:"文字颜色"},{default:t(()=>[l(_,{modelValue:a(o).text_color,"onUpdate:modelValue":e[3]||(e[3]=n=>a(o).text_color=n)},{default:t(()=>[l(m,{value:"1"},{default:t(()=>[...e[10]||(e[10]=[p("白色",-1)])]),_:1}),l(m,{value:"2"},{default:t(()=>[...e[11]||(e[11]=[p("黑色",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1})):i("",!0),l(u,{label:"页面背景"},{default:t(()=>[l(_,{modelValue:a(o).bg_type,"onUpdate:modelValue":e[4]||(e[4]=n=>a(o).bg_type=n)},{default:t(()=>[l(m,{value:"1"},{default:t(()=>[...e[12]||(e[12]=[p("背景颜色",-1)])]),_:1}),l(m,{value:"2"},{default:t(()=>[...e[13]||(e[13]=[p("背景图片",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),d.content.bg_type==1?(s(),r(u,{key:3},{default:t(()=>[l(k,{modelValue:a(o).bg_color,"onUpdate:modelValue":e[5]||(e[5]=n=>a(o).bg_color=n),"reset-color":"#F5F5F5"},null,8,["modelValue"])]),_:1})):i("",!0),d.content.bg_type==2?(s(),r(u,{key:4},{default:t(()=>[l(V,{modelValue:a(o).bg_image,"onUpdate:modelValue":e[6]||(e[6]=n=>a(o).bg_image=n),limit:1,size:"100px"},null,8,["modelValue"]),e[14]||(e[14]=b("div",{class:"form-tips"},"建议图片尺寸:750px*高度不限",-1))]),_:1})):i("",!0)]),_:1})]),_:1},512)}}});export{G as _};
import{o as E,q as s,O as r,D as t,v as l,bg as F,b6 as C,bc as N,u as a,bf as z,L as p,b7 as B,P as i,s as b,b9 as O,p as j}from"./.pnpm-BSw4l71J.js";import{_ as D}from"./index.vue_vue_type_script_setup_true_lang-D6YizxbY.js";import{_ as I}from"./picker-AxnNHltM.js";const G=E({__name:"attr",props:{content:{type:Object,default:()=>({})},styles:{type:Object,default:()=>({})}},emits:["update:content"],setup(d,{emit:g}){const y=g,x=d,o=j({get:()=>x.content,set:f=>{y("update:content",f)}});return(f,e)=>{const m=z,_=N,u=C,v=B,V=I,k=D,U=F,w=O;return s(),r(w,{ref:"form","label-width":"80px",size:"large"},{default:t(()=>[l(U,{shadow:"never",class:"!border-none flex mt-2"},{default:t(()=>[l(u,{label:"页面标题"},{default:t(()=>[l(_,{modelValue:a(o).title_type,"onUpdate:modelValue":e[0]||(e[0]=n=>a(o).title_type=n)},{default:t(()=>[l(m,{value:"1"},{default:t(()=>[...e[7]||(e[7]=[p("文字",-1)])]),_:1}),l(m,{value:"2"},{default:t(()=>[...e[8]||(e[8]=[p("图片",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),d.content.title_type==1?(s(),r(u,{key:0},{default:t(()=>[l(v,{modelValue:a(o).title,"onUpdate:modelValue":e[1]||(e[1]=n=>a(o).title=n),maxlength:"8","show-word-limit":"",class:"w-[300px]",placeholder:"请输入页面标题"},null,8,["modelValue"])]),_:1})):i("",!0),d.content.title_type==2?(s(),r(u,{key:1},{default:t(()=>[l(V,{modelValue:a(o).title_img,"onUpdate:modelValue":e[2]||(e[2]=n=>a(o).title_img=n),limit:1,size:"100px"},null,8,["modelValue"]),e[9]||(e[9]=b("div",{class:"form-tips"},"建议图片尺寸:300px*40px",-1))]),_:1})):i("",!0),d.content.title_type==1?(s(),r(u,{key:2,label:"文字颜色"},{default:t(()=>[l(_,{modelValue:a(o).text_color,"onUpdate:modelValue":e[3]||(e[3]=n=>a(o).text_color=n)},{default:t(()=>[l(m,{value:"1"},{default:t(()=>[...e[10]||(e[10]=[p("白色",-1)])]),_:1}),l(m,{value:"2"},{default:t(()=>[...e[11]||(e[11]=[p("黑色",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1})):i("",!0),l(u,{label:"页面背景"},{default:t(()=>[l(_,{modelValue:a(o).bg_type,"onUpdate:modelValue":e[4]||(e[4]=n=>a(o).bg_type=n)},{default:t(()=>[l(m,{value:"1"},{default:t(()=>[...e[12]||(e[12]=[p("背景颜色",-1)])]),_:1}),l(m,{value:"2"},{default:t(()=>[...e[13]||(e[13]=[p("背景图片",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),d.content.bg_type==1?(s(),r(u,{key:3},{default:t(()=>[l(k,{modelValue:a(o).bg_color,"onUpdate:modelValue":e[5]||(e[5]=n=>a(o).bg_color=n),"reset-color":"#F5F5F5"},null,8,["modelValue"])]),_:1})):i("",!0),d.content.bg_type==2?(s(),r(u,{key:4},{default:t(()=>[l(V,{modelValue:a(o).bg_image,"onUpdate:modelValue":e[6]||(e[6]=n=>a(o).bg_image=n),limit:1,size:"100px"},null,8,["modelValue"]),e[14]||(e[14]=b("div",{class:"form-tips"},"建议图片尺寸:750px*高度不限",-1))]),_:1})):i("",!0)]),_:1})]),_:1},512)}}});export{G as _};

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