Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e76f16193b |
+1
-284
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 将标准双 el-card 列表页迁移为 admin-page 架构组件
|
||||
* 用法: node scripts/migrate-admin-pages.mjs [--dry-run] [glob...]
|
||||
*/
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const viewsRoot = path.join(__dirname, '../src/views')
|
||||
|
||||
const dryRun = process.argv.includes('--dry-run')
|
||||
const extraPaths = process.argv.slice(2).filter((a) => !a.startsWith('--'))
|
||||
|
||||
const defaultTargets = [
|
||||
'article/lists/index.vue',
|
||||
'article/column/index.vue',
|
||||
'permission/role/index.vue',
|
||||
'permission/menu/index.vue',
|
||||
'dev_tools/code/index.vue',
|
||||
'setting/dict/type/index.vue',
|
||||
'setting/dict/data/index.vue',
|
||||
'message/notice/index.vue',
|
||||
'message/short_letter/index.vue',
|
||||
'fans/index.vue',
|
||||
'order/index.vue',
|
||||
'asset/user/index.vue',
|
||||
'asset/resource/index.vue',
|
||||
'finance/balance_details.vue',
|
||||
'finance/recharge_record.vue',
|
||||
'finance/refund_record.vue'
|
||||
]
|
||||
|
||||
function migrate(content) {
|
||||
if (content.includes('admin-page-filter-panel')) return null
|
||||
if (!content.includes('el-card class="!border-none"')) return null
|
||||
if (!content.includes('<el-table')) return null
|
||||
|
||||
let out = content
|
||||
|
||||
// 根容器 class 清理
|
||||
out = out.replace(
|
||||
/<div class="[^"]*">\s*\n\s*<el-card class="!border-none" shadow="never">/,
|
||||
'<div>\n <admin-page-filter-panel>'
|
||||
)
|
||||
if (!out.includes('admin-page-filter-panel')) {
|
||||
out = out.replace(
|
||||
/<el-card class="!border-none" shadow="never">/,
|
||||
'<admin-page-filter-panel>'
|
||||
)
|
||||
}
|
||||
|
||||
// 第一个 filter card 结束
|
||||
out = out.replace(
|
||||
/<\/el-form>\s*\n\s*<\/el-card>/,
|
||||
'</el-form>\n </admin-page-filter-panel>'
|
||||
)
|
||||
|
||||
// 第二个 data card 开始 - 提取 v-loading
|
||||
const loadingMatch = out.match(
|
||||
/<el-card([^>]*?)class="[^"]*mt-4[^"]*"[^>]*shadow="never"[^>]*>/
|
||||
)
|
||||
const altLoadingMatch = out.match(
|
||||
/<el-card v-loading="([^"]+)"([^>]*)class="[^"]*mt-4[^"]*"([^>]*)shadow="never"([^>]*)>/
|
||||
)
|
||||
|
||||
if (altLoadingMatch) {
|
||||
out = out.replace(
|
||||
altLoadingMatch[0],
|
||||
`<admin-page-data-panel v-loading="${altLoadingMatch[1]}">`
|
||||
)
|
||||
} else if (loadingMatch) {
|
||||
out = out.replace(loadingMatch[0], '<admin-page-data-panel>')
|
||||
} else {
|
||||
out = out.replace(
|
||||
/<el-card class="!border-none mt-4" shadow="never">/,
|
||||
'<admin-page-data-panel>'
|
||||
)
|
||||
out = out.replace(
|
||||
/<el-card v-loading="([^"]+)" class="mt-4 !border-none" shadow="never">/,
|
||||
'<admin-page-data-panel v-loading="$1">'
|
||||
)
|
||||
out = out.replace(
|
||||
/<el-card v-loading="([^"]+)" class="!border-none mt-4" shadow="never">/,
|
||||
'<admin-page-data-panel v-loading="$1">'
|
||||
)
|
||||
}
|
||||
|
||||
// toolbar: 紧跟 data panel 后的首个 div 包裹按钮
|
||||
out = out.replace(
|
||||
/(<admin-page-data-panel[^>]*>\s*)<div>\s*\n(\s*<el-button[\s\S]*?<\/div>\s*\n)/,
|
||||
'$1<template #toolbar>\n$2</template>\n'
|
||||
)
|
||||
out = out.replace(
|
||||
/(<admin-page-data-panel[^>]*>\s*)<div>\s*\n(\s*<router-link[\s\S]*?<\/div>\s*\n)/,
|
||||
'$1<template #toolbar>\n$2</template>\n'
|
||||
)
|
||||
|
||||
// 移除 table 前的 mt-4 class
|
||||
out = out.replace(/<el-table class="mt-4"/g, '<el-table')
|
||||
out = out.replace(/<el-table\s+class="mt-4"\s+/g, '<el-table ')
|
||||
|
||||
// pagination footer
|
||||
out = out.replace(
|
||||
/<div class="flex(?: mt-4)? justify-end(?: mt-4)?">\s*\n\s*<pagination([^/]*)\/>\s*\n\s*<\/div>\s*\n\s*<\/el-card>/,
|
||||
'<template #footer>\n <pagination$1/>\n </template>\n </admin-page-data-panel>'
|
||||
)
|
||||
out = out.replace(
|
||||
/<div class="flex mt-4 justify-end">\s*\n\s*<pagination([^/]*)\/>\s*\n\s*<\/div>\s*\n\s*<\/el-card>/,
|
||||
'<template #footer>\n <pagination$1/>\n </template>\n </admin-page-data-panel>'
|
||||
)
|
||||
|
||||
// 无 pagination 的 data card 闭合
|
||||
if (out.includes('<admin-page-data-panel') && out.includes('</el-card>')) {
|
||||
out = out.replace(/<\/el-table>\s*\n\s*<\/el-card>/, '</el-table>\n </admin-page-data-panel>')
|
||||
}
|
||||
|
||||
// table 上的 v-loading 移到 panel(若 panel 还没有)
|
||||
out = out.replace(
|
||||
/<admin-page-data-panel>\s*\n(\s*)<el-table([^>]*?)v-loading="([^"]+)"([^>]*)>/,
|
||||
'<admin-page-data-panel v-loading="$3">\n$1<el-table$2$4>'
|
||||
)
|
||||
out = out.replace(/<el-table([^>]*?)v-loading="([^"]+)"([^>]*)>/, '<el-table$1$3>')
|
||||
|
||||
if (out === content || !out.includes('admin-page-data-panel')) return null
|
||||
return out
|
||||
}
|
||||
|
||||
const targets = extraPaths.length
|
||||
? extraPaths.map((p) => path.resolve(p))
|
||||
: defaultTargets.map((p) => path.join(viewsRoot, p))
|
||||
|
||||
let migrated = 0
|
||||
let skipped = 0
|
||||
|
||||
for (const file of targets) {
|
||||
if (!fs.existsSync(file)) {
|
||||
console.warn('skip (missing):', path.relative(viewsRoot, file))
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
const original = fs.readFileSync(file, 'utf8')
|
||||
const result = migrate(original)
|
||||
if (!result) {
|
||||
console.log('skip (no match):', path.relative(viewsRoot, file))
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
if (dryRun) {
|
||||
console.log('would migrate:', path.relative(viewsRoot, file))
|
||||
} else {
|
||||
fs.writeFileSync(file, result, 'utf8')
|
||||
console.log('migrated:', path.relative(viewsRoot, file))
|
||||
}
|
||||
migrated++
|
||||
}
|
||||
|
||||
console.log(`\nDone. migrated=${migrated} skipped=${skipped}${dryRun ? ' (dry-run)' : ''}`)
|
||||
@@ -61,14 +61,6 @@ export function tcmDiagnosisDetail(params: any) {
|
||||
return request.get({ url: '/tcm.diagnosis/detail', params })
|
||||
}
|
||||
|
||||
/** 设置复诊接诊率统计起始偏移(统计诊次=实单序号+偏移;1=二诊起,2=三诊起) */
|
||||
export function tcmDiagnosisSetRevisitSlotStartOffset(params: {
|
||||
id: number
|
||||
revisit_slot_start_offset: number
|
||||
}) {
|
||||
return request.post({ url: '/tcm.diagnosis/setRevisitSlotStartOffset', params })
|
||||
}
|
||||
|
||||
/** 诊单挂号 / 取消挂号 操作日志 */
|
||||
export function tcmDiagnosisGuahaoLogList(params: { id: number }) {
|
||||
return request.get({ url: '/tcm.diagnosis/guahaoLogList', params })
|
||||
@@ -433,17 +425,6 @@ export function prescriptionOrderPatchPrescriptionPatient(params: {
|
||||
return request.post({ url: '/tcm.prescriptionOrder/patchPrescriptionPatient', params })
|
||||
}
|
||||
|
||||
export function prescriptionOrderPatchPrescriptionUsage(params: {
|
||||
id: number
|
||||
times_per_day: number
|
||||
usage_days: number
|
||||
medication_days: number
|
||||
aux_times_per_day?: number
|
||||
aux_usage_days?: number
|
||||
}) {
|
||||
return request.post({ url: '/tcm.prescriptionOrder/patchPrescriptionUsage', params })
|
||||
}
|
||||
|
||||
export function prescriptionOrderAuditPrescription(params: {
|
||||
id: number
|
||||
action: 'approve' | 'reject'
|
||||
@@ -542,18 +523,6 @@ export function prescriptionOrderLogs(params: { id: number }) {
|
||||
return request.get({ url: '/tcm.prescriptionOrder/logs', params })
|
||||
}
|
||||
|
||||
/** 手工新增操作日志(可选调整处方/支付单审核状态) */
|
||||
export function prescriptionOrderAddLog(params: {
|
||||
id: number
|
||||
summary: string
|
||||
prescription_audit_status?: number | ''
|
||||
payment_slip_audit_status?: number | ''
|
||||
prescription_audit_remark?: string
|
||||
payment_slip_audit_remark?: string
|
||||
}) {
|
||||
return request.post({ url: '/tcm.prescriptionOrder/addLog', params })
|
||||
}
|
||||
|
||||
/** 修改订单金额 */
|
||||
export function prescriptionOrderUpdateAmount(params: { id: number; amount: number }) {
|
||||
return request.post({ url: '/tcm.prescriptionOrder/updateAmount', params })
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<section class="admin-data-panel" v-loading="loading">
|
||||
<div v-if="$slots.toolbar" class="admin-data-panel__toolbar">
|
||||
<slot name="toolbar" />
|
||||
</div>
|
||||
<div class="admin-data-panel__body">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="$slots.footer" class="admin-data-panel__footer">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
loading?: boolean
|
||||
}>(),
|
||||
{
|
||||
loading: false
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
|
||||
<section class="admin-filter-panel" :class="{ 'is-collapsed': collapsed }">
|
||||
|
||||
<div v-if="collapsible" class="admin-filter-panel__head">
|
||||
|
||||
<span class="admin-filter-panel__label">{{ title }}</span>
|
||||
|
||||
<el-button class="admin-filter-panel__toggle" link type="primary" @click="collapsed = !collapsed">
|
||||
|
||||
{{ collapsed ? '展开筛选' : '收起筛选' }}
|
||||
|
||||
</el-button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="admin-filter-panel__body">
|
||||
|
||||
<slot />
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
withDefaults(
|
||||
|
||||
defineProps<{
|
||||
|
||||
/** 是否显示收起/展开 */
|
||||
|
||||
collapsible?: boolean
|
||||
|
||||
/** 筛选区标题 */
|
||||
|
||||
title?: string
|
||||
|
||||
}>(),
|
||||
|
||||
{
|
||||
|
||||
collapsible: true,
|
||||
|
||||
title: '筛选条件'
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
const collapsed = ref(false)
|
||||
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<section class="admin-form-panel">
|
||||
<header v-if="title || $slots.header" class="admin-form-panel__header">
|
||||
<slot name="header">
|
||||
<h2 v-if="title" class="admin-form-panel__title">{{ title }}</h2>
|
||||
</slot>
|
||||
</header>
|
||||
<div class="admin-form-panel__body">
|
||||
<slot />
|
||||
</div>
|
||||
<footer v-if="$slots.footer" class="admin-form-panel__footer">
|
||||
<slot name="footer" />
|
||||
</footer>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="admin-page-actions">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div class="admin-stat-grid" :class="[`admin-stat-grid--cols-${columns}`]">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
columns?: 2 | 3 | 4 | 5
|
||||
}>(),
|
||||
{
|
||||
columns: 4
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<article class="admin-stat-item" :class="[`admin-stat-item--${tone}`]">
|
||||
<div class="admin-stat-item__label">{{ label }}</div>
|
||||
<div class="admin-stat-item__value" :class="{ 'is-money': money }">
|
||||
<slot>{{ value }}</slot>
|
||||
</div>
|
||||
<p v-if="hint" class="admin-stat-item__hint">{{ hint }}</p>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
label: string
|
||||
value?: string | number
|
||||
hint?: string
|
||||
money?: boolean
|
||||
tone?: 'default' | 'primary' | 'success' | 'warning'
|
||||
}>(),
|
||||
{
|
||||
value: '',
|
||||
hint: '',
|
||||
money: false,
|
||||
tone: 'default'
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -17,14 +17,18 @@ defineProps({
|
||||
|
||||
<style scoped lang="scss">
|
||||
.footer-btns {
|
||||
height: 60px;
|
||||
height: 64px;
|
||||
|
||||
&__content {
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
height: 64px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
@apply flex justify-center items-center shadow bg-body;
|
||||
padding: 0 20px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.04);
|
||||
@apply flex justify-center items-center gap-3 bg-body;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
|
||||
<div class="page-stage admin-page" :class="{ 'page-stage--flat': hideChrome }">
|
||||
|
||||
<header v-if="showHeader" class="page-stage__hero">
|
||||
|
||||
<div class="page-stage__hero-glow" aria-hidden="true"></div>
|
||||
|
||||
<div class="page-stage__hero-inner">
|
||||
|
||||
<div class="page-stage__intro">
|
||||
|
||||
<h1 class="page-stage__title">{{ pageTitle }}</h1>
|
||||
|
||||
<p v-if="pageDesc" class="page-stage__desc">{{ pageDesc }}</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.actions" class="page-stage__actions">
|
||||
|
||||
<slot name="actions" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<div class="page-stage__body">
|
||||
|
||||
<slot />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { PageEnum } from '@/enums/pageEnum'
|
||||
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
|
||||
const hideChrome = computed(() => {
|
||||
|
||||
if (route.meta?.hidePageHeader) return true
|
||||
|
||||
if (route.path === PageEnum.INDEX || route.path === `${PageEnum.INDEX}/`) return true
|
||||
|
||||
const title = (route.meta?.title as string) || ''
|
||||
|
||||
return title.includes('工作台')
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
|
||||
const matched = route.matched.filter((item) => item.meta?.title)
|
||||
|
||||
const last = matched[matched.length - 1]
|
||||
|
||||
return (last?.meta?.title as string) || ''
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
const pageDesc = computed(() => (route.meta?.pageDesc as string) || '')
|
||||
|
||||
|
||||
|
||||
const showHeader = computed(() => !hideChrome.value && Boolean(pageTitle.value))
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.page-stage {
|
||||
|
||||
min-height: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage__hero {
|
||||
|
||||
position: relative;
|
||||
|
||||
margin-bottom: 20px;
|
||||
|
||||
padding: 22px 24px;
|
||||
|
||||
border-radius: var(--admin-radius-xl);
|
||||
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
background: var(--admin-surface-glass);
|
||||
|
||||
backdrop-filter: blur(16px) saturate(140%);
|
||||
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage__hero-glow {
|
||||
|
||||
position: absolute;
|
||||
|
||||
inset: 0;
|
||||
|
||||
background:
|
||||
|
||||
linear-gradient(120deg, rgba(6, 182, 212, 0.12) 0%, transparent 42%),
|
||||
|
||||
linear-gradient(300deg, rgba(16, 185, 129, 0.1) 0%, transparent 38%);
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage__hero-inner {
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-wrap: wrap;
|
||||
|
||||
align-items: flex-start;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
gap: 12px 20px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage__title {
|
||||
|
||||
margin: 0;
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
font-weight: 800;
|
||||
|
||||
line-height: 1.2;
|
||||
|
||||
letter-spacing: -0.03em;
|
||||
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
background: var(--admin-brand-gradient);
|
||||
|
||||
background-clip: text;
|
||||
|
||||
-webkit-background-clip: text;
|
||||
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage__desc {
|
||||
|
||||
margin: 8px 0 0;
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
line-height: 1.5;
|
||||
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
max-width: 62ch;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage__actions {
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-wrap: wrap;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 8px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage__body {
|
||||
|
||||
min-width: 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.page-stage--flat .page-stage__hero {
|
||||
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
+20
-20
@@ -1,28 +1,28 @@
|
||||
const defaultSetting = {
|
||||
showCrumb: true, // 是否显示面包屑
|
||||
showLogo: false, // 是否显示logo
|
||||
isUniqueOpened: true, //只展开一个一级菜单
|
||||
sideWidth: 183, //侧边栏宽度
|
||||
sideTheme: 'dark', //侧边栏主题
|
||||
sideDarkColor: '#1d2124', //侧边栏深色主题颜色
|
||||
openMultipleTabs: true, // 是否开启多标签tab栏
|
||||
theme: '#4A5DFF', //主题色
|
||||
successTheme: '#67c23a', //成功主题色
|
||||
warningTheme: '#e6a23c', //警告主题色
|
||||
dangerTheme: '#f56c6c', //危险主题色
|
||||
errorTheme: '#f56c6c', //错误主题色
|
||||
infoTheme: '#909399' //信息主题色
|
||||
showCrumb: false,
|
||||
showLogo: true,
|
||||
isUniqueOpened: true,
|
||||
sideWidth: 248,
|
||||
sideTheme: 'dark',
|
||||
sideDarkColor: '#060d18',
|
||||
openMultipleTabs: true,
|
||||
theme: '#06b6d4',
|
||||
successTheme: '#10b981',
|
||||
warningTheme: '#f59e0b',
|
||||
dangerTheme: '#ef4444',
|
||||
errorTheme: '#ef4444',
|
||||
infoTheme: '#6366f1'
|
||||
}
|
||||
|
||||
/** 本地 setting 缓存结构版本。提升后仅对低于该版本的老缓存执行 SETTING_SCHEMA_MIGRATIONS */
|
||||
export const SETTING_SCHEMA_VERSION = 1
|
||||
export const SETTING_SCHEMA_VERSION = 6
|
||||
|
||||
/**
|
||||
* 按版本写入 defaultSetting 中的键(老用户 localStorage 会长期盖住 config 默认值)。
|
||||
* 以后若要再推一批新默认值:把 SETTING_SCHEMA_VERSION +1,并为本版本追加一条迁移键列表。
|
||||
*/
|
||||
export const SETTING_SCHEMA_MIGRATIONS: Record<number, (keyof typeof defaultSetting)[]> = {
|
||||
1: ['sideTheme', 'sideDarkColor']
|
||||
1: ['sideTheme', 'sideDarkColor'],
|
||||
2: ['theme', 'successTheme', 'warningTheme', 'dangerTheme', 'errorTheme', 'infoTheme', 'sideDarkColor', 'sideWidth', 'showLogo'],
|
||||
3: ['theme', 'sideDarkColor', 'sideWidth', 'showCrumb', 'showLogo'],
|
||||
4: ['theme', 'successTheme', 'warningTheme', 'dangerTheme', 'errorTheme', 'infoTheme', 'sideDarkColor'],
|
||||
5: ['theme', 'successTheme', 'warningTheme', 'dangerTheme', 'errorTheme', 'infoTheme', 'sideDarkColor'],
|
||||
6: ['theme', 'successTheme', 'warningTheme', 'dangerTheme', 'errorTheme', 'infoTheme', 'sideDarkColor', 'sideWidth']
|
||||
}
|
||||
|
||||
export default defaultSetting
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb">
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<el-breadcrumb-item v-for="item in breadcrumbs" :key="item.path">
|
||||
{{ item.meta.title }}
|
||||
</el-breadcrumb-item>
|
||||
@@ -21,22 +21,24 @@ useWatchRoute((route) => {
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-breadcrumb {
|
||||
:deep(.el-breadcrumb__item) {
|
||||
.el-breadcrumb__inner {
|
||||
color: #303133;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-weight: 500;
|
||||
font-size: var(--el-font-size-small);
|
||||
}
|
||||
|
||||
|
||||
&:last-child .el-breadcrumb__inner {
|
||||
color: #303133;
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-breadcrumb__separator) {
|
||||
color: #606266;
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<header class="header">
|
||||
<div class="navbar">
|
||||
<div class="flex-1 flex">
|
||||
<div class="flex-1 flex items-center gap-1 min-w-0">
|
||||
<div class="navbar-item">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
@@ -17,11 +17,14 @@
|
||||
<refresh />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="flex items-center px-2" v-if="!isMobile && settingStore.showCrumb">
|
||||
<div
|
||||
class="hidden md:flex items-center min-w-0 px-2"
|
||||
v-if="settingStore.showCrumb && breadcrumbs.length"
|
||||
>
|
||||
<breadcrumb />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="navbar-item" v-if="!isMobile">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
@@ -36,12 +39,7 @@
|
||||
<user-drop-down />
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="主题设置"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip class="box-item" effect="dark" content="主题设置" placement="bottom">
|
||||
<setting />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@@ -52,8 +50,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { RouteLocationMatched } from 'vue-router'
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
|
||||
import { useWatchRoute } from '@/hooks/useWatchRoute'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import useSettingStore from '@/stores/modules/setting'
|
||||
|
||||
@@ -70,14 +70,20 @@ const isMobile = computed(() => appStore.isMobile)
|
||||
const isCollapsed = computed(() => appStore.isCollapsed)
|
||||
const settingStore = useSettingStore()
|
||||
const { isFullscreen } = useFullscreen()
|
||||
|
||||
const breadcrumbs = ref<RouteLocationMatched[]>([])
|
||||
useWatchRoute((route) => {
|
||||
breadcrumbs.value = route.matched.filter((item) => item.meta && item.meta.title)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.navbar {
|
||||
height: var(--navbar-height);
|
||||
@apply flex px-2 bg-body;
|
||||
@apply flex px-3 bg-body;
|
||||
|
||||
.navbar-item {
|
||||
@apply h-full flex justify-center items-center hover:bg-page;
|
||||
@apply h-full flex justify-center items-center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-tabs pl-4 flex bg-body">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="app-tabs flex bg-body">
|
||||
<div class="flex-1 min-w-0 pl-3">
|
||||
<el-tabs
|
||||
:model-value="currentTab"
|
||||
:closable="tabsLists.length > 1"
|
||||
@@ -13,7 +13,7 @@
|
||||
</el-tabs>
|
||||
</div>
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="flex items-center px-3">
|
||||
<span class="tabs-more-btn flex items-center px-3">
|
||||
<icon :size="16" name="el-icon-arrow-down" />
|
||||
</span>
|
||||
<template #dropdown>
|
||||
@@ -60,61 +60,84 @@ const handleCommand = (command: any) => {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.app-tabs {
|
||||
@apply border-t border-br;
|
||||
height: var(--tabs-height);
|
||||
border-top: 1px solid var(--admin-header-border);
|
||||
background: var(--admin-surface-glass);
|
||||
backdrop-filter: blur(12px);
|
||||
|
||||
.tabs-more-btn {
|
||||
height: var(--tabs-height);
|
||||
color: var(--el-text-color-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease, background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--admin-brand-primary-dark);
|
||||
background: var(--admin-brand-gradient-soft);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tabs) {
|
||||
height: 40px;
|
||||
height: var(--tabs-height);
|
||||
|
||||
.el-tabs {
|
||||
&__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__nav-next,
|
||||
&__nav-prev {
|
||||
@apply text-xl;
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
&__nav-wrap::after {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&__item {
|
||||
font-weight: normal;
|
||||
padding: 0 15px !important;
|
||||
font-weight: 600;
|
||||
font-size: var(--el-font-size-small);
|
||||
padding: 0 16px !important;
|
||||
height: calc(var(--tabs-height) - 8px);
|
||||
margin-top: 4px;
|
||||
border-radius: var(--admin-radius-md) var(--admin-radius-md) 0 0;
|
||||
box-sizing: border-box;
|
||||
color: var(--el-text-color-secondary);
|
||||
transition: color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--admin-brand-primary-dark);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-text-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--el-color-primary);
|
||||
margin-right: 6px;
|
||||
border-radius: 50%;
|
||||
vertical-align: 2px;
|
||||
}
|
||||
color: var(--admin-brand-primary-dark);
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-bottom-color: transparent;
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
top: 0;
|
||||
height: 2px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: var(--el-color-primary);
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.is-icon-close {
|
||||
color: var(--el-text-color-regular);
|
||||
color: var(--el-text-color-placeholder);
|
||||
vertical-align: -2px;
|
||||
border-radius: var(--admin-radius-sm);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-white);
|
||||
background-color: var(--el-color-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__active-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<el-dropdown class="px-2" @command="handleCommand">
|
||||
<div class="flex items-center">
|
||||
<el-avatar :size="34" :src="userInfo.avatar" />
|
||||
<div class="ml-3 mr-1">{{ userInfo.name }}</div>
|
||||
<icon name="el-icon-ArrowDown" />
|
||||
<el-dropdown class="user-dropdown px-1" @command="handleCommand">
|
||||
<div class="user-trigger flex items-center gap-2.5 px-2 py-1 rounded-md cursor-pointer">
|
||||
<el-avatar :size="32" :src="userInfo.avatar" />
|
||||
<span class="user-name max-w-[120px] truncate text-sm font-medium text-tx-primary">{{
|
||||
userInfo.name
|
||||
}}</span>
|
||||
<icon name="el-icon-ArrowDown" :size="14" />
|
||||
</div>
|
||||
|
||||
<template #dropdown>
|
||||
@@ -41,3 +43,13 @@ const handleCommand = async (command: string) => {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-trigger {
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,56 @@
|
||||
<template>
|
||||
<main class="main-wrap h-full bg-page">
|
||||
|
||||
<main class="main-wrap h-full">
|
||||
|
||||
<el-scrollbar>
|
||||
<div class="px-2 py-4">
|
||||
<router-view v-if="isRouteShow" v-slot="{ Component, route }">
|
||||
<keep-alive :include="includeList" :max="20">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
|
||||
<div class="main-stage">
|
||||
|
||||
<page-shell v-if="isRouteShow">
|
||||
|
||||
<router-view v-slot="{ Component, route }">
|
||||
|
||||
<keep-alive :include="includeList" :max="20">
|
||||
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
|
||||
</keep-alive>
|
||||
|
||||
</router-view>
|
||||
|
||||
</page-shell>
|
||||
|
||||
</div>
|
||||
|
||||
</el-scrollbar>
|
||||
|
||||
</main>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import PageShell from '@/components/page-shell/index.vue'
|
||||
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
|
||||
import useTabsStore from '@/stores/modules/multipleTabs'
|
||||
|
||||
import useSettingStore from '@/stores/modules/setting'
|
||||
|
||||
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const tabsStore = useTabsStore()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const isRouteShow = computed(() => appStore.isRouteShow)
|
||||
|
||||
const includeList = computed(() => (settingStore.openMultipleTabs ? tabsStore.getCacheTabList : []))
|
||||
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
||||
@@ -100,7 +100,7 @@ import theme_light from '@/assets/images/theme_white.png'
|
||||
import useSettingStore from '@/stores/modules/setting'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const predefineColors = ref(['#409EFF', '#28C76F', '#EA5455', '#FF9F43', '#01CFE8', '#4A5DFF'])
|
||||
const predefineColors = ref(['#06b6d4', '#10b981', '#0891b2', '#6366f1', '#f59e0b', '#ef4444', '#64748b'])
|
||||
const sideThemeList = [
|
||||
{
|
||||
type: 'dark',
|
||||
|
||||
@@ -68,32 +68,42 @@ const themeClass = computed(() => `theme-${props.theme}`)
|
||||
.el-menu {
|
||||
:deep(.el-menu-item) {
|
||||
&.is-active {
|
||||
@apply bg-primary border-primary;
|
||||
@apply bg-primary;
|
||||
box-shadow: inset 3px 0 0 var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-menu--collapse) {
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
@apply bg-primary #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.theme-light {
|
||||
:deep(.el-menu) {
|
||||
.el-menu-item {
|
||||
border-color: transparent;
|
||||
|
||||
&.is-active {
|
||||
@apply bg-primary-light-9 border-r-2 border-primary;
|
||||
@apply bg-primary-light-9;
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
box-shadow: inset 3px 0 0 var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
|
||||
&:not(.el-menu--collapse) {
|
||||
width: var(--aside-width);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<template>
|
||||
<div class="side" :style="sideStyle">
|
||||
<div v-if="showBrandStrip" class="sidebar-brand">
|
||||
<div class="sidebar-brand__mark">ZY</div>
|
||||
<overflow-tooltip
|
||||
class="sidebar-brand__name"
|
||||
:content="config.web_name"
|
||||
:teleported="true"
|
||||
placement="bottom"
|
||||
overflo-type="unset"
|
||||
/>
|
||||
</div>
|
||||
<side-logo v-if="settingStore.showLogo" :show-title="!isCollapsed" :theme="sideTheme" />
|
||||
<side-menu
|
||||
:routes="routes"
|
||||
@@ -25,17 +35,19 @@ const appStore = useAppStore()
|
||||
const isCollapsed = computed(() => {
|
||||
if (appStore.isMobile) {
|
||||
return false
|
||||
} else {
|
||||
return appStore.isCollapsed
|
||||
}
|
||||
return appStore.isCollapsed
|
||||
})
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const sideTheme = computed(() => settingStore.sideTheme)
|
||||
const userStore = useUserStore()
|
||||
const config = computed(() => appStore.config)
|
||||
|
||||
const routes = computed(() => userStore.routes)
|
||||
|
||||
const showBrandStrip = computed(() => !settingStore.showLogo && !isCollapsed.value)
|
||||
|
||||
const sideStyle = computed(() => {
|
||||
return sideTheme.value == 'dark'
|
||||
? {
|
||||
@@ -43,6 +55,7 @@ const sideStyle = computed(() => {
|
||||
}
|
||||
: ''
|
||||
})
|
||||
|
||||
const menuProp = computed(() => {
|
||||
return {
|
||||
backgroundColor: sideTheme.value == 'dark' ? settingStore.sideDarkColor : '',
|
||||
@@ -50,6 +63,7 @@ const menuProp = computed(() => {
|
||||
activeTextColor: sideTheme.value == 'dark' ? 'var(--el-color-white)' : ''
|
||||
}
|
||||
})
|
||||
|
||||
const handleSelect = () => {
|
||||
if (appStore.isMobile) {
|
||||
appStore.toggleCollapsed(true)
|
||||
@@ -61,7 +75,8 @@ const handleSelect = () => {
|
||||
.side {
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
@apply border-r border-br-light h-full flex flex-col;
|
||||
background-color: var(--side-dark-color, var(--el-bg-color));
|
||||
@apply h-full flex flex-col;
|
||||
border-right: 1px solid var(--admin-sidebar-border);
|
||||
background-color: var(--side-dark-color, var(--sidebar-dark-bg));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* Admin 页面架构 v2 - 大改版面板系统
|
||||
*/
|
||||
|
||||
.admin-page .page-stage__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* ---------- FilterPanel ---------- */
|
||||
.admin-filter-panel {
|
||||
position: relative;
|
||||
border-radius: var(--admin-radius-xl);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
background: var(--admin-surface-glass);
|
||||
backdrop-filter: blur(12px);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
height: 3px;
|
||||
background: var(--admin-brand-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
.admin-filter-panel__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px 22px 0;
|
||||
}
|
||||
|
||||
.admin-filter-panel__label {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.admin-filter-panel__toggle {
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.admin-filter-panel__body {
|
||||
padding: 14px 22px 12px;
|
||||
}
|
||||
|
||||
.admin-filter-panel.is-collapsed .admin-filter-panel__body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admin-filter-panel .el-form--inline {
|
||||
margin-bottom: 0;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 12px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- DataPanel ---------- */
|
||||
.admin-data-panel {
|
||||
border-radius: var(--admin-radius-xl);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
background: var(--admin-surface-elevated);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-data-panel__toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 22px;
|
||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||
background: var(--admin-brand-gradient-soft);
|
||||
}
|
||||
|
||||
.admin-data-panel__body {
|
||||
padding: 0 22px 18px;
|
||||
}
|
||||
|
||||
.admin-data-panel__toolbar + .admin-data-panel__body {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.admin-data-panel__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 0 22px 18px;
|
||||
}
|
||||
|
||||
/* ---------- FormPanel ---------- */
|
||||
.admin-form-panel {
|
||||
border-radius: var(--admin-radius-xl);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
background: var(--admin-surface-elevated);
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-form-panel__header {
|
||||
padding: 16px 22px;
|
||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||
background: var(--admin-brand-gradient-soft);
|
||||
}
|
||||
|
||||
.admin-form-panel__title {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.admin-form-panel__body {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.admin-form-panel__footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 16px 22px 22px;
|
||||
border-top: 1px solid var(--el-border-color-extra-light);
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
/* ---------- StatGrid ---------- */
|
||||
.admin-stat-grid {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.admin-stat-grid--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.admin-stat-grid--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.admin-stat-grid--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.admin-stat-grid--cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||
|
||||
.admin-stat-item {
|
||||
position: relative;
|
||||
padding: 18px 20px;
|
||||
border-radius: var(--admin-radius-lg);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
background: var(--admin-surface-elevated);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 4px;
|
||||
background: var(--el-border-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
}
|
||||
|
||||
.admin-stat-item--primary {
|
||||
&::before { background: var(--admin-brand-gradient); }
|
||||
border-color: rgba(6, 182, 212, 0.2);
|
||||
background: linear-gradient(145deg, rgba(6, 182, 212, 0.08), rgba(16, 185, 129, 0.05));
|
||||
}
|
||||
|
||||
.admin-stat-item--success {
|
||||
&::before { background: #10b981; }
|
||||
border-color: rgba(16, 185, 129, 0.22);
|
||||
}
|
||||
|
||||
.admin-stat-item--warning {
|
||||
&::before { background: #f59e0b; }
|
||||
border-color: rgba(245, 158, 11, 0.22);
|
||||
}
|
||||
|
||||
.admin-stat-item__label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.admin-stat-item__value {
|
||||
margin-top: 10px;
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--el-text-color-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.admin-stat-item__value.is-money {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.admin-stat-item__hint {
|
||||
margin: 8px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
|
||||
/* ---------- Tables ---------- */
|
||||
.admin-data-panel .el-table,
|
||||
.admin-page .el-card .el-table {
|
||||
--el-table-border-color: transparent;
|
||||
border-radius: var(--admin-radius-md);
|
||||
|
||||
thead th.el-table__cell {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background: var(--table-header-bg-color) !important;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
font-weight: 700;
|
||||
font-size: var(--el-font-size-small);
|
||||
}
|
||||
|
||||
td.el-table__cell {
|
||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||
}
|
||||
|
||||
.el-table__row:hover > td.el-table__cell {
|
||||
background-color: rgba(6, 182, 212, 0.04) !important;
|
||||
}
|
||||
|
||||
.el-table__row.current-row > td.el-table__cell {
|
||||
background-color: rgba(6, 182, 212, 0.08) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Legacy cards ---------- */
|
||||
.admin-page .page-stage__body {
|
||||
> div > .el-card:first-child:has(.el-form),
|
||||
> .el-card:first-child:has(.el-form) {
|
||||
border-radius: var(--admin-radius-xl);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
}
|
||||
|
||||
> div > .el-card + .el-card,
|
||||
> .el-card + .el-card {
|
||||
border-radius: var(--admin-radius-xl);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.workbench-page {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.admin-stat-grid--cols-4,
|
||||
.admin-stat-grid--cols-5 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.admin-filter-panel__body,
|
||||
.admin-data-panel__body,
|
||||
.admin-data-panel__toolbar,
|
||||
.admin-data-panel__footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.admin-stat-grid--cols-2,
|
||||
.admin-stat-grid--cols-3,
|
||||
.admin-stat-grid--cols-4,
|
||||
.admin-stat-grid--cols-5 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.admin-stat-item:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.admin-stat-item {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/* Admin Shell v2 - 互联网医院工作台大改版 */
|
||||
|
||||
.layout-default {
|
||||
background: var(--sidebar-dark-bg);
|
||||
}
|
||||
|
||||
/* ---------- Sidebar ---------- */
|
||||
.app-aside .side {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: var(--sidebar-rail-width);
|
||||
background: var(--admin-brand-gradient);
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 0% 0%, var(--admin-brand-mesh-a), transparent 45%),
|
||||
radial-gradient(circle at 100% 100%, var(--admin-brand-mesh-b), transparent 40%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: calc(var(--navbar-height) + 4px);
|
||||
padding: 0 18px 0 20px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.sidebar-brand__mark {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.1em;
|
||||
color: #ffffff;
|
||||
background: var(--admin-brand-gradient);
|
||||
box-shadow: var(--admin-brand-glow);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-brand__name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
line-height: 1.25;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu.theme-dark .el-menu {
|
||||
--el-menu-bg-color: transparent;
|
||||
--el-menu-hover-bg-color: var(--sidebar-dark-hover);
|
||||
--el-menu-active-color: #ffffff;
|
||||
--el-menu-text-color: rgba(255, 255, 255, 0.62);
|
||||
padding: 12px 10px 16px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.menu.theme-dark .el-menu .el-menu-item,
|
||||
.menu.theme-dark .el-menu .el-sub-menu__title {
|
||||
margin: 3px 8px;
|
||||
border-radius: var(--admin-radius-md);
|
||||
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, transform 0.12s ease;
|
||||
}
|
||||
|
||||
.menu.theme-dark .el-menu .el-menu-item.is-active {
|
||||
background: var(--sidebar-dark-active) !important;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(6, 182, 212, 0.28),
|
||||
0 8px 24px rgba(6, 182, 212, 0.15);
|
||||
}
|
||||
|
||||
.menu.theme-dark .el-menu .el-menu-item:hover,
|
||||
.menu.theme-dark .el-menu .el-sub-menu__title:hover {
|
||||
background: var(--sidebar-dark-hover);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.menu.theme-light .el-menu .el-menu-item.is-active {
|
||||
font-weight: 600;
|
||||
color: var(--admin-brand-primary-dark);
|
||||
background: var(--admin-brand-gradient-soft);
|
||||
box-shadow: inset 3px 0 0 var(--admin-brand-primary);
|
||||
}
|
||||
|
||||
/* ---------- Header ---------- */
|
||||
.app-header {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
background: var(--admin-surface-glass);
|
||||
backdrop-filter: blur(20px) saturate(160%);
|
||||
border-bottom: 1px solid var(--admin-header-border);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: auto 0 0;
|
||||
height: 2px;
|
||||
background: var(--admin-brand-gradient);
|
||||
opacity: 0.75;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
:root.dark .app-header {
|
||||
background: rgba(15, 23, 42, 0.88);
|
||||
}
|
||||
|
||||
.navbar-tool {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--admin-radius-md);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--el-text-color-regular);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, color 0.2s ease, transform 0.12s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--admin-brand-gradient-soft);
|
||||
color: var(--admin-brand-primary-dark);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Main stage (去掉双层白盒) ---------- */
|
||||
.main-wrap {
|
||||
position: relative;
|
||||
background:
|
||||
radial-gradient(circle at 0% 0%, var(--admin-brand-mesh-a), transparent 34%),
|
||||
radial-gradient(circle at 100% 0%, var(--admin-brand-mesh-c), transparent 30%),
|
||||
radial-gradient(circle at 50% 100%, var(--admin-brand-mesh-b), transparent 38%),
|
||||
var(--el-bg-color-page);
|
||||
}
|
||||
|
||||
.main-stage {
|
||||
max-width: var(--content-max-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--stage-padding-y) var(--stage-padding-x);
|
||||
min-height: calc(100vh - var(--navbar-height) - var(--tabs-height, 0px));
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:root {
|
||||
--stage-padding-x: 14px;
|
||||
--stage-padding-y: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.navbar-tool,
|
||||
.menu.theme-dark .el-menu .el-menu-item,
|
||||
.menu.theme-dark .el-menu .el-sub-menu__title {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.app-header {
|
||||
backdrop-filter: none;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
}
|
||||
+46
-31
@@ -1,43 +1,58 @@
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--table-header-bg-color: var(--el-bg-color);
|
||||
--el-bg-color-page: #0a0a0a;
|
||||
--el-bg-color: #1d2124;
|
||||
--el-bg-color-overlay: #1d1e1f;
|
||||
--el-text-color-primary: #e5eaf3;
|
||||
--el-text-color-regular: #cfd3dc;
|
||||
--el-text-color-secondary: #a3a6ad;
|
||||
--el-text-color-placeholder: #8d9095;
|
||||
--el-text-color-disabled: #6c6e72;
|
||||
--el-border-color-darker: #636466;
|
||||
--el-border-color-dark: #58585b;
|
||||
--el-border-color: #4c4d4f;
|
||||
--el-border-color-light: #414243;
|
||||
--el-border-color-lighter: #363637;
|
||||
--el-border-color-extra-light: #2b2b2c;
|
||||
--el-fill-color-darker: #424243;
|
||||
--el-fill-color-dark: #39393a;
|
||||
--el-fill-color: #303030;
|
||||
--el-fill-color-light: #262727;
|
||||
--el-fill-color-lighter: #1d1d1d;
|
||||
--el-fill-color-extra-light: #191919;
|
||||
|
||||
--table-header-bg-color: rgba(6, 182, 212, 0.12);
|
||||
--sidebar-dark-bg: #030712;
|
||||
--sidebar-dark-hover: rgba(255, 255, 255, 0.05);
|
||||
--sidebar-dark-active: rgba(6, 182, 212, 0.2);
|
||||
|
||||
--admin-surface-elevated: #111827;
|
||||
--admin-surface-muted: rgba(17, 24, 39, 0.88);
|
||||
--admin-surface-glass: rgba(17, 24, 39, 0.86);
|
||||
|
||||
--el-bg-color-page: #030712;
|
||||
--el-bg-color: #0f172a;
|
||||
--el-bg-color-overlay: #1e293b;
|
||||
--el-text-color-primary: #f1f5f9;
|
||||
--el-text-color-regular: #cbd5e1;
|
||||
--el-text-color-secondary: #94a3b8;
|
||||
--el-text-color-placeholder: #64748b;
|
||||
--el-text-color-disabled: #475569;
|
||||
--el-border-color-darker: #64748b;
|
||||
--el-border-color-dark: #475569;
|
||||
--el-border-color: rgba(6, 182, 212, 0.18);
|
||||
--el-border-color-light: rgba(6, 182, 212, 0.12);
|
||||
--el-border-color-lighter: rgba(255, 255, 255, 0.06);
|
||||
--el-border-color-extra-light: rgba(255, 255, 255, 0.04);
|
||||
--el-fill-color-darker: #334155;
|
||||
--el-fill-color-dark: #1e293b;
|
||||
--el-fill-color: #1e293b;
|
||||
--el-fill-color-light: #172033;
|
||||
--el-fill-color-lighter: #131c2e;
|
||||
--el-fill-color-extra-light: #0f172a;
|
||||
--el-fill-color-blank: var(--el-bg-color);
|
||||
--el-mask-color: rgba(0, 0, 0, 0.8);
|
||||
--el-mask-color-extra-light: rgba(0, 0, 0, 0.3);
|
||||
--el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.36), 0px 8px 20px rgba(0, 0, 0, 0.72);
|
||||
--el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.72);
|
||||
--el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.72);
|
||||
--el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.72), 0px 12px 32px #000000,
|
||||
0px 8px 16px -8px #000000 !important;
|
||||
/* wangeditor主题 */
|
||||
|
||||
--el-mask-color: rgba(2, 6, 23, 0.78);
|
||||
--el-mask-color-extra-light: rgba(2, 6, 23, 0.42);
|
||||
|
||||
--el-box-shadow: 0 4px 24px rgba(0, 0, 0, 0.28);
|
||||
--el-box-shadow-light: 0 2px 16px rgba(0, 0, 0, 0.22);
|
||||
--el-box-shadow-lighter: 0 1px 4px rgba(0, 0, 0, 0.16);
|
||||
--el-box-shadow-dark: 0 16px 48px rgba(0, 0, 0, 0.36);
|
||||
|
||||
--admin-header-border: rgba(6, 182, 212, 0.14);
|
||||
--admin-sidebar-border: rgba(255, 255, 255, 0.04);
|
||||
|
||||
--admin-brand-mesh-a: rgba(6, 182, 212, 0.12);
|
||||
--admin-brand-mesh-b: rgba(16, 185, 129, 0.08);
|
||||
--admin-brand-mesh-c: rgba(99, 102, 241, 0.06);
|
||||
|
||||
--w-e-textarea-bg-color: var(--el-bg-color);
|
||||
--w-e-textarea-color: var(--el-text-color-primary);
|
||||
--w-e-textarea-border-color: var(--el-border-color);
|
||||
--w-e-textarea-slight-border-color: var(--el-border-color-light);
|
||||
--w-e-textarea-slight-color: var(--el-border-color);
|
||||
--w-e-textarea-slight-bg-color: var(--el-bg-color-page);
|
||||
/* --w-e-textarea-selected-border-color: #b4d5ff;
|
||||
--w-e-textarea-handler-bg-color: #4290f7; */
|
||||
--w-e-toolbar-color: var(--el-text-color-primary);
|
||||
--w-e-toolbar-bg-color: var(--el-bg-color);
|
||||
--w-e-toolbar-active-color: var(--el-text-color-primary);
|
||||
|
||||
+246
-26
@@ -1,14 +1,20 @@
|
||||
:root {
|
||||
// 确保消息提示在最上层
|
||||
/* Messages & notifications */
|
||||
.el-message {
|
||||
z-index: 9999 !important;
|
||||
border-radius: var(--admin-radius-md);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
}
|
||||
|
||||
|
||||
.el-notification {
|
||||
z-index: 9999 !important;
|
||||
border-radius: var(--admin-radius-lg);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
}
|
||||
|
||||
// 弹窗居中
|
||||
|
||||
/* Dialog */
|
||||
.el-overlay-dialog {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -19,11 +25,14 @@
|
||||
.el-dialog {
|
||||
--el-dialog-content-font-size: var(--el-font-size-base);
|
||||
--el-dialog-margin-top: 50px;
|
||||
max-width: calc(100vw - 30px);
|
||||
--el-dialog-border-radius: var(--admin-radius-lg);
|
||||
max-width: calc(100vw - 32px);
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--admin-radius-lg);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow-dark);
|
||||
|
||||
&.body-padding .el-dialog__body {
|
||||
padding: 0;
|
||||
@@ -31,50 +40,158 @@
|
||||
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
padding: 16px 20px 20px;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
font-size: var(--el-font-size-large);
|
||||
font-weight: 600;
|
||||
padding: 16px 20px 12px;
|
||||
margin-right: 0;
|
||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 12px 20px 16px;
|
||||
border-top: 1px solid var(--el-border-color-extra-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Cards - applies to all list pages */
|
||||
.el-card {
|
||||
--el-card-border-radius: var(--admin-radius-lg);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: none;
|
||||
background: var(--admin-surface-elevated);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&.is-always-shadow,
|
||||
&.is-hover-shadow:hover {
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
|
||||
.el-card__header {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
.el-menu {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Drawer */
|
||||
.el-drawer {
|
||||
--el-drawer-padding-primary: 16px;
|
||||
|
||||
&__header {
|
||||
margin-bottom: 0;
|
||||
padding: 13px 16px;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@apply text-tx-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--el-table-header-text-color: var(--el-text-color-primary);
|
||||
--el-table-header-bg-color: var(--table-header-bg-color);
|
||||
font-size: var(--el-font-size-base);
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-weight: 400;
|
||||
}
|
||||
&__body {
|
||||
padding: 16px 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.el-table {
|
||||
--el-table-header-text-color: var(--el-text-color-primary);
|
||||
--el-table-header-bg-color: var(--table-header-bg-color);
|
||||
--el-table-border-color: var(--el-border-color-extra-light);
|
||||
--el-table-row-hover-bg-color: var(--el-fill-color-lighter);
|
||||
font-size: var(--el-font-size-base);
|
||||
border-radius: var(--admin-radius-md);
|
||||
overflow: hidden;
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-weight: 600;
|
||||
font-size: var(--el-font-size-small);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
th.el-table__cell {
|
||||
background-color: var(--table-header-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__cell {
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form controls */
|
||||
.el-input-group__prepend {
|
||||
background-color: var(--el-fill-color-blank);
|
||||
background-color: var(--el-fill-color-light);
|
||||
border-color: var(--el-border-color);
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
--el-checkbox-font-size: var(--el-font-size-base);
|
||||
}
|
||||
|
||||
.el-button {
|
||||
--el-border-radius-base: var(--admin-radius-md);
|
||||
font-weight: 500;
|
||||
transition: transform 0.12s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||
|
||||
&:active:not(.is-disabled) {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--primary:not(.is-link):not(.is-text):not(.is-plain) {
|
||||
background: var(--admin-brand-gradient);
|
||||
border: none;
|
||||
box-shadow: var(--admin-brand-glow);
|
||||
--el-button-bg-color: transparent;
|
||||
--el-button-border-color: transparent;
|
||||
--el-button-hover-bg-color: transparent;
|
||||
--el-button-hover-border-color: transparent;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.05);
|
||||
box-shadow: 0 12px 32px rgba(6, 182, 212, 0.32);
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--primary.is-link,
|
||||
.el-button--primary.is-text {
|
||||
--el-button-hover-link-text-color: var(--admin-brand-primary-dark);
|
||||
}
|
||||
|
||||
.el-button--primary.is-plain {
|
||||
--el-button-bg-color: rgba(6, 182, 212, 0.1);
|
||||
--el-button-border-color: rgba(6, 182, 212, 0.35);
|
||||
--el-button-text-color: var(--admin-brand-primary-dark);
|
||||
--el-button-hover-bg-color: rgba(6, 182, 212, 0.16);
|
||||
--el-button-hover-border-color: var(--admin-brand-primary);
|
||||
}
|
||||
|
||||
.el-button--large {
|
||||
--el-border-radius-base: var(--admin-radius-md);
|
||||
}
|
||||
|
||||
.el-button.is-round {
|
||||
--el-border-radius-base: var(--admin-radius-round);
|
||||
}
|
||||
|
||||
/* Popup menus */
|
||||
.el-menu--popup-container {
|
||||
&.theme-light {
|
||||
.el-menu {
|
||||
@@ -83,12 +200,14 @@
|
||||
@apply bg-primary-light-9 border-primary border-r-2;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.theme-dark {
|
||||
.el-menu {
|
||||
.el-menu-item {
|
||||
@@ -101,52 +220,120 @@
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
--el-messagebox-width: 350px;
|
||||
--el-messagebox-width: 380px;
|
||||
--el-messagebox-border-radius: var(--admin-radius-lg);
|
||||
}
|
||||
|
||||
.el-date-editor {
|
||||
--el-date-editor-datetimerange-width: 380px;
|
||||
|
||||
.el-range-input {
|
||||
font-size: var(--el-font-size-small);
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
--el-button-hover-link-text-color: var(--el-color-primary-light-3);
|
||||
}
|
||||
.el-button--success {
|
||||
--el-button-hover-link-text-color: var(--el-color-success-light-3);
|
||||
}
|
||||
|
||||
.el-button--info {
|
||||
--el-button-hover-link-text-color: var(--el-color-info-light-3);
|
||||
}
|
||||
|
||||
.el-button--warning {
|
||||
--el-button-hover-link-text-color: var(--el-color-warning-light-3);
|
||||
}
|
||||
|
||||
.el-button--danger {
|
||||
--el-button-hover-link-text-color: var(--el-color-danger-light-3);
|
||||
}
|
||||
|
||||
.el-image__error {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background: var(--el-border-color-extra-light);
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 999px;
|
||||
background: var(--admin-brand-gradient);
|
||||
}
|
||||
|
||||
.el-page-header {
|
||||
&__breadcrumb {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.el-tag {
|
||||
--el-tag-border-radius: var(--admin-radius-sm);
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.el-pagination {
|
||||
.el-pager li {
|
||||
border-radius: var(--admin-radius-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-prev,
|
||||
.btn-next {
|
||||
border-radius: var(--admin-radius-sm);
|
||||
}
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.el-alert {
|
||||
border-radius: var(--admin-radius-md);
|
||||
border: 1px solid var(--el-border-color-extra-light);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.el-empty {
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
/* Descriptions */
|
||||
.el-descriptions {
|
||||
.el-descriptions__label {
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* Focus rings */
|
||||
.el-input,
|
||||
.el-select,
|
||||
.el-textarea {
|
||||
@apply shadow-primary-light-8;
|
||||
|
||||
box-shadow: 0 0 0 0 var(--tw-shadow-color);
|
||||
|
||||
&:focus-within {
|
||||
box-shadow: 0 0 0 2px var(--tw-shadow-color);
|
||||
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
|
||||
transition: box-shadow ease 0.1s;
|
||||
transition: box-shadow ease 0.15s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +343,7 @@
|
||||
border-radius: var(--el-checkbox-border-radius);
|
||||
|
||||
box-shadow: 0 0 0 0 var(--tw-shadow-color);
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 0 0 2px var(--tw-shadow-color);
|
||||
transition: box-shadow ease 0s;
|
||||
@@ -173,29 +361,61 @@
|
||||
.el-form-item.is-error .el-checkbox {
|
||||
@apply shadow-danger-light-8;
|
||||
}
|
||||
|
||||
/* Dropdown */
|
||||
.el-dropdown-menu {
|
||||
border-radius: var(--admin-radius-md);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
border-radius: var(--admin-radius-sm);
|
||||
}
|
||||
|
||||
/* Popover / tooltip polish */
|
||||
.el-popover.el-popper {
|
||||
border-radius: var(--admin-radius-md);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.el-pagination > .el-pagination__jump {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.el-pagination > .el-pagination__sizes {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
// 防止被tailwindcss默认样式覆盖
|
||||
background-color: var(--el-button-bg-color, var(--el-color-white));
|
||||
|
||||
//覆盖el-button的点击样式
|
||||
&:focus {
|
||||
color: var(--el-button-text-color);
|
||||
border-color: var(--el-button-border-color);
|
||||
background-color: var(--el-button-bg-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-button-hover-text-color);
|
||||
border-color: var(--el-button-hover-border-color);
|
||||
background-color: var(--el-button-hover-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline form filter blocks on list pages */
|
||||
.el-card .el-form--inline {
|
||||
.el-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Page section spacing between stacked cards */
|
||||
.el-card + .el-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@import 'element.scss';
|
||||
@import 'dark.css';
|
||||
@import 'var.css';
|
||||
@import 'dark.css';
|
||||
@import 'tailwind.css';
|
||||
@import 'element.scss';
|
||||
@import 'admin-shell.scss';
|
||||
@import 'admin-pages.scss';
|
||||
@import 'public.scss';
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
body {
|
||||
@apply text-base text-tx-primary overflow-hidden min-w-[375px];
|
||||
font-feature-settings: 'kern' 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.form-tips {
|
||||
@apply text-tx-secondary text-xs leading-6 mt-1;
|
||||
}
|
||||
@@ -12,7 +16,51 @@ body {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--el-border-color);
|
||||
border-radius: 999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--el-border-color-dark);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
/* NProgress */
|
||||
#nprogress .bar {
|
||||
@apply bg-primary #{!important};
|
||||
height: 2px !important;
|
||||
}
|
||||
|
||||
#nprogress .peg {
|
||||
box-shadow: 0 0 8px var(--el-color-primary), 0 0 4px var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
/* Shared page utilities */
|
||||
.admin-page-title {
|
||||
@apply text-xl font-semibold text-tx-primary tracking-tight;
|
||||
}
|
||||
|
||||
.admin-page-desc {
|
||||
@apply text-sm text-tx-secondary mt-1;
|
||||
}
|
||||
|
||||
.admin-stat-value {
|
||||
@apply text-3xl font-semibold text-tx-primary tabular-nums;
|
||||
}
|
||||
|
||||
.admin-stat-label {
|
||||
@apply text-sm text-tx-secondary;
|
||||
}
|
||||
|
||||
+85
-35
@@ -1,49 +1,99 @@
|
||||
:root {
|
||||
/* Typography */
|
||||
--el-font-family: theme(fontFamily.sans);
|
||||
--el-font-weight-primary: 400;
|
||||
--el-menu-item-height: 46px;
|
||||
--el-menu-sub-item-height: var(--el-menu-item-height);
|
||||
--el-menu-icon-width: 18px;
|
||||
--aside-width: 200px;
|
||||
--navbar-height: 50px;
|
||||
--color-white: #ffffff;
|
||||
--table-header-bg-color: #f8f8f8;
|
||||
--el-font-size-extra-large: 18px;
|
||||
--el-menu-base-level-padding: 16px;
|
||||
--el-menu-level-padding: 26px;
|
||||
--el-font-size-large: 16px;
|
||||
--el-font-size-medium: 15px;
|
||||
--el-font-size-base: 14px;
|
||||
--el-font-size-small: 13px;
|
||||
--el-font-size-extra-small: 12px;
|
||||
|
||||
/* Brand - MedTech 绚丽临床 */
|
||||
--admin-brand-primary: #06b6d4;
|
||||
--admin-brand-primary-dark: #0891b2;
|
||||
--admin-brand-accent: #10b981;
|
||||
--admin-brand-secondary: #6366f1;
|
||||
--admin-brand-success: #10b981;
|
||||
--admin-brand-gradient: linear-gradient(120deg, #06b6d4 0%, #10b981 48%, #0891b2 100%);
|
||||
--admin-brand-gradient-soft: linear-gradient(
|
||||
120deg,
|
||||
rgba(6, 182, 212, 0.16) 0%,
|
||||
rgba(16, 185, 129, 0.1) 50%,
|
||||
rgba(8, 145, 178, 0.08) 100%
|
||||
);
|
||||
--admin-brand-glow: 0 0 24px rgba(6, 182, 212, 0.35);
|
||||
--admin-brand-mesh-a: rgba(6, 182, 212, 0.18);
|
||||
--admin-brand-mesh-b: rgba(16, 185, 129, 0.14);
|
||||
--admin-brand-mesh-c: rgba(99, 102, 241, 0.1);
|
||||
|
||||
/* Layout shell v2 */
|
||||
--aside-width: 248px;
|
||||
--navbar-height: 60px;
|
||||
--tabs-height: 42px;
|
||||
--stage-padding-x: 28px;
|
||||
--stage-padding-y: 24px;
|
||||
--content-max-width: 1680px;
|
||||
--sidebar-rail-width: 3px;
|
||||
|
||||
/* Shape */
|
||||
--admin-radius-sm: 8px;
|
||||
--admin-radius-md: 12px;
|
||||
--admin-radius-lg: 16px;
|
||||
--admin-radius-xl: 22px;
|
||||
--admin-radius-2xl: 28px;
|
||||
--el-border-radius-base: var(--admin-radius-md);
|
||||
--el-border-radius-small: var(--admin-radius-sm);
|
||||
--el-border-radius-round: 999px;
|
||||
|
||||
/* Menu */
|
||||
--el-menu-item-height: 46px;
|
||||
--el-menu-sub-item-height: var(--el-menu-item-height);
|
||||
--el-menu-icon-width: 20px;
|
||||
--el-menu-base-level-padding: 14px;
|
||||
--el-menu-level-padding: 22px;
|
||||
|
||||
/* Surfaces */
|
||||
--color-white: #ffffff;
|
||||
--table-header-bg-color: rgba(6, 182, 212, 0.06);
|
||||
--sidebar-dark-bg: #060d18;
|
||||
--sidebar-dark-hover: rgba(255, 255, 255, 0.05);
|
||||
--sidebar-dark-active: rgba(6, 182, 212, 0.14);
|
||||
--admin-surface-elevated: #ffffff;
|
||||
--admin-surface-muted: rgba(255, 255, 255, 0.72);
|
||||
--admin-surface-glass: rgba(255, 255, 255, 0.82);
|
||||
|
||||
--el-bg-color: var(--color-white);
|
||||
--el-bg-color-page: #f6f6f6;
|
||||
--el-bg-color-page: #eef6fb;
|
||||
--el-bg-color-overlay: #ffffff;
|
||||
--el-text-color-primary: #333333;
|
||||
--el-text-color-regular: #666666;
|
||||
--el-text-color-secondary: #999999;
|
||||
--el-text-color-placeholder: #a8abb2;
|
||||
--el-text-color-disabled: #c0c4cc;
|
||||
--el-border-color: #dcdfe6;
|
||||
--el-border-color-light: #e4e7ed;
|
||||
--el-border-color-lighter: #ebeef5;
|
||||
--el-border-color-extra-light: #f2f2f2;
|
||||
--el-border-color-dark: #d4d7de;
|
||||
--el-border-color-darker: #cdd0d6;
|
||||
--el-fill-color: #f0f2f5;
|
||||
--el-fill-color-light: #f8f8f8;
|
||||
--el-fill-color-lighter: #fafafa;
|
||||
--el-fill-color-extra-light: #fafcff;
|
||||
--el-fill-color-dark: #ebedf0;
|
||||
--el-fill-color-darker: #e6e8eb;
|
||||
--el-text-color-primary: #0b1220;
|
||||
--el-text-color-regular: #334155;
|
||||
--el-text-color-secondary: #64748b;
|
||||
--el-text-color-placeholder: #94a3b8;
|
||||
--el-text-color-disabled: #cbd5e1;
|
||||
--el-border-color: rgba(6, 182, 212, 0.12);
|
||||
--el-border-color-light: rgba(6, 182, 212, 0.08);
|
||||
--el-border-color-lighter: rgba(15, 23, 42, 0.06);
|
||||
--el-border-color-extra-light: rgba(15, 23, 42, 0.04);
|
||||
--el-border-color-dark: #cbd5e1;
|
||||
--el-border-color-darker: #94a3b8;
|
||||
--el-fill-color: #f1f5f9;
|
||||
--el-fill-color-light: #f8fafc;
|
||||
--el-fill-color-lighter: #fafbfc;
|
||||
--el-fill-color-extra-light: #fcfdfe;
|
||||
--el-fill-color-dark: #e2e8f0;
|
||||
--el-fill-color-darker: #cbd5e1;
|
||||
--el-fill-color-blank: #ffffff;
|
||||
/* 过亮会盖住抽屉/弹窗下的内容;Element Loading 与部分蒙层共用此变量 */
|
||||
--el-mask-color: rgba(255, 255, 255, 0.5);
|
||||
--el-mask-color-extra-light: rgba(255, 255, 255, 0.22);
|
||||
-el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.04), 0px 8px 20px rgba(0, 0, 0, 0.08);
|
||||
--el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.12);
|
||||
--el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.12);
|
||||
--el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.08), 0px 12px 32px rgba(0, 0, 0, 0.12),
|
||||
0px 8px 16px -8px rgba(0, 0, 0, 0.16);
|
||||
|
||||
--el-mask-color: rgba(6, 13, 24, 0.55);
|
||||
--el-mask-color-extra-light: rgba(6, 13, 24, 0.1);
|
||||
|
||||
--el-box-shadow: 0 4px 24px rgba(6, 182, 212, 0.08), 0 12px 40px rgba(15, 23, 42, 0.06);
|
||||
--el-box-shadow-light: 0 2px 16px rgba(6, 182, 212, 0.1);
|
||||
--el-box-shadow-lighter: 0 1px 4px rgba(15, 23, 42, 0.04);
|
||||
--el-box-shadow-dark: 0 16px 48px rgba(6, 182, 212, 0.14), 0 32px 64px rgba(15, 23, 42, 0.1);
|
||||
|
||||
--admin-header-border: rgba(6, 182, 212, 0.1);
|
||||
--admin-sidebar-border: rgba(255, 255, 255, 0.06);
|
||||
--admin-stage-bg: transparent;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div class="change-password flex flex-col">
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<div class="change-password-card bg-body rounded-md px-10 py-10 w-[480px]">
|
||||
<div class="text-center text-2xl font-medium mb-2">首次登录</div>
|
||||
<div class="text-center text-gray-500 text-sm mb-8">为了您的账号安全,请修改初始密码</div>
|
||||
<div class="change-password-backdrop" aria-hidden="true"></div>
|
||||
<div class="flex-1 flex items-center justify-center relative z-[1] px-4">
|
||||
<div class="change-password-card">
|
||||
<div class="text-center text-2xl font-semibold mb-2 text-tx-primary">首次登录</div>
|
||||
<div class="text-center text-tx-secondary text-sm mb-8">为了您的账号安全,请修改初始密码</div>
|
||||
|
||||
<el-form ref="formRef" :model="formData" size="large" :rules="rules">
|
||||
<el-form-item prop="password">
|
||||
@@ -117,7 +118,25 @@ const { isLock, lockFn: lockSubmit } = useLockFn(handleSubmit)
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.change-password {
|
||||
background-image: url('./images/login_bg.png');
|
||||
@apply min-h-screen bg-no-repeat bg-center bg-cover;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
.change-password-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 15% 20%, rgba(15, 118, 110, 0.35), transparent 42%),
|
||||
linear-gradient(160deg, #0f172a 0%, #111827 48%, #0b1120 100%);
|
||||
}
|
||||
|
||||
.change-password-card {
|
||||
width: min(480px, 100%);
|
||||
padding: 40px 36px;
|
||||
border-radius: var(--admin-radius-xl);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 24px 64px rgba(2, 6, 23, 0.45);
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
<template>
|
||||
<div class="login flex flex-col">
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<div class="login-card flex rounded-md overflow-hidden">
|
||||
<div class="flex-1 h-full hidden md:inline-block">
|
||||
<image-contain :src="config.login_image" :width="400" height="100%" />
|
||||
<div class="login-v2">
|
||||
<div class="login-v2__mesh" aria-hidden="true"></div>
|
||||
<div class="login-v2__layout">
|
||||
<aside class="login-v2__brand">
|
||||
<div class="login-v2__brand-inner">
|
||||
<div class="login-v2__mark">ZYT</div>
|
||||
<p class="login-v2__kicker">互联网医院</p>
|
||||
<h1 class="login-v2__headline">医生与医助<br />智慧工作台</h1>
|
||||
<p class="login-v2__lead">问诊、处方、订单与运营数据,一屏协同。</p>
|
||||
<div v-if="config.login_image" class="login-v2__visual hidden xl:block">
|
||||
<image-contain :src="config.login_image" :width="420" height="280" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="login-form bg-body flex flex-col justify-center px-10 py-10 md:w-[420px] w-[380px] flex-none mx-auto"
|
||||
>
|
||||
<div class="text-center text-3xl font-medium mb-8">{{ config.web_name }}</div>
|
||||
</aside>
|
||||
|
||||
<!-- 企业微信自动授权中 -->
|
||||
<div v-if="wxWorkAutoLogin" class="text-center py-10">
|
||||
<section class="login-v2__panel">
|
||||
<div class="login-v2__glass">
|
||||
<div class="login-v2__form-head">
|
||||
<h2 class="login-v2__form-title">{{ config.web_name }}</h2>
|
||||
<p class="login-v2__form-sub">登录后继续你的接诊与管理工作</p>
|
||||
</div>
|
||||
|
||||
<div v-if="wxWorkAutoLogin" class="login-v2__loading">
|
||||
<el-icon class="is-loading mb-4" :size="40" color="var(--el-color-primary)">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<div class="text-gray-500">企业微信授权登录中...</div>
|
||||
<div class="text-tx-secondary">企业微信授权登录中...</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 登录方式切换标签 -->
|
||||
<div v-if="wxWorkEnabled" class="flex justify-center mb-6">
|
||||
<div v-if="wxWorkEnabled" class="login-v2__mode">
|
||||
<el-radio-group v-model="loginMode" size="large">
|
||||
<el-radio-button value="account">账号登录</el-radio-button>
|
||||
<el-radio-button value="wxwork">企业微信</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 账号密码登录 -->
|
||||
<template v-if="loginMode === 'account'">
|
||||
<el-form ref="formRef" :model="formData" size="large" :rules="rules">
|
||||
<el-form-item prop="account">
|
||||
@@ -58,29 +66,34 @@
|
||||
<div class="mb-5">
|
||||
<el-checkbox v-model="remAccount" label="记住账号"></el-checkbox>
|
||||
</div>
|
||||
<el-button type="primary" size="large" :loading="isLock" @click="lockLogin">
|
||||
登录
|
||||
<el-button
|
||||
class="login-v2__submit"
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="isLock"
|
||||
@click="lockLogin"
|
||||
>
|
||||
进入工作台
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 企业微信扫码登录(非企业微信内浏览器) -->
|
||||
<template v-if="loginMode === 'wxwork'">
|
||||
<div class="wxwork-qrcode-wrap">
|
||||
<div v-if="wxWorkLoading" class="text-center py-10">
|
||||
<el-icon class="is-loading" :size="32" color="var(--el-color-primary)">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<div class="mt-2 text-gray-400 text-sm">加载企业微信扫码...</div>
|
||||
<div class="mt-2 text-tx-secondary text-sm">加载企业微信扫码...</div>
|
||||
</div>
|
||||
<div v-else id="wxwork_qrcode_container" class="wxwork-qrcode"></div>
|
||||
</div>
|
||||
<div class="text-center text-sm text-gray-400 mt-4">
|
||||
<div class="text-center text-sm text-tx-secondary mt-4">
|
||||
请使用企业微信扫描二维码登录
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<layout-footer />
|
||||
</div>
|
||||
@@ -286,31 +299,178 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
background-image: url('./images/login_bg.png');
|
||||
@apply min-h-screen bg-no-repeat bg-center bg-cover;
|
||||
.login-card {
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
.login-v2 {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #030712;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-v2__mesh {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 12% 18%, rgba(6, 182, 212, 0.35), transparent 42%),
|
||||
radial-gradient(circle at 88% 12%, rgba(16, 185, 129, 0.28), transparent 38%),
|
||||
radial-gradient(circle at 70% 88%, rgba(99, 102, 241, 0.18), transparent 40%),
|
||||
linear-gradient(155deg, #030712 0%, #0b1220 45%, #060d18 100%);
|
||||
}
|
||||
|
||||
.login-v2__layout {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
min-height: calc(100vh - 48px);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.login-v2__layout {
|
||||
grid-template-columns: minmax(0, 1.05fr) minmax(420px, 520px);
|
||||
}
|
||||
}
|
||||
|
||||
.login-v2__brand {
|
||||
display: none;
|
||||
padding: 48px 56px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.login-v2__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.login-v2__brand-inner {
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.login-v2__mark {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.1em;
|
||||
background: var(--admin-brand-gradient);
|
||||
box-shadow: var(--admin-brand-glow);
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.login-v2__kicker {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.login-v2__headline {
|
||||
margin: 0;
|
||||
font-size: clamp(32px, 4vw, 44px);
|
||||
font-weight: 800;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.login-v2__lead {
|
||||
margin: 16px 0 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
max-width: 36ch;
|
||||
}
|
||||
|
||||
.login-v2__visual {
|
||||
margin-top: 36px;
|
||||
border-radius: var(--admin-radius-xl);
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.login-v2__panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 20px;
|
||||
}
|
||||
|
||||
.login-v2__glass {
|
||||
width: min(440px, 100%);
|
||||
padding: 36px 32px;
|
||||
border-radius: var(--admin-radius-2xl);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(24px) saturate(160%);
|
||||
box-shadow: var(--el-box-shadow-dark);
|
||||
}
|
||||
|
||||
.login-v2__form-head {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.login-v2__form-title {
|
||||
margin: 0;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.login-v2__form-sub {
|
||||
margin: 8px 0 0;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.login-v2__mode {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.login-v2__submit {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-v2__loading {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.wxwork-qrcode-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.wxwork-qrcode {
|
||||
width: 340px;
|
||||
height: 400px;
|
||||
width: 320px;
|
||||
height: 360px;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(iframe) {
|
||||
width: 340px !important;
|
||||
height: 400px !important;
|
||||
width: 320px !important;
|
||||
height: 360px !important;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.login-v2__glass {
|
||||
background: #ffffff;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:用于管理网站的分类,只可添加到一级"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never" v-loading="pager.loading">
|
||||
<div>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button
|
||||
class="mb-4"
|
||||
v-perms="['article.articleCate/add']"
|
||||
type="primary"
|
||||
@click="handleAdd()"
|
||||
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table size="large" :data="pager.lists">
|
||||
<el-table-column label="栏目名称" prop="name" min-width="120" />
|
||||
<el-table-column label="文章数" prop="article_count" min-width="120" />
|
||||
@@ -58,10 +58,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="article-lists">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="文章标题">
|
||||
<el-input
|
||||
@@ -33,24 +33,25 @@
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<router-link
|
||||
v-perms="['article.article/add', 'article.article/add:edit']"
|
||||
:to="{
|
||||
path: getRoutePath('article.article/add:edit')
|
||||
}"
|
||||
>
|
||||
<el-button type="primary" class="mb-4">
|
||||
<el-button type="primary">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
发布文章
|
||||
</el-button>
|
||||
</router-link>
|
||||
</div>
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
</template>
|
||||
<el-table size="large" :data="pager.lists">
|
||||
<el-table-column label="ID" prop="id" min-width="80" />
|
||||
<el-table-column label="封面" min-width="100">
|
||||
<template #default="{ row }">
|
||||
@@ -116,10 +117,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="articleLists">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="asset-resource-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-form class="ls-form" :model="searchData" inline>
|
||||
<el-form-item class="w-[280px]" label="标题">
|
||||
<el-input
|
||||
@@ -28,25 +28,25 @@
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="mb-4 flex items-center gap-2">
|
||||
<admin-page-data-panel v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="handleAdd">上传资源</el-button>
|
||||
<el-button type="danger" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||
批量删除
|
||||
</el-button>
|
||||
<span v-if="selectedIds.length > 0" class="text-sm text-gray-500">已选 {{ selectedIds.length }} 条</span>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<el-tabs v-model="queryParams.type" @tab-change="handleTabChange">
|
||||
<el-tab-pane label="图片" name="1"></el-tab-pane>
|
||||
<el-tab-pane label="视频" name="2"></el-tab-pane>
|
||||
<el-tab-pane label="语音" name="3"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-table :data="tableData" v-loading="loading" @selection-change="handleSelectionChange">
|
||||
<el-table :data="tableData" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<!-- <el-table-column prop="id" label="ID" width="80" /> -->
|
||||
<el-table-column prop="title" label="标题" min-width="80" />
|
||||
@@ -71,7 +71,7 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<template #footer>
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page_no"
|
||||
v-model:page-size="queryParams.page_size"
|
||||
@@ -80,8 +80,8 @@
|
||||
@size-change="getList"
|
||||
@current-change="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
|
||||
<!-- 新增/编辑资源弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑资源' : '新增分发资源'" width="600px" destroy-on-close>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="asset-user-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-form class="ls-form" :model="searchData" inline>
|
||||
<el-form-item class="w-[280px]" label="手机号">
|
||||
<el-input
|
||||
@@ -16,15 +16,15 @@
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="mb-4">
|
||||
<admin-page-data-panel v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="handleAdd">新增账号</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" v-loading="loading">
|
||||
</template>
|
||||
|
||||
<el-table :data="tableData">
|
||||
<!-- <el-table-column prop="id" label="ID" width="80" /> -->
|
||||
<el-table-column prop="phone" label="手机号" />
|
||||
<el-table-column label="备注" min-width="200">
|
||||
@@ -63,7 +63,7 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<template #footer>
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page_no"
|
||||
v-model:page-size="queryParams.page_size"
|
||||
@@ -72,8 +72,8 @@
|
||||
@size-change="getList"
|
||||
@current-change="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
|
||||
<!-- 编辑/新增弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" destroy-on-close>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="用户信息">
|
||||
<el-input
|
||||
@@ -37,8 +37,9 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel>
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
<el-table-column label="头像" min-width="100">
|
||||
<template #default="{ row }">
|
||||
@@ -67,10 +68,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="consumerLists">
|
||||
|
||||
+28
-407
@@ -327,27 +327,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="flex items-center gap-1">
|
||||
<span>服用方式</span>
|
||||
<el-button
|
||||
v-if="
|
||||
!readonly &&
|
||||
detailData.prescription_id &&
|
||||
detailPrescription &&
|
||||
!String(detailData.prescription_detail_error || '').trim()
|
||||
"
|
||||
v-perms="['tcm.prescriptionOrder/patchPrescriptionUsage']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
@click="openPatchUsageDialog"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions-item label="服用方式">
|
||||
<div class="flex flex-col gap-1.5 text-sm leading-relaxed">
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div>
|
||||
@@ -651,7 +631,7 @@
|
||||
<el-descriptions-item label="上次医护">{{ detailData.prev_staff || '—' }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="服务渠道">{{ detailData.service_channel || '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="服务套餐">{{ detailServicePackageText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="服务套餐">{{ formatServicePackage(detailData.service_package) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="费用类别">{{ feeTypeText(detailData.fee_type) }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="showInternalCost" label="内部成本">
|
||||
@@ -800,18 +780,7 @@
|
||||
class="po-panel border-gray-100 mt-4"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="font-medium text-[15px]">操作日志</span>
|
||||
<el-button
|
||||
v-if="canAddPrescriptionOrderLog()"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="openAddLogDialog"
|
||||
>
|
||||
新增日志
|
||||
</el-button>
|
||||
</div>
|
||||
<span class="font-medium text-[15px]">操作日志</span>
|
||||
</template>
|
||||
<el-timeline v-if="detailLogs.length" class="mt-2 pl-2">
|
||||
<el-timeline-item
|
||||
@@ -831,182 +800,19 @@
|
||||
</el-timeline>
|
||||
<el-empty v-else description="暂无操作日志" :image-size="64" />
|
||||
</el-card>
|
||||
|
||||
<!-- 新增操作日志 -->
|
||||
<el-dialog
|
||||
v-model="addLogVisible"
|
||||
title="新增操作日志"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
@closed="resetAddLogForm"
|
||||
>
|
||||
<el-form ref="addLogFormRef" :model="addLogForm" :rules="addLogRules" label-width="108px">
|
||||
<el-form-item label="日志内容" prop="summary">
|
||||
<el-input
|
||||
v-model="addLogForm.summary"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="记录本次操作说明、沟通结果等"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="canSetRxAuditOnAddLog" label="处方审核">
|
||||
<el-select v-model="addLogForm.prescription_audit_status" class="w-full" clearable placeholder="不修改">
|
||||
<el-option label="待审核" :value="0" />
|
||||
<el-option label="已通过" :value="1" />
|
||||
<el-option label="已驳回" :value="2" />
|
||||
</el-select>
|
||||
<div v-if="detailData" class="text-xs text-gray-400 mt-1">
|
||||
当前:{{ auditStatusText(detailData.prescription_audit_status) }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="canSetRxAuditOnAddLog && addLogForm.prescription_audit_status !== '' && addLogForm.prescription_audit_status !== null && addLogForm.prescription_audit_status !== undefined"
|
||||
label="处方审核意见"
|
||||
>
|
||||
<el-input
|
||||
v-model="addLogForm.prescription_audit_remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="选填"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="canSetPayAuditOnAddLog" label="支付单审核">
|
||||
<el-select v-model="addLogForm.payment_slip_audit_status" class="w-full" clearable placeholder="不修改">
|
||||
<el-option label="待审核" :value="0" />
|
||||
<el-option label="已通过" :value="1" />
|
||||
<el-option label="已驳回" :value="2" />
|
||||
</el-select>
|
||||
<div v-if="detailData" class="text-xs text-gray-400 mt-1">
|
||||
当前:{{ auditStatusText(detailData.payment_slip_audit_status) }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="canSetPayAuditOnAddLog && addLogForm.payment_slip_audit_status !== '' && addLogForm.payment_slip_audit_status !== null && addLogForm.payment_slip_audit_status !== undefined"
|
||||
label="支付审核意见"
|
||||
>
|
||||
<el-input
|
||||
v-model="addLogForm.payment_slip_audit_remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="选填"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addLogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="addLogSaving" @click="submitAddLog">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改服用参数:主方 / 辅方 / 订单设置 -->
|
||||
<el-dialog
|
||||
v-model="patchUsageVisible"
|
||||
title="修改服用参数"
|
||||
width="480px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@closed="resetPatchUsageForm"
|
||||
>
|
||||
<el-form
|
||||
ref="patchUsageFormRef"
|
||||
:model="patchUsageForm"
|
||||
:rules="patchUsageRules"
|
||||
label-width="108px"
|
||||
>
|
||||
<div v-if="detailHasAuxHerbs" class="text-xs font-medium text-gray-500 mb-3">主方</div>
|
||||
<el-form-item label="每天次数" prop="times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">辅方</div>
|
||||
<el-form-item label="每天次数" prop="aux_times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="aux_usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">订单设置</div>
|
||||
<el-form-item label="服用天数" prop="medication_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.medication_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="patchUsageVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="patchUsageSaving" @click="submitPatchUsage">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="PrescriptionOrderDetailDrawer">
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { Refresh, Loading, Search, Van } from '@element-plus/icons-vue'
|
||||
import {
|
||||
prescriptionOrderDetail,
|
||||
prescriptionOrderLogs,
|
||||
prescriptionOrderAddLog,
|
||||
prescriptionOrderLogisticsTrace,
|
||||
prescriptionOrderLogisticsJdUpdate,
|
||||
prescriptionOrderPaidPayOrders,
|
||||
prescriptionOrderPatchPrescriptionUsage
|
||||
prescriptionOrderPaidPayOrders
|
||||
} from '@/api/tcm'
|
||||
import { getDictData } from '@/api/app'
|
||||
import feedback from '@/utils/feedback'
|
||||
@@ -1025,7 +831,6 @@ import {
|
||||
consumerRxAuditTag,
|
||||
expressCompanyLabel,
|
||||
logActionText,
|
||||
auditStatusText,
|
||||
formatPayOrderSource,
|
||||
normalizeBizPhone,
|
||||
recipientVsPrescriptionPhoneMismatch,
|
||||
@@ -1036,10 +841,7 @@ import {
|
||||
analyzeLogisticsPayloadUrgent,
|
||||
parseLogisticsTracePayload,
|
||||
canUpdateAmount,
|
||||
formatDietaryTaboo,
|
||||
type ServicePackageOption,
|
||||
normalizeServicePackageOptions,
|
||||
formatServicePackageLabels
|
||||
formatDietaryTaboo
|
||||
} from './prescription-order-utils'
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -1071,7 +873,6 @@ const emit = defineEmits<{
|
||||
(e: 'view-prescription'): void
|
||||
(e: 'test-gancao-preview'): void
|
||||
(e: 'view-patient'): void
|
||||
(e: 'detail-changed'): void
|
||||
}>()
|
||||
|
||||
const userStore = useUserStore()
|
||||
@@ -1287,24 +1088,36 @@ const detailFullAddress = computed(() => {
|
||||
})
|
||||
|
||||
// ─── 服务套餐字典 ───
|
||||
const servicePackageOptions = ref<ServicePackageOption[]>([])
|
||||
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||
|
||||
async function loadServicePackageOptions() {
|
||||
if (servicePackageOptions.value.length > 0) return
|
||||
try {
|
||||
const data: any = await getDictData({ type: 'server_order' })
|
||||
const opts = normalizeServicePackageOptions(data?.server_order)
|
||||
if (opts.length > 0) {
|
||||
servicePackageOptions.value = opts
|
||||
}
|
||||
servicePackageOptions.value = (data?.server_order || []).filter((item: any) => item.status !== 0)
|
||||
} catch {
|
||||
/* 请求被同参数请求取消或失败时保留现值,open() 时会重试 */
|
||||
servicePackageOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const detailServicePackageText = computed(() =>
|
||||
formatServicePackageLabels(detailData.value?.service_package, servicePackageOptions.value)
|
||||
)
|
||||
function formatServicePackage(value: any): string {
|
||||
if (!value) return '—'
|
||||
|
||||
let packages: string[] = []
|
||||
if (Array.isArray(value)) {
|
||||
packages = value
|
||||
} else if (typeof value === 'string') {
|
||||
packages = value.split(',').filter((v) => v.trim() !== '')
|
||||
}
|
||||
|
||||
if (packages.length === 0) return '—'
|
||||
|
||||
const names = packages.map((val) => {
|
||||
const option = servicePackageOptions.value.find((opt) => opt.value === val)
|
||||
return option ? option.name : val
|
||||
})
|
||||
|
||||
return names.join('、')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadServicePackageOptions()
|
||||
@@ -1329,196 +1142,6 @@ async function fetchLogs(id: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function hasPerm(perm: string) {
|
||||
const p = userStore.perms || []
|
||||
return p.includes('*') || p.includes(perm)
|
||||
}
|
||||
|
||||
function canAddPrescriptionOrderLog() {
|
||||
return hasPerm('tcm.prescriptionOrder/addLog')
|
||||
}
|
||||
|
||||
/** 与后端 canAuditPrescriptionOrder 同档:超管或 prescription_audit_roles */
|
||||
const canSetRxAuditOnAddLog = computed(() => {
|
||||
const u = userStore.userInfo
|
||||
if (!u || Number(u.root) === 1) return true
|
||||
const ids = Array.isArray(u.role_ids) ? u.role_ids.map((n: unknown) => Number(n)) : []
|
||||
return ids.some((id) => PRESCRIPTION_AUDIT_ROLE_IDS.includes(id))
|
||||
})
|
||||
|
||||
/** 与后端 canAuditPaymentSlipOrder 同档:超管或 prescription_order_payment_audit_roles 默认 0,3 */
|
||||
const canSetPayAuditOnAddLog = computed(() => {
|
||||
const u = userStore.userInfo
|
||||
if (!u || Number(u.root) === 1) return true
|
||||
const ids = Array.isArray(u.role_ids) ? u.role_ids.map((n: unknown) => Number(n)) : []
|
||||
return ids.some((id) => [0, 3].includes(id))
|
||||
})
|
||||
|
||||
const addLogVisible = ref(false)
|
||||
const addLogSaving = ref(false)
|
||||
const addLogFormRef = ref<FormInstance>()
|
||||
const addLogForm = reactive({
|
||||
summary: '',
|
||||
prescription_audit_status: '' as number | '',
|
||||
payment_slip_audit_status: '' as number | '',
|
||||
prescription_audit_remark: '',
|
||||
payment_slip_audit_remark: ''
|
||||
})
|
||||
const addLogRules: FormRules = {
|
||||
summary: [{ required: true, message: '请填写日志内容', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const patchUsageVisible = ref(false)
|
||||
const patchUsageSaving = ref(false)
|
||||
const patchUsageFormRef = ref<FormInstance>()
|
||||
const patchUsageForm = reactive({
|
||||
times_per_day: 3 as number | undefined,
|
||||
usage_days: 7 as number | undefined,
|
||||
aux_times_per_day: 3 as number | undefined,
|
||||
aux_usage_days: 7 as number | undefined,
|
||||
medication_days: undefined as number | undefined
|
||||
})
|
||||
const patchUsageRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {
|
||||
times_per_day: [{ required: true, message: '请填写主方每天次数', trigger: 'change' }],
|
||||
usage_days: [{ required: true, message: '请填写主方开立天数', trigger: 'change' }],
|
||||
medication_days: [{ required: true, message: '请填写订单服用天数', trigger: 'change' }]
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
rules.aux_times_per_day = [{ required: true, message: '请填写辅方每天次数', trigger: 'change' }]
|
||||
rules.aux_usage_days = [{ required: true, message: '请填写辅方开立天数', trigger: 'change' }]
|
||||
}
|
||||
return rules
|
||||
})
|
||||
|
||||
function openPatchUsageDialog() {
|
||||
const rx = detailPrescription.value
|
||||
const ord = detailData.value
|
||||
if (!rx || !ord?.id || !ord.prescription_id) {
|
||||
feedback.msgWarning('无处方数据')
|
||||
return
|
||||
}
|
||||
const aux = detailAuxUsage.value
|
||||
patchUsageForm.times_per_day =
|
||||
Number(rx.times_per_day) > 0 ? Number(rx.times_per_day) : 3
|
||||
patchUsageForm.usage_days =
|
||||
Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageForm.aux_times_per_day =
|
||||
aux && Number(aux.times_per_day) > 0 ? Number(aux.times_per_day) : 3
|
||||
patchUsageForm.aux_usage_days =
|
||||
aux && Number(aux.usage_days) > 0 ? Number(aux.usage_days) : 7
|
||||
const md = Number(ord.medication_days)
|
||||
patchUsageForm.medication_days = md > 0 ? md : Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageVisible.value = true
|
||||
nextTick(() => patchUsageFormRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function resetPatchUsageForm() {
|
||||
patchUsageForm.times_per_day = 3
|
||||
patchUsageForm.usage_days = 7
|
||||
patchUsageForm.aux_times_per_day = 3
|
||||
patchUsageForm.aux_usage_days = 7
|
||||
patchUsageForm.medication_days = undefined
|
||||
}
|
||||
|
||||
async function submitPatchUsage() {
|
||||
const form = patchUsageFormRef.value
|
||||
if (!form) return
|
||||
try {
|
||||
await form.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const ordId = detailData.value?.id
|
||||
if (!ordId) return
|
||||
patchUsageSaving.value = true
|
||||
try {
|
||||
const payload: {
|
||||
id: number
|
||||
times_per_day: number
|
||||
usage_days: number
|
||||
medication_days: number
|
||||
aux_times_per_day?: number
|
||||
aux_usage_days?: number
|
||||
} = {
|
||||
id: ordId,
|
||||
times_per_day: Number(patchUsageForm.times_per_day),
|
||||
usage_days: Number(patchUsageForm.usage_days),
|
||||
medication_days: Number(patchUsageForm.medication_days)
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
payload.aux_times_per_day = Number(patchUsageForm.aux_times_per_day)
|
||||
payload.aux_usage_days = Number(patchUsageForm.aux_usage_days)
|
||||
}
|
||||
await prescriptionOrderPatchPrescriptionUsage(payload)
|
||||
feedback.msgSuccess('保存成功')
|
||||
patchUsageVisible.value = false
|
||||
await refresh()
|
||||
emit('detail-changed')
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
patchUsageSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetAddLogForm() {
|
||||
addLogForm.summary = ''
|
||||
addLogForm.prescription_audit_status = ''
|
||||
addLogForm.payment_slip_audit_status = ''
|
||||
addLogForm.prescription_audit_remark = ''
|
||||
addLogForm.payment_slip_audit_remark = ''
|
||||
addLogFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
function openAddLogDialog() {
|
||||
if (!detailData.value?.id) return
|
||||
resetAddLogForm()
|
||||
const d = detailData.value
|
||||
addLogForm.prescription_audit_remark = String(d.prescription_audit_remark || '')
|
||||
addLogForm.payment_slip_audit_remark = String(d.payment_slip_audit_remark || '')
|
||||
addLogVisible.value = true
|
||||
}
|
||||
|
||||
async function submitAddLog() {
|
||||
if (!addLogFormRef.value || !detailData.value?.id) return
|
||||
await addLogFormRef.value.validate()
|
||||
addLogSaving.value = true
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
id: detailData.value.id,
|
||||
summary: addLogForm.summary.trim()
|
||||
}
|
||||
if (
|
||||
canSetRxAuditOnAddLog.value &&
|
||||
addLogForm.prescription_audit_status !== '' &&
|
||||
addLogForm.prescription_audit_status !== null &&
|
||||
addLogForm.prescription_audit_status !== undefined
|
||||
) {
|
||||
payload.prescription_audit_status = addLogForm.prescription_audit_status
|
||||
payload.prescription_audit_remark = addLogForm.prescription_audit_remark
|
||||
}
|
||||
if (
|
||||
canSetPayAuditOnAddLog.value &&
|
||||
addLogForm.payment_slip_audit_status !== '' &&
|
||||
addLogForm.payment_slip_audit_status !== null &&
|
||||
addLogForm.payment_slip_audit_status !== undefined
|
||||
) {
|
||||
payload.payment_slip_audit_status = addLogForm.payment_slip_audit_status
|
||||
payload.payment_slip_audit_remark = addLogForm.payment_slip_audit_remark
|
||||
}
|
||||
await prescriptionOrderAddLog(payload as any)
|
||||
feedback.msgSuccess('日志已添加')
|
||||
addLogVisible.value = false
|
||||
await refresh()
|
||||
emit('detail-changed')
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
addLogSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 未关联支付单 ───
|
||||
async function loadDetailUnlinkedPayOrders(diagnosisId: number, prescriptionOrderId: number, linkedIds: number[]) {
|
||||
if (!diagnosisId) {
|
||||
@@ -1643,8 +1266,6 @@ async function updateJdLogistics() {
|
||||
|
||||
// ─── 打开 / 刷新 ───
|
||||
async function open(id: number) {
|
||||
// 页面级同参数字典请求会取消抽屉挂载时的那次(axios 去重取消),打开时兜底重试
|
||||
void loadServicePackageOptions()
|
||||
// 显式彻底清空缓存,防止前一次弹窗的数据残留
|
||||
detailData.value = null
|
||||
detailUnlinkedPayOrders.value = []
|
||||
|
||||
@@ -135,25 +135,13 @@ export function logActionText(act: string) {
|
||||
revoke_pay_audit: '撤回支付审核',
|
||||
gancao_submit: '甘草下单',
|
||||
patch_rx_patient: '处方患者信息',
|
||||
patch_rx_usage: '服用参数',
|
||||
update_amount: '修改订单金额',
|
||||
complete: '完成订单',
|
||||
refund: '退款',
|
||||
manual_log: '手工备注',
|
||||
assign_assistant: '改派医助',
|
||||
add_pay_order: '补齐支付单',
|
||||
set_ship_mode: '发货类型'
|
||||
refund: '退款'
|
||||
}
|
||||
return m[act] || act
|
||||
}
|
||||
|
||||
/** 处方/支付单审核状态文案(0 待审核 / 1 已通过 / 2 已驳回) */
|
||||
export function auditStatusText(s: number | undefined) {
|
||||
if (s === 1) return '已通过'
|
||||
if (s === 2) return '已驳回'
|
||||
return '待审核'
|
||||
}
|
||||
|
||||
/** 支付单来源/方式:企微对外收款、付呗、快递代收等创建链路 + 支付方式回退 */
|
||||
export function formatPayOrderSource(row: { payment_method?: unknown; create_type?: unknown }) {
|
||||
const createType = String(row?.create_type || '')
|
||||
@@ -369,85 +357,3 @@ export function formatDietaryTaboo(raw: unknown): string {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 服务套餐 dict:server_order */
|
||||
export type ServicePackageOption = { name: string; value: string; status?: number }
|
||||
|
||||
export function normalizeServicePackageValue(v: unknown): string {
|
||||
return String(v ?? '').trim()
|
||||
}
|
||||
|
||||
/** 解析订单 service_package(逗号串 / 数组 / 单值数字) */
|
||||
export function parseServicePackageValues(raw: unknown): string[] {
|
||||
if (raw == null || raw === '') return []
|
||||
if (Array.isArray(raw)) {
|
||||
return raw.map(normalizeServicePackageValue).filter(Boolean)
|
||||
}
|
||||
if (typeof raw === 'string') {
|
||||
return raw.split(',').map((v) => v.trim()).filter(Boolean)
|
||||
}
|
||||
const one = normalizeServicePackageValue(raw)
|
||||
return one ? [one] : []
|
||||
}
|
||||
|
||||
export function servicePackageValueEquals(a: unknown, b: unknown): boolean {
|
||||
const sa = normalizeServicePackageValue(a)
|
||||
const sb = normalizeServicePackageValue(b)
|
||||
if (!sa || !sb) return false
|
||||
if (sa === sb) return true
|
||||
const na = Number(sa)
|
||||
const nb = Number(sb)
|
||||
return Number.isFinite(na) && Number.isFinite(nb) && na === nb
|
||||
}
|
||||
|
||||
export function normalizeServicePackageOptions(raw: unknown): ServicePackageOption[] {
|
||||
if (!Array.isArray(raw)) return []
|
||||
return raw
|
||||
.map((item: any) => ({
|
||||
name: String(item?.name ?? '').trim() || normalizeServicePackageValue(item?.value),
|
||||
value: normalizeServicePackageValue(item?.value),
|
||||
status: Number(item?.status ?? 1)
|
||||
}))
|
||||
.filter((item) => item.value !== '')
|
||||
}
|
||||
|
||||
export function findServicePackageOption(
|
||||
options: ServicePackageOption[],
|
||||
value: unknown
|
||||
): ServicePackageOption | undefined {
|
||||
const key = normalizeServicePackageValue(value)
|
||||
if (!key) return undefined
|
||||
return options.find((opt) => servicePackageValueEquals(opt.value, key))
|
||||
}
|
||||
|
||||
/** 展示用:value → 字典 name,多选用「、」连接 */
|
||||
export function formatServicePackageLabels(
|
||||
value: unknown,
|
||||
options: ServicePackageOption[],
|
||||
emptyText = '—'
|
||||
): string {
|
||||
const packages = parseServicePackageValues(value)
|
||||
if (packages.length === 0) return emptyText
|
||||
const names = packages.map((val) => {
|
||||
const option = findServicePackageOption(options, val)
|
||||
return option?.name || val
|
||||
})
|
||||
return names.join('、')
|
||||
}
|
||||
|
||||
/** 编辑下拉:字典项 + 当前已选但字典缺失的兜底项 */
|
||||
export function mergeServicePackageSelectOptions(
|
||||
options: ServicePackageOption[],
|
||||
selected: unknown[]
|
||||
): ServicePackageOption[] {
|
||||
const known = new Set(options.map((o) => o.value))
|
||||
const extras: ServicePackageOption[] = []
|
||||
for (const raw of selected) {
|
||||
const val = normalizeServicePackageValue(raw)
|
||||
if (!val || known.has(val)) continue
|
||||
const matched = findServicePackageOption(options, val)
|
||||
extras.push(matched ?? { name: val, value: val, status: 0 })
|
||||
known.add(val)
|
||||
}
|
||||
return extras.length ? [...options, ...extras] : options
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@
|
||||
:fetch-fun="prescriptionOrderExport"
|
||||
:params="prescriptionOrderExportParams"
|
||||
:page-size="pager.size"
|
||||
export-hint="导出范围与上方筛选一致(履约状态、创建时间及其他条件均会生效)。含「自媒体渠道(挂号渠道来源)」:优先取该单关联处方登记的挂号;无则诊单下同患者挂号取 id 最大的一条(与前台挂号选择的记录一致);业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源。「服务套餐」按字典 server_order 解析展示。「处方」导出主方/辅方药材明细;「主方/辅方服用方式、天数」与详情侧栏同口径(主方/辅方天数分别取处方 usage_days、辅方 aux_usage.usage_days;「天数」列为订单 medication_days)。「关联收款记录」与详情侧栏同源(已支付/已退款/待审核),每笔两行展示(摘要行+明细行),多笔空行分隔,单元格自动换行。「签收日期」与详情/业绩看板同源,仅读物流库(轨迹/签收时间):导出不再实时查快递100,速度只取决于数据库;签收时间由 `tcm:backfill-sign-time` 命令与物流自动更新定时任务落库,刚发货尚未同步的单子会暂时为空,待下次回填/定时任务刷新后显示。"
|
||||
export-hint="导出范围与上方筛选一致(履约状态、创建时间及其他条件均会生效)。含「自媒体渠道(挂号渠道来源)」:优先取该单关联处方登记的挂号;无则诊单下同患者挂号取 id 最大的一条(与前台挂号选择的记录一致);业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源。「服务套餐」按字典 server_order 解析展示。「关联收款记录」与详情侧栏同源(已支付/已退款/待审核),每笔两行展示(摘要行+明细行),多笔空行分隔,单元格自动换行。「签收日期」与详情/业绩看板同源,仅读物流库(轨迹/签收时间):导出不再实时查快递100,速度只取决于数据库;签收时间由 `tcm:backfill-sign-time` 命令与物流自动更新定时任务落库,刚发货尚未同步的单子会暂时为空,待下次回填/定时任务刷新后显示。"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -623,7 +623,6 @@
|
||||
@view-prescription="detailData && openPrescriptionView(detailData)"
|
||||
@test-gancao-preview="testGancaoPreviewFromDetail"
|
||||
@view-patient="openDiagnosisPatientDetailFromOrder"
|
||||
@detail-changed="getLists"
|
||||
>
|
||||
<template #header-extra="{ detail }">
|
||||
<div class="flex items-center gap-2 ml-4 shrink-0">
|
||||
@@ -1025,11 +1024,10 @@
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in editServicePackageSelectOptions"
|
||||
v-for="item in servicePackageOptions"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
:disabled="item.status === 0"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -2227,11 +2225,7 @@ import {
|
||||
canUpdateAmount,
|
||||
formatDietaryTaboo,
|
||||
type SlipFormulaType,
|
||||
type SlipAuxUsageForm,
|
||||
type ServicePackageOption,
|
||||
normalizeServicePackageOptions,
|
||||
parseServicePackageValues,
|
||||
mergeServicePackageSelectOptions
|
||||
type SlipAuxUsageForm
|
||||
} from './components/prescription-order-utils'
|
||||
import { useListTimeFilter } from '@/hooks/useListTimeFilter'
|
||||
import {
|
||||
@@ -2420,8 +2414,8 @@ async function submitReassign() {
|
||||
// 省市区数据
|
||||
const regionOptions = ref([])
|
||||
|
||||
// 服务套餐选项(含已停用项,便于编辑时回显历史值)
|
||||
const servicePackageOptions = ref<ServicePackageOption[]>([])
|
||||
// 服务套餐选项
|
||||
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||
|
||||
/** 筛选:开方医生、诊单医助(与列表接口 doctor_id / assistant_id 一致) */
|
||||
const doctorOptions = ref<Array<{ id: number; name: string }>>([])
|
||||
@@ -2542,7 +2536,8 @@ const loadRegionData = async () => {
|
||||
const loadServicePackageOptions = async () => {
|
||||
try {
|
||||
const data = await getDictData({ type: 'server_order' })
|
||||
servicePackageOptions.value = normalizeServicePackageOptions(data?.server_order)
|
||||
servicePackageOptions.value = (data?.server_order || []).filter((item: any) => item.status !== 0)
|
||||
console.log('服务套餐选项已加载:', servicePackageOptions.value.length)
|
||||
} catch (error) {
|
||||
console.error('加载服务套餐选项失败:', error)
|
||||
servicePackageOptions.value = []
|
||||
@@ -3200,17 +3195,10 @@ async function onDetailShipModeChange(mode: string | number | boolean | undefine
|
||||
}
|
||||
}
|
||||
|
||||
function canAddPayOrderRow(row: {
|
||||
fulfillment_status?: number
|
||||
amount?: number | string
|
||||
linked_pay_paid_total?: number | string
|
||||
}) {
|
||||
// 已发货(5) / 已签收(6) 状态可补齐支付单;总金额已付清则不允许
|
||||
function canAddPayOrderRow(row: { fulfillment_status?: number }) {
|
||||
// 已发货(5) / 已签收(6) 状态可补齐支付单
|
||||
const fs = Number(row.fulfillment_status)
|
||||
if (fs !== 5 && fs !== 6) return false
|
||||
const orderAmount = Math.round((Number(row.amount) || 0) * 100) / 100
|
||||
const paidTotal = Math.round((Number(row.linked_pay_paid_total) || 0) * 100) / 100
|
||||
return paidTotal < orderAmount
|
||||
return fs === 5 || fs === 6
|
||||
}
|
||||
|
||||
function canCompleteRow(row: { fulfillment_status?: number; payment_slip_audit_status?: number }) {
|
||||
@@ -3461,11 +3449,6 @@ const editForm = reactive({
|
||||
diagnosis_creator_dept_path: ''
|
||||
})
|
||||
|
||||
/** 编辑弹窗下拉:字典项 + 当前已选但字典中缺失的兜底项 */
|
||||
const editServicePackageSelectOptions = computed(() =>
|
||||
mergeServicePackageSelectOptions(servicePackageOptions.value, editForm.service_package)
|
||||
)
|
||||
|
||||
/** 诊单创建人部门:拆成面包屑。多部门为「;」分隔;路径内为「 / 」含父级(与后端 buildDeptPath 一致) */
|
||||
const editDiagnosisCreatorDeptBreadcrumbs = computed(() => {
|
||||
const raw = String(editForm.diagnosis_creator_dept_path || '').trim()
|
||||
@@ -3698,7 +3681,18 @@ async function openEdit(row: {
|
||||
editForm.dose_unit = d.dose_unit || '剂'
|
||||
editForm.prev_staff = d.prev_staff || ''
|
||||
editForm.service_channel = d.service_channel || ''
|
||||
editForm.service_package = parseServicePackageValues(d.service_package)
|
||||
// 处理服务套餐:如果是字符串,转换为数组
|
||||
if (d.service_package) {
|
||||
if (Array.isArray(d.service_package)) {
|
||||
editForm.service_package = d.service_package
|
||||
} else if (typeof d.service_package === 'string') {
|
||||
editForm.service_package = d.service_package.split(',').filter(v => v.trim() !== '')
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
editForm.express_company = String(d.express_company || 'auto') || 'auto'
|
||||
editForm.tracking_number = d.tracking_number || ''
|
||||
editForm.fee_type = Number(d.fee_type) || 3
|
||||
@@ -4515,18 +4509,7 @@ async function loadAddPayOrderAvailable(diagnosisId: number, currentLinkedIds: n
|
||||
}
|
||||
}
|
||||
|
||||
function openAddPayOrder(row: {
|
||||
id: number
|
||||
diagnosis_id?: number
|
||||
pay_order_ids?: number[]
|
||||
fulfillment_status?: number
|
||||
amount?: number | string
|
||||
linked_pay_paid_total?: number | string
|
||||
}) {
|
||||
if (!canAddPayOrderRow(row)) {
|
||||
feedback.msgWarning('订单总金额与已付金额一致,无需补齐支付单')
|
||||
return
|
||||
}
|
||||
function openAddPayOrder(row: { id: number; diagnosis_id?: number; pay_order_ids?: number[] }) {
|
||||
addPayOrderRowId.value = row.id
|
||||
addPayOrderForm.add_mode = 'create'
|
||||
addPayOrderForm.order_type = 3
|
||||
|
||||
@@ -858,94 +858,31 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用量">
|
||||
<div class="flex flex-col gap-1 text-sm leading-relaxed">
|
||||
<div>
|
||||
<span v-if="detailHasAuxHerbs" class="text-gray-500 mr-1">主方:</span>
|
||||
<template v-if="detailPrescription.dosage_amount">
|
||||
{{ detailPrescription.dosage_amount }}{{ detailPrescription.dosage_unit || 'g' }}
|
||||
<template v-if="detailPrescription.prescription_type === '浓缩水丸'">
|
||||
· {{ Number(detailPrescription.dosage_bag_count) > 0 ? Number(detailPrescription.dosage_bag_count) : 1 }}袋
|
||||
</template>
|
||||
<span v-if="detailPrescription.prescription_type === '饮片' && detailPrescription.need_decoction !== null" class="ml-2 text-gray-500">
|
||||
({{ detailPrescription.need_decoction ? '代煎' : '不代煎' }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</div>
|
||||
<div v-if="detailHasAuxHerbs && detailAuxUsage">
|
||||
<span class="text-gray-500 mr-1">辅方:</span>
|
||||
<template v-if="detailAuxUsage.dosage_amount != null && detailAuxUsage.dosage_amount !== 0">
|
||||
{{ detailAuxUsage.dosage_amount }}{{ detailPrescription.dosage_unit || 'g' }}
|
||||
<template v-if="detailPrescription.prescription_type === '浓缩水丸'">
|
||||
· {{ Number(detailAuxUsage.dosage_bag_count) > 0 ? Number(detailAuxUsage.dosage_bag_count) : 1 }}袋
|
||||
</template>
|
||||
<span v-if="detailPrescription.prescription_type === '饮片'" class="ml-2 text-gray-500">
|
||||
({{ detailAuxUsage.need_decoction ? '代煎' : '不代煎' }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="flex items-center gap-1 flex-wrap">
|
||||
<span>服用方式</span>
|
||||
<el-button
|
||||
v-if="
|
||||
detailData.prescription_id &&
|
||||
detailPrescription &&
|
||||
!String(detailData.prescription_detail_error || '').trim()
|
||||
"
|
||||
v-perms="['tcm.prescriptionOrder/patchPrescriptionUsage']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
@click="openPatchUsageDialog"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</div>
|
||||
<template v-if="detailPrescription.dosage_amount">
|
||||
{{ detailPrescription.dosage_amount }}{{ detailPrescription.dosage_unit || 'g' }}
|
||||
<template v-if="detailPrescription.prescription_type === '浓缩水丸'">
|
||||
· {{ Number(detailPrescription.dosage_bag_count) > 0 ? Number(detailPrescription.dosage_bag_count) : 1 }}袋
|
||||
</template>
|
||||
<span v-if="detailPrescription.prescription_type === '饮片' && detailPrescription.need_decoction !== null" class="ml-2 text-gray-500">
|
||||
({{ detailPrescription.need_decoction ? '代煎' : '不代煎' }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="服用方式">
|
||||
<div class="flex flex-col gap-1.5 text-sm leading-relaxed">
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div>
|
||||
<span class="text-gray-500 mr-1">主方:</span>
|
||||
每天
|
||||
{{ detailPrescription.times_per_day ? detailPrescription.times_per_day + ' 次' : '— 次' }}
|
||||
· 处方开立
|
||||
{{
|
||||
detailPrescription.usage_days != null && detailPrescription.usage_days !== ''
|
||||
? detailPrescription.usage_days + ' 天'
|
||||
: '— 天'
|
||||
}}
|
||||
</div>
|
||||
<div v-if="detailAuxUsage">
|
||||
<span class="text-gray-500 mr-1">辅方:</span>
|
||||
每天
|
||||
{{ detailAuxUsage.times_per_day ? detailAuxUsage.times_per_day + ' 次' : '— 次' }}
|
||||
· 处方开立
|
||||
{{
|
||||
detailAuxUsage.usage_days != null && Number(detailAuxUsage.usage_days) > 0
|
||||
? detailAuxUsage.usage_days + ' 天'
|
||||
: '— 天'
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<span class="text-gray-500">每天次数:</span>
|
||||
{{ detailPrescription.times_per_day ? detailPrescription.times_per_day + ' 次' : '—' }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">处方开立:</span>
|
||||
{{
|
||||
detailPrescription.usage_days != null && detailPrescription.usage_days !== ''
|
||||
? detailPrescription.usage_days + ' 天'
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<span class="text-gray-500">每天次数:</span>
|
||||
{{ detailPrescription.times_per_day ? detailPrescription.times_per_day + ' 次' : '—' }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">处方开立:</span>
|
||||
{{
|
||||
detailPrescription.usage_days != null && detailPrescription.usage_days !== ''
|
||||
? detailPrescription.usage_days + ' 天'
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">订单设置:</span>
|
||||
{{
|
||||
@@ -1202,7 +1139,7 @@
|
||||
<el-descriptions-item label="上次医护">{{ detailData.prev_staff || '—' }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="服务渠道">{{ detailData.service_channel || '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="服务套餐">{{ detailServicePackageText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="服务套餐">{{ formatServicePackage(detailData.service_package) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="费用类别">{{ feeTypeText(detailData.fee_type) }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="detailData.internal_cost != null && detailData.internal_cost !== ''" label="内部成本">
|
||||
@@ -1588,11 +1525,10 @@
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in editServicePackageSelectOptions"
|
||||
v-for="item in servicePackageOptions"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
:disabled="item.status === 0"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -2215,93 +2151,6 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改服用参数:主方 / 辅方 / 订单设置 -->
|
||||
<el-dialog
|
||||
v-model="patchUsageVisible"
|
||||
title="修改服用参数"
|
||||
width="92%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="po-h5-dialog"
|
||||
@closed="resetPatchUsageForm"
|
||||
>
|
||||
<el-form
|
||||
ref="patchUsageFormRef"
|
||||
:model="patchUsageForm"
|
||||
:rules="patchUsageRules"
|
||||
label-width="96px"
|
||||
>
|
||||
<div v-if="detailHasAuxHerbs" class="text-xs font-medium text-gray-500 mb-3">主方</div>
|
||||
<el-form-item label="每天次数" prop="times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">辅方</div>
|
||||
<el-form-item label="每天次数" prop="aux_times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="aux_usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">订单设置</div>
|
||||
<el-form-item label="服用天数" prop="medication_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.medication_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="patchUsageVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="patchUsageSaving" @click="submitPatchUsage">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 处方详情查看(处方单样式) -->
|
||||
<el-drawer
|
||||
v-model="prescriptionViewVisible"
|
||||
@@ -2716,7 +2565,6 @@ import {
|
||||
prescriptionOrderRevokeRxAudit,
|
||||
prescriptionOrderRevokePayAudit,
|
||||
prescriptionOrderPatchPrescriptionPatient,
|
||||
prescriptionOrderPatchPrescriptionUsage,
|
||||
prescriptionOrderLinkPayOrder,
|
||||
prescriptionOrderRequestCompletion,
|
||||
prescriptionOrderSubmitGancaoRecipel,
|
||||
@@ -2725,16 +2573,7 @@ import {
|
||||
getDoctors,
|
||||
getAssistants
|
||||
} from '@/api/tcm'
|
||||
import {
|
||||
formatDietaryTaboo,
|
||||
type ServicePackageOption,
|
||||
normalizeServicePackageOptions,
|
||||
parseServicePackageValues,
|
||||
mergeServicePackageSelectOptions,
|
||||
formatServicePackageLabels,
|
||||
normalizeSlipAuxUsageForm,
|
||||
prescriptionHasAuxFormula
|
||||
} from './components/prescription-order-utils'
|
||||
import { formatDietaryTaboo } from './components/prescription-order-utils'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { jsPDF } from 'jspdf'
|
||||
import { getDictData } from '@/api/app'
|
||||
@@ -2830,7 +2669,7 @@ const canViewFinanceFields = () => {
|
||||
const regionOptions = ref([])
|
||||
|
||||
// 服务套餐选项
|
||||
const servicePackageOptions = ref<ServicePackageOption[]>([])
|
||||
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||
|
||||
/** 筛选:开方医生、诊单医助(与列表接口 doctor_id / assistant_id 一致) */
|
||||
const doctorOptions = ref<Array<{ id: number; name: string }>>([])
|
||||
@@ -2951,7 +2790,8 @@ const loadRegionData = async () => {
|
||||
const loadServicePackageOptions = async () => {
|
||||
try {
|
||||
const data = await getDictData({ type: 'server_order' })
|
||||
servicePackageOptions.value = normalizeServicePackageOptions(data?.server_order)
|
||||
servicePackageOptions.value = (data?.server_order || []).filter((item: any) => item.status !== 0)
|
||||
console.log('服务套餐选项已加载:', servicePackageOptions.value.length)
|
||||
} catch (error) {
|
||||
console.error('加载服务套餐选项失败:', error)
|
||||
servicePackageOptions.value = []
|
||||
@@ -3359,6 +3199,28 @@ function feeTypeText(t: number | undefined) {
|
||||
return m[Number(t)] ?? '—'
|
||||
}
|
||||
|
||||
// 格式化服务套餐显示
|
||||
function formatServicePackage(value: any): string {
|
||||
if (!value) return '—'
|
||||
|
||||
let packages: string[] = []
|
||||
if (Array.isArray(value)) {
|
||||
packages = value
|
||||
} else if (typeof value === 'string') {
|
||||
packages = value.split(',').filter(v => v.trim() !== '')
|
||||
}
|
||||
|
||||
if (packages.length === 0) return '—'
|
||||
|
||||
// 将值转换为名称
|
||||
const names = packages.map(val => {
|
||||
const option = servicePackageOptions.value.find(opt => opt.value === val)
|
||||
return option ? option.name : val
|
||||
})
|
||||
|
||||
return names.join('、')
|
||||
}
|
||||
|
||||
function auditStatusText(s: number | undefined) {
|
||||
if (s === 1) return '已通过'
|
||||
if (s === 2) return '已驳回'
|
||||
@@ -3611,10 +3473,6 @@ const detailVisible = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detailData = ref<Record<string, any> | null>(null)
|
||||
|
||||
const detailServicePackageText = computed(() =>
|
||||
formatServicePackageLabels(detailData.value?.service_package, servicePackageOptions.value)
|
||||
)
|
||||
|
||||
// --- 订单可视化审批履约流程逻辑 ---
|
||||
const workflowActiveStep = computed(() => {
|
||||
if (!detailData.value) return 0
|
||||
@@ -3771,16 +3629,6 @@ const detailLinkedAppointmentResolvedFromTag = computed(() => {
|
||||
|
||||
const detailRxHerbs = computed(() => normalizeSlipHerbs(detailPrescription.value?.herbs))
|
||||
|
||||
const detailHasAuxHerbs = computed(() => prescriptionHasAuxFormula(detailPrescription.value as any))
|
||||
|
||||
const detailAuxUsage = computed(() => {
|
||||
const rx = detailPrescription.value as any
|
||||
if (!rx || !prescriptionHasAuxFormula(rx)) return null
|
||||
const raw = rx.aux_usage
|
||||
if (raw == null || raw === '' || (Array.isArray(raw) && raw.length === 0)) return null
|
||||
return normalizeSlipAuxUsageForm(raw, rx.prescription_type || '浓缩水丸')
|
||||
})
|
||||
|
||||
/** false=无权限;true/缺省兼容旧接口(旧版未下发该字段时仍展示药材) */
|
||||
const detailHerbsVisible = computed(() => detailData.value?.prescription_detail_herbs_visible !== false)
|
||||
|
||||
@@ -4007,101 +3855,6 @@ const patchRxPatientRules: FormRules = {
|
||||
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const patchUsageVisible = ref(false)
|
||||
const patchUsageSaving = ref(false)
|
||||
const patchUsageFormRef = ref<FormInstance>()
|
||||
const patchUsageForm = reactive({
|
||||
times_per_day: 3 as number | undefined,
|
||||
usage_days: 7 as number | undefined,
|
||||
aux_times_per_day: 3 as number | undefined,
|
||||
aux_usage_days: 7 as number | undefined,
|
||||
medication_days: undefined as number | undefined
|
||||
})
|
||||
const patchUsageRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {
|
||||
times_per_day: [{ required: true, message: '请填写主方每天次数', trigger: 'change' }],
|
||||
usage_days: [{ required: true, message: '请填写主方开立天数', trigger: 'change' }],
|
||||
medication_days: [{ required: true, message: '请填写订单服用天数', trigger: 'change' }]
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
rules.aux_times_per_day = [{ required: true, message: '请填写辅方每天次数', trigger: 'change' }]
|
||||
rules.aux_usage_days = [{ required: true, message: '请填写辅方开立天数', trigger: 'change' }]
|
||||
}
|
||||
return rules
|
||||
})
|
||||
|
||||
function openPatchUsageDialog() {
|
||||
const rx = detailPrescription.value
|
||||
const ord = detailData.value
|
||||
if (!rx || !ord?.id || !ord.prescription_id) {
|
||||
feedback.msgWarning('无处方数据')
|
||||
return
|
||||
}
|
||||
const aux = detailAuxUsage.value
|
||||
patchUsageForm.times_per_day =
|
||||
Number(rx.times_per_day) > 0 ? Number(rx.times_per_day) : 3
|
||||
patchUsageForm.usage_days =
|
||||
Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageForm.aux_times_per_day =
|
||||
aux && Number(aux.times_per_day) > 0 ? Number(aux.times_per_day) : 3
|
||||
patchUsageForm.aux_usage_days =
|
||||
aux && Number(aux.usage_days) > 0 ? Number(aux.usage_days) : 7
|
||||
const md = Number(ord.medication_days)
|
||||
patchUsageForm.medication_days = md > 0 ? md : Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageVisible.value = true
|
||||
nextTick(() => patchUsageFormRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function resetPatchUsageForm() {
|
||||
patchUsageForm.times_per_day = 3
|
||||
patchUsageForm.usage_days = 7
|
||||
patchUsageForm.aux_times_per_day = 3
|
||||
patchUsageForm.aux_usage_days = 7
|
||||
patchUsageForm.medication_days = undefined
|
||||
}
|
||||
|
||||
async function submitPatchUsage() {
|
||||
const form = patchUsageFormRef.value
|
||||
if (!form) return
|
||||
try {
|
||||
await form.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const ordId = detailData.value?.id
|
||||
if (!ordId) return
|
||||
patchUsageSaving.value = true
|
||||
try {
|
||||
const payload: {
|
||||
id: number
|
||||
times_per_day: number
|
||||
usage_days: number
|
||||
medication_days: number
|
||||
aux_times_per_day?: number
|
||||
aux_usage_days?: number
|
||||
} = {
|
||||
id: ordId,
|
||||
times_per_day: Number(patchUsageForm.times_per_day),
|
||||
usage_days: Number(patchUsageForm.usage_days),
|
||||
medication_days: Number(patchUsageForm.medication_days)
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
payload.aux_times_per_day = Number(patchUsageForm.aux_times_per_day)
|
||||
payload.aux_usage_days = Number(patchUsageForm.aux_usage_days)
|
||||
}
|
||||
await prescriptionOrderPatchPrescriptionUsage(payload)
|
||||
feedback.msgSuccess('保存成功')
|
||||
patchUsageVisible.value = false
|
||||
await refreshCurrentPrescriptionOrderDetail()
|
||||
await fetchLogs(ordId)
|
||||
getLists()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
patchUsageSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openPatchRxPatientDialog() {
|
||||
const rx = detailPrescription.value
|
||||
const ord = detailData.value
|
||||
@@ -4307,10 +4060,6 @@ const editForm = reactive({
|
||||
diagnosis_creator_dept_path: ''
|
||||
})
|
||||
|
||||
const editServicePackageSelectOptions = computed(() =>
|
||||
mergeServicePackageSelectOptions(servicePackageOptions.value, editForm.service_package)
|
||||
)
|
||||
|
||||
/** 诊单创建人部门:拆成面包屑。多部门为「;」分隔;路径内为「 / 」含父级(与后端 buildDeptPath 一致) */
|
||||
const editDiagnosisCreatorDeptBreadcrumbs = computed(() => {
|
||||
const raw = String(editForm.diagnosis_creator_dept_path || '').trim()
|
||||
@@ -4561,7 +4310,18 @@ async function openEdit(row: {
|
||||
editForm.dose_unit = d.dose_unit || '剂'
|
||||
editForm.prev_staff = d.prev_staff || ''
|
||||
editForm.service_channel = d.service_channel || ''
|
||||
editForm.service_package = parseServicePackageValues(d.service_package)
|
||||
// 处理服务套餐:如果是字符串,转换为数组
|
||||
if (d.service_package) {
|
||||
if (Array.isArray(d.service_package)) {
|
||||
editForm.service_package = d.service_package
|
||||
} else if (typeof d.service_package === 'string') {
|
||||
editForm.service_package = d.service_package.split(',').filter(v => v.trim() !== '')
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
editForm.express_company = String(d.express_company || 'auto') || 'auto'
|
||||
editForm.tracking_number = d.tracking_number || ''
|
||||
editForm.fee_type = Number(d.fee_type) || 3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="code-generation">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<el-form class="mb-[-16px]" :model="formData" inline>
|
||||
<el-form-item class="w-[280px]" label="表名称">
|
||||
<el-input v-model="formData.table_name" clearable @keyup.enter="resetPage" />
|
||||
@@ -13,8 +13,8 @@
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never" v-loading="pager.loading">
|
||||
</admin-page-filter-panel>
|
||||
<admin-page-data-panel>
|
||||
<div class="flex">
|
||||
<data-table
|
||||
v-perms="['tools.generator/selectTable']"
|
||||
@@ -126,10 +126,10 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<code-preview
|
||||
v-if="previewState.show"
|
||||
v-model="previewState.show"
|
||||
|
||||
@@ -866,7 +866,7 @@ onUnmounted(() => {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: linear-gradient(145deg, #6366f1 0%, #8b5cf6 100%);
|
||||
background: linear-gradient(145deg, #0d9488 0%, #0891b2 100%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="error">
|
||||
<div>
|
||||
<div class="error-panel">
|
||||
<slot name="content">
|
||||
<div class="error-code">{{ code }}</div>
|
||||
</slot>
|
||||
<div class="text-lg text-tx-secondary mt-7 mb-7">{{ title }}</div>
|
||||
<el-button v-if="showBtn" type="primary" @click="router.go(-1)">
|
||||
<div class="error-title">{{ title }}</div>
|
||||
<el-button v-if="showBtn" type="primary" size="large" @click="router.go(-1)">
|
||||
{{ second }} 秒后返回上一页
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -43,16 +43,38 @@ onUnmounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
.error {
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.error-code {
|
||||
@apply text-primary;
|
||||
font-size: 150px;
|
||||
}
|
||||
.el-button {
|
||||
width: 176px;
|
||||
}
|
||||
background: var(--el-bg-color-page);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.error-panel {
|
||||
width: min(480px, 100%);
|
||||
padding: 48px 32px;
|
||||
border-radius: var(--admin-radius-xl);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
background: var(--el-bg-color);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
}
|
||||
|
||||
.error-code {
|
||||
@apply text-primary;
|
||||
font-size: 96px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
@apply text-tx-secondary;
|
||||
font-size: 18px;
|
||||
margin: 20px 0 28px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
min-width: 176px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="fans-management">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-form class="ls-form" :model="formData" inline>
|
||||
<el-form-item class="w-[280px]" label="姓名">
|
||||
<el-input
|
||||
@@ -36,14 +36,14 @@
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="mb-4">
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="handleAdd">新增粉丝</el-button>
|
||||
</div>
|
||||
<el-table :data="pager.lists" size="large" v-loading="pager.loading">
|
||||
</template>
|
||||
<el-table :data="pager.lists" size="large">
|
||||
<el-table-column label="ID" prop="id" width="70" />
|
||||
<el-table-column label="姓名" prop="name" min-width="100" />
|
||||
<el-table-column label="手机号" prop="phone" min-width="130" />
|
||||
@@ -81,10 +81,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex mt-4 justify-end">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
|
||||
<!-- 新增/编辑粉丝弹窗 -->
|
||||
<el-dialog
|
||||
|
||||
@@ -3704,7 +3704,7 @@ function getYejiDeptComboOption(tb: YejiTable) {
|
||||
const cats = rows.map(r =>
|
||||
r.dept_name.length > 10 ? `${r.dept_name.slice(0, 10)}…` : r.dept_name
|
||||
)
|
||||
const palette = ['#64748b', '#38bdf8', '#6366f1']
|
||||
const palette = ['#64748b', '#38bdf8', '#0d9488']
|
||||
const series: any[] = [
|
||||
{
|
||||
name: '进线',
|
||||
@@ -4029,8 +4029,8 @@ onMounted(async () => {
|
||||
|
||||
.yeji-page {
|
||||
min-width: 0;
|
||||
--yj-brand: #6366f1;
|
||||
--yj-brand-soft: #eef2ff;
|
||||
--yj-brand: #0d9488;
|
||||
--yj-brand-soft: #f0fdfa;
|
||||
--yj-teal: #0ea5e9;
|
||||
--yj-teal-soft: #f0f9ff;
|
||||
--yj-accent: #f43f5e;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:用户账户变动记录"
|
||||
@@ -38,9 +38,9 @@
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
</admin-page-filter-panel>
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<el-table size="large" :data="pager.lists">
|
||||
<el-table-column label="用户账号" prop="account" min-width="100" />
|
||||
<el-table-column label="用户昵称" min-width="160">
|
||||
<template #default="{ row }">
|
||||
@@ -71,10 +71,10 @@
|
||||
<el-table-column label="来源单号" prop="source_sn" min-width="100" />
|
||||
<el-table-column label="记录时间" prop="create_time" min-width="120" />
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="balanceDetail">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:用户充值记录"
|
||||
@@ -54,9 +54,9 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
</admin-page-filter-panel>
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<el-table size="large" :data="pager.lists">
|
||||
<el-table-column label="用户信息" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
@@ -104,10 +104,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="rechargeRecord">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-form ref="formRef" class="mb-[-16px] mt-[16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="退款单号">
|
||||
<el-input
|
||||
@@ -69,8 +69,8 @@
|
||||
/> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
</admin-page-filter-panel>
|
||||
<admin-page-data-panel>
|
||||
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
|
||||
<el-tab-pane
|
||||
v-for="(item, index) in tabLists"
|
||||
@@ -78,7 +78,7 @@
|
||||
:name="index"
|
||||
:key="index"
|
||||
>
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
<el-table size="large" :data="pager.lists">
|
||||
<el-table-column label="退款单号" prop="sn" min-width="190" />
|
||||
<el-table-column label="用户信息" min-width="160">
|
||||
<template #default="{ row }">
|
||||
@@ -140,10 +140,10 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<refund-log v-model="showRefundLog" :refund-id="selectRefundId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:平台配置在各个场景下的通知发送方式和内容模板"
|
||||
:closable="false"
|
||||
show-icon
|
||||
></el-alert>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
</admin-page-filter-panel>
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<el-tabs v-model="tabsActive" @tab-change="getLists">
|
||||
<el-tab-pane
|
||||
v-for="(item, index) in tabsMap"
|
||||
@@ -18,7 +18,7 @@
|
||||
lazy
|
||||
></el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-table size="large" :data="pager.lists" v-loading="pager.loading">
|
||||
<el-table size="large" :data="pager.lists" >
|
||||
<el-table-column label="通知场景" prop="scene_name" min-width="120" />
|
||||
<el-table-column label="通知类型" prop="type_desc" min-width="160" />
|
||||
<el-table-column label="短信通知" min-width="80">
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</admin-page-data-panel>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="notice">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never" v-loading="state.loading">
|
||||
<admin-page-data-panel v-loading="state.loading">
|
||||
<el-table size="large" :data="state.lists">
|
||||
<el-table-column label="短信渠道" prop="name" min-width="120" />
|
||||
<el-table-column label="状态" min-width="120">
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup ref="editRef" @success="getLists" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,13 +33,11 @@ import EditPopup from './edit.vue'
|
||||
|
||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||
|
||||
// 列表数据
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
lists: []
|
||||
})
|
||||
|
||||
// 获取存储引擎列表数据
|
||||
const getLists = async () => {
|
||||
try {
|
||||
state.loading = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- 订单列表 -->
|
||||
<template>
|
||||
<div class="order-list">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<!-- 搜索表单 -->
|
||||
<el-form class="ls-form" :model="queryParams" inline>
|
||||
<el-form-item class="w-[280px]" label="订单号">
|
||||
@@ -106,10 +106,10 @@
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<!-- Tab 筛选 + 今日收益 -->
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<!-- Tab 筛选 + 今日收益 + 数据表格 -->
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<el-tabs v-model="patientAssociationTab" @tab-change="handleTabChange">
|
||||
<el-tab-pane label="全部" name="" />
|
||||
@@ -122,10 +122,7 @@
|
||||
<span class="text-gray-400">({{ todayRevenue.count }} 笔)</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div v-perms="['order.order/zhipai']" class="mb-3 flex items-center gap-3">
|
||||
<el-button type="primary" :disabled="!selectedOrderIds.length" @click="openAssignDialog">
|
||||
将创建人指给医助
|
||||
@@ -136,7 +133,6 @@
|
||||
</div>
|
||||
<el-table
|
||||
ref="orderTableRef"
|
||||
v-loading="pager.loading"
|
||||
:data="pager.lists"
|
||||
row-key="id"
|
||||
size="large"
|
||||
@@ -283,10 +279,10 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="department">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="部门名称" prop="name">
|
||||
<el-input
|
||||
@@ -22,22 +22,21 @@
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button v-perms="['dept.dept/add']" type="primary" @click="handleAdd()">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
<el-button @click="handleExpand"> 展开/折叠 </el-button>
|
||||
</div>
|
||||
<el-button @click="handleExpand">展开/折叠</el-button>
|
||||
</template>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
class="mt-4"
|
||||
size="large"
|
||||
v-loading="loading"
|
||||
:data="lists"
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
@@ -68,7 +67,6 @@
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="排序" prop="sort" min-width="100" />
|
||||
<el-table-column label="更新时间" prop="update_time" min-width="180" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
@@ -101,7 +99,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -120,14 +118,18 @@ let isExpand = false
|
||||
const loading = ref(false)
|
||||
const lists = ref<any[]>([])
|
||||
const queryParams = reactive({
|
||||
status: '',
|
||||
name: ''
|
||||
name: '',
|
||||
status: ''
|
||||
})
|
||||
const showEdit = ref(false)
|
||||
|
||||
const getLists = async () => {
|
||||
loading.value = true
|
||||
lists.value = await deptLists(queryParams)
|
||||
loading.value = false
|
||||
try {
|
||||
lists.value = await deptLists(queryParams)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetParams = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="post-lists">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="岗位编码">
|
||||
<el-input
|
||||
@@ -31,17 +31,18 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button v-perms="['dept.jobs/add']" type="primary" @click="handleAdd()">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table class="mt-4" size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
</template>
|
||||
<el-table size="large" :data="pager.lists">
|
||||
<el-table-column label="岗位编码" prop="code" min-width="100" />
|
||||
<el-table-column label="岗位名称" prop="name" min-width="100" />
|
||||
<el-table-column label="排序" prop="sort" min-width="100" />
|
||||
@@ -75,10 +76,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1275,7 +1275,7 @@ onUnmounted(() => {
|
||||
|
||||
.float-action.edit {
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
||||
background: linear-gradient(135deg, #0d9488 0%, #0f766e 100%);
|
||||
}
|
||||
|
||||
.float-action.edit:hover,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="admin">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<el-form class="mb-[-16px]" :model="formData" inline>
|
||||
<el-form-item class="w-[280px]" label="管理员账号">
|
||||
<el-input
|
||||
@@ -40,77 +40,78 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card v-loading="pager.loading" class="mt-4 !border-none" shadow="never">
|
||||
<el-button v-perms="['auth.admin/add']" type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
<div class="mt-4">
|
||||
<el-table :data="pager.lists" size="large">
|
||||
<el-table-column label="ID" prop="id" min-width="60" />>
|
||||
<el-table-column label="头像" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-avatar :size="50" :src="row.avatar"></el-avatar>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账号" prop="account" min-width="100" />
|
||||
<el-table-column label="名称" prop="name" min-width="100" />
|
||||
<el-table-column
|
||||
label="角色"
|
||||
prop="role_name"
|
||||
min-width="100"
|
||||
show-tooltip-when-overflow
|
||||
/>
|
||||
<el-table-column
|
||||
label="部门"
|
||||
prop="dept_name"
|
||||
min-width="100"
|
||||
show-tooltip-when-overflow
|
||||
/>
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="180" />
|
||||
<el-table-column label="最近登录时间" prop="login_time" min-width="180" />
|
||||
<el-table-column label="最近登录IP" prop="login_ip" min-width="120" />
|
||||
<el-table-column label="状态" min-width="100" v-perms="['auth.admin/edit']">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-if="row.root != 1"
|
||||
v-model="row.disable"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="changeStatus(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-perms="['auth.admin/edit']"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.root != 1"
|
||||
v-perms="['auth.admin/delete']"
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="flex mt-4 justify-end">
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button v-perms="['auth.admin/add']" type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
<el-table :data="pager.lists" size="large">
|
||||
<el-table-column label="ID" prop="id" min-width="60" />
|
||||
<el-table-column label="头像" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-avatar :size="50" :src="row.avatar"></el-avatar>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账号" prop="account" min-width="100" />
|
||||
<el-table-column label="名称" prop="name" min-width="100" />
|
||||
<el-table-column
|
||||
label="角色"
|
||||
prop="role_name"
|
||||
min-width="100"
|
||||
show-tooltip-when-overflow
|
||||
/>
|
||||
<el-table-column
|
||||
label="部门"
|
||||
prop="dept_name"
|
||||
min-width="100"
|
||||
show-tooltip-when-overflow
|
||||
/>
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="180" />
|
||||
<el-table-column label="最近登录时间" prop="login_time" min-width="180" />
|
||||
<el-table-column label="最近登录IP" prop="login_ip" min-width="120" />
|
||||
<el-table-column label="状态" min-width="100" v-perms="['auth.admin/edit']">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-if="row.root != 1"
|
||||
v-model="row.disable"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="changeStatus(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-perms="['auth.admin/edit']"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.root != 1"
|
||||
v-perms="['auth.admin/delete']"
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -125,7 +126,6 @@ import feedback from '@/utils/feedback'
|
||||
import EditPopup from './edit.vue'
|
||||
|
||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
account: '',
|
||||
name: '',
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<template>
|
||||
<div class="menu-lists">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<div>
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button v-perms="['auth.menu/add']" type="primary" @click="handleAdd()">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
<el-button @click="handleExpand"> 展开/折叠 </el-button>
|
||||
</div>
|
||||
<el-button @click="handleExpand">展开/折叠</el-button>
|
||||
</template>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
class="mt-4"
|
||||
size="large"
|
||||
v-loading="pager.loading"
|
||||
:data="pager.lists"
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
@@ -87,7 +85,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,68 +1,64 @@
|
||||
<template>
|
||||
<div class="role-lists">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<div>
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button v-perms="['auth.role/add']" type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-4" v-loading="pager.loading">
|
||||
<div>
|
||||
<el-table :data="pager.lists" size="large">
|
||||
<el-table-column prop="id" label="ID" min-width="100" />
|
||||
<el-table-column prop="name" label="名称" min-width="150" />
|
||||
<el-table-column
|
||||
prop="desc"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="sort" label="排序" min-width="100" />
|
||||
<el-table-column label="数据范围" min-width="140">
|
||||
<template #default="{ row }">
|
||||
{{ dataScopeLabel(row.data_scope) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="num" label="管理员人数" min-width="100" />
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-perms="['auth.role/edit']"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-perms="['auth.role/edit']"
|
||||
@click="handleAuth(row)"
|
||||
>
|
||||
分配权限
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['auth.role/delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
<el-table :data="pager.lists" size="large">
|
||||
<el-table-column prop="id" label="ID" min-width="100" />
|
||||
<el-table-column prop="name" label="名称" min-width="150" />
|
||||
<el-table-column
|
||||
prop="desc"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="sort" label="排序" min-width="100" />
|
||||
<el-table-column label="数据范围" min-width="140">
|
||||
<template #default="{ row }">
|
||||
{{ dataScopeLabel(row.data_scope) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="num" label="管理员人数" min-width="100" />
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-perms="['auth.role/edit']"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-perms="['auth.role/edit']"
|
||||
@click="handleAuth(row)"
|
||||
>
|
||||
分配权限
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['auth.role/delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
<auth-popup v-if="showAuth" ref="authRef" @success="getLists" @close="showAuth = false" />
|
||||
</div>
|
||||
@@ -115,7 +111,6 @@ const dataScopeLabel = (scope: number | string | null | undefined) => {
|
||||
)
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await roleDelete({ id })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="dict-type">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<el-page-header class="mb-4" content="数据管理" @back="$router.back()" />
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" inline>
|
||||
<el-form-item class="w-[280px]" label="字典名称">
|
||||
@@ -28,9 +28,10 @@
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_data/add']"
|
||||
type="primary"
|
||||
@@ -52,58 +53,54 @@
|
||||
</template>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-4" v-loading="pager.loading">
|
||||
<div>
|
||||
<el-table
|
||||
:data="pager.lists"
|
||||
size="large"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="ID" prop="id" />
|
||||
<el-table-column label="数据名称" prop="name" min-width="120" />
|
||||
<el-table-column label="数据值" prop="value" min-width="120" />
|
||||
<el-table-column label="状态">
|
||||
<template v-slot="{ row }">
|
||||
<el-tag v-if="row.status == 1">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
prop="remark"
|
||||
min-width="120"
|
||||
show-tooltip-when-overflow
|
||||
/>
|
||||
<el-table-column label="排序" prop="sort" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_data/edit']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_data/delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
<el-table
|
||||
:data="pager.lists"
|
||||
size="large"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="ID" prop="id" />
|
||||
<el-table-column label="数据名称" prop="name" min-width="120" />
|
||||
<el-table-column label="数据值" prop="value" min-width="120" />
|
||||
<el-table-column label="状态">
|
||||
<template v-slot="{ row }">
|
||||
<el-tag v-if="row.status == 1">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
prop="remark"
|
||||
min-width="120"
|
||||
show-tooltip-when-overflow
|
||||
/>
|
||||
<el-table-column label="排序" prop="sort" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_data/edit']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_data/delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="dict-type">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<div>
|
||||
<admin-page-filter-panel>
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" inline>
|
||||
<el-form-item class="w-[280px]" label="字典名称">
|
||||
<el-input v-model="queryParams.name" clearable @keyup.enter="resetPage" />
|
||||
@@ -20,9 +20,10 @@
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<admin-page-data-panel v-loading="pager.loading">
|
||||
<template #toolbar>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_type/add']"
|
||||
type="primary"
|
||||
@@ -44,69 +45,65 @@
|
||||
</template>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-4" v-loading="pager.loading">
|
||||
<div>
|
||||
<el-table
|
||||
:data="pager.lists"
|
||||
size="large"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="ID" prop="id" />
|
||||
<el-table-column label="字典名称" prop="name" min-width="120" />
|
||||
<el-table-column label="字典类型" prop="type" min-width="120" />
|
||||
<el-table-column label="状态">
|
||||
<template v-slot="{ row }">
|
||||
<el-tag v-if="row.status == 1">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" show-tooltip-when-overflow />
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="180" />
|
||||
<el-table-column label="操作" width="190" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_type/edit']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_data/lists']"
|
||||
type="primary"
|
||||
link
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
path: getRoutePath('setting.dict.dict_data/lists'),
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
}"
|
||||
>
|
||||
数据管理
|
||||
</router-link>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_type/delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
<el-table
|
||||
:data="pager.lists"
|
||||
size="large"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="ID" prop="id" />
|
||||
<el-table-column label="字典名称" prop="name" min-width="120" />
|
||||
<el-table-column label="字典类型" prop="type" min-width="120" />
|
||||
<el-table-column label="状态">
|
||||
<template v-slot="{ row }">
|
||||
<el-tag v-if="row.status == 1">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" show-tooltip-when-overflow />
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="180" />
|
||||
<el-table-column label="操作" width="190" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_type/edit']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_data/lists']"
|
||||
type="primary"
|
||||
link
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
path: getRoutePath('setting.dict.dict_data/lists'),
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
}"
|
||||
>
|
||||
数据管理
|
||||
</router-link>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['setting.dict.dict_type/delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</template>
|
||||
</admin-page-data-panel>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -151,7 +148,6 @@ const handleEdit = async (data: any) => {
|
||||
editRef.value?.setFormData(data)
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
const handleDelete = async (id: any[] | number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await dictTypeDelete({ id })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="conversion-stats-page">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<admin-page-filter-panel>
|
||||
<el-form :inline="true" :model="queryParams" class="stats-filter-form">
|
||||
<el-form-item label="统计维度">
|
||||
<el-segmented
|
||||
@@ -89,7 +89,7 @@
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</admin-page-filter-panel>
|
||||
|
||||
<div class="stats-kpi-grid">
|
||||
<div
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<el-tree-select
|
||||
v-model="deptId"
|
||||
:data="deptTreeOptions"
|
||||
placeholder="二中心(全部)"
|
||||
placeholder="全部部门"
|
||||
clearable
|
||||
filterable
|
||||
check-strictly
|
||||
@@ -43,10 +43,10 @@
|
||||
</template>
|
||||
<div class="rate-caliber">
|
||||
<p>
|
||||
<b>当月被指派总数</b>:当月内诊单被指派给医助(按指派操作时间落月,<b>剔除勾选「继承」的指派</b>)的诊单数,按「医助 × 诊单」去重;部门行 / 合计行按诊单去重;<b>再剔除</b>名下存在履约「拒收 / 退款」业务订单的诊单。
|
||||
<b>当月被指派总数</b>:当月内诊单被指派给医助(按指派操作时间落月,<b>剔除勾选「继承」的指派</b>)的诊单数,按「医助 × 诊单」去重;部门行 / 合计行按诊单去重。
|
||||
</p>
|
||||
<p>
|
||||
<b>诊次(第 N 次下单)</b>:患者(诊单)名下计入业绩的业务订单(剔除已取消 / 拒收 / 退款)按下单时间升序编号为「实单序号」,<b>统计诊次 = 实单序号 + 诊单偏移</b>(默认偏移 0 → 第 1 笔实单为一诊;偏移 1 → 第 1 笔实单为二诊;偏移 2 → 第 1 笔实单为三诊,5 笔实单等价七诊)。诊次<b>跨月累计不重置</b>。诊单可在「业务订单」tab 配置偏移量。
|
||||
<b>诊次(第 N 次下单)</b>:患者(诊单)名下计入业绩的业务订单(剔除已取消 / 拒收 / 退款)按下单时间升序的全局序号,<b>跨月累计不重置</b>——如 5 月指派后旗下成交 4 单为二诊~五诊,下月再成交即为六诊。
|
||||
</p>
|
||||
<p>
|
||||
<b>当月 N 诊单数</b>:当月内下单且诊次为 N 的订单数,归属下单时点<b>持有该患者的医助</b>(指派可在往月;释放后不再归属;「继承」指派会转移持有人但不计被指派数)。
|
||||
@@ -55,7 +55,7 @@
|
||||
<b>当月 N 诊接诊率</b> = 当月 N 诊单数 ÷ 当月被指派总数。往月指派、当月成交会推高分子,比率可能超过 100%;医助当月无新指派但旗下有成交时,被指派数为 0、比率显示「—」。
|
||||
</p>
|
||||
<p>
|
||||
医助按人事部门归组;<b>仅统计「二中心」及其组织下级</b>;部门下拉与未选时的默认范围均限定在该子树,选定部门时含其组织下级。
|
||||
医助按人事部门归组;选定部门时含其组织下级。
|
||||
</p>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
@@ -83,23 +83,6 @@
|
||||
<el-radio-button value="0">未确认</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">部门</span>
|
||||
<el-tree-select
|
||||
v-model="formData.assistant_dept_id"
|
||||
:data="departmentTreeRaw"
|
||||
class="filter-dept-select"
|
||||
clearable
|
||||
filterable
|
||||
check-strictly
|
||||
:default-expand-all="true"
|
||||
node-key="id"
|
||||
size="small"
|
||||
:props="assistantDeptTreeProps"
|
||||
placeholder="选父级含子级"
|
||||
@change="handleAssistantDeptChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
@@ -559,7 +542,6 @@
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { appointmentLists, cancelAppointment, completeAppointment, appointmentDetail } from '@/api/doctor'
|
||||
import { deptAll } from '@/api/org/department'
|
||||
import { getCallSignature, generateMiniProgramQrcode, tcmDiagnosisDetail, prescriptionGetByAppointment } from '@/api/tcm'
|
||||
import { getDictData } from '@/api/app'
|
||||
import { addDoctorNote } from '@/api/patient'
|
||||
@@ -621,19 +603,10 @@ const formData = reactive({
|
||||
end_date: '',
|
||||
date_preset: 'today' as '' | 'yesterday' | 'day_before' | 'today' | 'tomorrow' | 'day_after',
|
||||
diagnosis_confirmed: '' as '' | '0' | '1', // ''=全部 1=已确认 0=未确认
|
||||
/** 接诊医生 / 诊单医助 / 挂号医助所属部门(选父级含子级) */
|
||||
assistant_dept_id: '' as number | '',
|
||||
/** 为 1 时后端 extend 返回各状态数量,避免额外 4 次列表请求 */
|
||||
include_status_counts: 0 as 0 | 1
|
||||
})
|
||||
|
||||
const departmentTreeRaw = ref<unknown[]>([])
|
||||
const assistantDeptTreeProps = {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
const activeTab = ref('1')
|
||||
const dateCustomVisible = ref(false)
|
||||
const statusCount = ref<Record<number, number>>({
|
||||
@@ -748,12 +721,6 @@ const handleDiagnosisConfirmedChange = () => {
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 部门筛选变更
|
||||
const handleAssistantDeptChange = () => {
|
||||
pager.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 快捷日期变更
|
||||
const handleDatePresetChange = (val: string | number | boolean | undefined) => {
|
||||
const v = String(val || '')
|
||||
@@ -803,7 +770,6 @@ const handleReset = () => {
|
||||
formData.doctor_name = ''
|
||||
formData.date_preset = 'today'
|
||||
formData.diagnosis_confirmed = ''
|
||||
formData.assistant_dept_id = ''
|
||||
const t = new Date()
|
||||
const p = (n: number) => String(n).padStart(2, '0')
|
||||
formData.start_date = `${t.getFullYear()}-${p(t.getMonth() + 1)}-${p(t.getDate())}`
|
||||
@@ -1133,13 +1099,7 @@ formData.start_date = `${_today.getFullYear()}-${_pad(_today.getMonth() + 1)}-${
|
||||
formData.end_date = formData.start_date
|
||||
formData.status = 1
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const deptTree = await deptAll()
|
||||
departmentTreeRaw.value = Array.isArray(deptTree) ? deptTree : []
|
||||
} catch {
|
||||
departmentTreeRaw.value = []
|
||||
}
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
listPollTimer = setInterval(() => {
|
||||
loadData({ silent: true })
|
||||
@@ -1268,10 +1228,6 @@ onUnmounted(() => {
|
||||
padding: 6px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-dept-select {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,43 +4,6 @@
|
||||
<el-empty description="当前诊单未携带患者ID,无法列出业务订单" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="diagnosisId > 0" class="po-revisit-offset-bar mb-3">
|
||||
<div class="po-revisit-offset-bar__main">
|
||||
<span class="text-sm text-gray-600">复诊统计起始偏移</span>
|
||||
<el-tooltip placement="top">
|
||||
<template #content>
|
||||
<div class="max-w-xs leading-relaxed">
|
||||
在实单诊次序号上叠加偏移量。设为 0(默认):第 1 笔实单计为一诊;设为 1:第 1 笔实单计为二诊;设为 2:第 1 笔实单计为三诊——若有 5 笔实单且偏移 2,则统计上相当于计至七诊(5+2)。
|
||||
</div>
|
||||
</template>
|
||||
<el-icon class="text-gray-400 align-middle ml-1"><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
<el-input-number
|
||||
v-model="revisitSlotStartOffset"
|
||||
v-perms="['tcm.diagnosis/setRevisitSlotStartOffset']"
|
||||
:min="0"
|
||||
:max="20"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-[120px] ml-3"
|
||||
:disabled="offsetSaving"
|
||||
/>
|
||||
<span class="text-xs text-gray-500 ml-2">
|
||||
第 1 笔实单计为{{ visitSlotStartLabel }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
v-perms="['tcm.diagnosis/setRevisitSlotStartOffset']"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="offsetSaving"
|
||||
:disabled="!offsetDirty"
|
||||
@click="saveRevisitSlotStartOffset"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="pager.loading"
|
||||
:data="pager.lists"
|
||||
@@ -49,23 +12,6 @@
|
||||
empty-text="暂无业务订单"
|
||||
>
|
||||
<el-table-column label="订单编号" prop="order_no" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="全局诊次" width="96" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.global_visit_seq">{{ row.global_visit_seq }}诊</span>
|
||||
<span v-else class="text-gray-400">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计入统计" width="96" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="row.counts_for_revisit_rate"
|
||||
type="success"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>是</el-tag>
|
||||
<el-tag v-else type="info" size="small" effect="plain">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="text-red-500 font-semibold">¥{{ formatAmount(row.amount) }}</span>
|
||||
@@ -117,11 +63,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { prescriptionOrderLists, tcmDiagnosisDetail, tcmDiagnosisSetRevisitSlotStartOffset } from '@/api/tcm'
|
||||
import { prescriptionOrderLists } from '@/api/tcm'
|
||||
import PrescriptionOrderDetailDrawer from '@/views/consumer/prescription/components/PrescriptionOrderDetailDrawer.vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
import {
|
||||
formatTime,
|
||||
fulfillmentText,
|
||||
@@ -147,26 +91,6 @@ const { pager, getLists, resetPage } = usePaging({
|
||||
size: 10
|
||||
})
|
||||
|
||||
const revisitSlotStartOffset = ref(0)
|
||||
const savedRevisitSlotStartOffset = ref(0)
|
||||
const offsetSaving = ref(false)
|
||||
const offsetLoading = ref(false)
|
||||
|
||||
const offsetDirty = computed(
|
||||
() => Number(revisitSlotStartOffset.value) !== Number(savedRevisitSlotStartOffset.value)
|
||||
)
|
||||
|
||||
const visitSlotStartLabel = computed(() => {
|
||||
const raw = Number(revisitSlotStartOffset.value)
|
||||
const offset = Number.isFinite(raw) ? raw : 0
|
||||
const slot = offset + 1
|
||||
const cn = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
if (slot >= 1 && slot <= 10) {
|
||||
return cn[slot] + '诊'
|
||||
}
|
||||
return `第${slot}诊`
|
||||
})
|
||||
|
||||
const buildParams = () => {
|
||||
Object.keys(queryParams).forEach((k) => delete queryParams[k])
|
||||
if (props.diagnosisId > 0) {
|
||||
@@ -178,47 +102,6 @@ const buildParams = () => {
|
||||
queryParams.scene = 'diagnosis_edit'
|
||||
}
|
||||
|
||||
async function loadRevisitSlotStartOffset() {
|
||||
if (props.diagnosisId <= 0) return
|
||||
offsetLoading.value = true
|
||||
try {
|
||||
const res: any = await tcmDiagnosisDetail({ id: props.diagnosisId })
|
||||
const d = res?.data ?? res ?? {}
|
||||
const offset = Number(d.revisit_slot_start_offset)
|
||||
const val = Number.isFinite(offset) && offset >= 0 && offset <= 20 ? offset : 0
|
||||
revisitSlotStartOffset.value = val
|
||||
savedRevisitSlotStartOffset.value = val
|
||||
} catch {
|
||||
revisitSlotStartOffset.value = 0
|
||||
savedRevisitSlotStartOffset.value = 0
|
||||
} finally {
|
||||
offsetLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRevisitSlotStartOffset() {
|
||||
if (props.diagnosisId <= 0) return
|
||||
const offset = Number(revisitSlotStartOffset.value)
|
||||
if (!Number.isFinite(offset) || offset < 0 || offset > 20) {
|
||||
feedback.msgWarning('起始偏移须在 0~20 之间')
|
||||
return
|
||||
}
|
||||
offsetSaving.value = true
|
||||
try {
|
||||
await tcmDiagnosisSetRevisitSlotStartOffset({
|
||||
id: props.diagnosisId,
|
||||
revisit_slot_start_offset: offset
|
||||
})
|
||||
savedRevisitSlotStartOffset.value = offset
|
||||
feedback.msgSuccess('保存成功')
|
||||
getLists()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
offsetSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 详情抽屉(共享组件,数据拉取/展示全部在组件内) ───
|
||||
const detailDrawerRef = ref<InstanceType<typeof PrescriptionOrderDetailDrawer>>()
|
||||
|
||||
@@ -234,13 +117,8 @@ const formatAmount = (value: unknown) => {
|
||||
watch(
|
||||
() => [props.diagnosisId, patientIdNum.value] as const,
|
||||
() => {
|
||||
if (!patientIdAvailable.value) {
|
||||
pager.lists = []
|
||||
pager.count = 0
|
||||
return
|
||||
}
|
||||
if (!patientIdAvailable.value) { pager.lists = []; pager.count = 0; return }
|
||||
buildParams()
|
||||
void loadRevisitSlotStartOffset()
|
||||
resetPage()
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -256,21 +134,4 @@ defineExpose({ refresh: () => getLists() })
|
||||
.po-empty-tip {
|
||||
padding: 24px 0;
|
||||
}
|
||||
.po-revisit-offset-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
.po-revisit-offset-bar__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -121,16 +121,6 @@
|
||||
end-placeholder="最近挂号结束"
|
||||
@change="handleLatestAppointmentFilterChange"
|
||||
/>
|
||||
<daterange-picker
|
||||
class="latest-assign-range"
|
||||
v-model:startTime="formData.latest_assign_start_date"
|
||||
v-model:endTime="formData.latest_assign_end_date"
|
||||
picker-type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
start-placeholder="最近指派开始"
|
||||
end-placeholder="最近指派结束"
|
||||
@change="handleLatestAssignFilterChange"
|
||||
/>
|
||||
<el-select
|
||||
v-model="formData.latest_appointment_channel_source"
|
||||
placeholder="最近挂号渠道"
|
||||
@@ -183,7 +173,6 @@
|
||||
v-loading="pager.loading"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="goReadonly"
|
||||
@sort-change="handleTableSortChange"
|
||||
:row-class-name="getRowClassName"
|
||||
class="diagnosis-table"
|
||||
stripe
|
||||
@@ -294,14 +283,7 @@
|
||||
<span v-else class="status-unprescribed">未开方</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="未服务天数"
|
||||
prop="unserved_days"
|
||||
width="110"
|
||||
align="center"
|
||||
sortable="custom"
|
||||
:sort-orders="['descending', 'ascending']"
|
||||
>
|
||||
<el-table-column label="未服务天数" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip
|
||||
v-if="row.last_blood_record_at"
|
||||
@@ -780,10 +762,6 @@ const formData = reactive({
|
||||
latest_appointment_start_date: '' as string,
|
||||
latest_appointment_end_date: '' as string,
|
||||
latest_appointment_channel_source: '' as string,
|
||||
latest_assign_start_date: '' as string,
|
||||
latest_assign_end_date: '' as string,
|
||||
/** 未服务天数排序:desc=天数多到少 asc=少到多 */
|
||||
sort_unserved_days: '' as '' | 'asc' | 'desc',
|
||||
diagnosis_confirmed: '' as '' | '0' | '1',
|
||||
appointment_date: '' as string,
|
||||
has_appointment: '' as '' | '0' | '1',
|
||||
@@ -871,50 +849,22 @@ function resolvePendingAssignOrderMonthForRequest(): string {
|
||||
return dayjs().format('YYYY-MM')
|
||||
}
|
||||
|
||||
/** 除顶部 Tab 专属条件外,与主列表共用的「更多筛选」参数(角标 count 需同步) */
|
||||
function buildSharedDiagnosisFilterPayload(): Record<string, unknown> {
|
||||
return {
|
||||
keyword: formData.keyword,
|
||||
diagnosis_type: formData.diagnosis_type,
|
||||
syndrome_type: formData.syndrome_type,
|
||||
assistant_id: formData.assistant_id,
|
||||
diagnosis_confirmed: formData.diagnosis_confirmed,
|
||||
has_appointment: formData.has_appointment,
|
||||
latest_appointment_start_date: formData.latest_appointment_start_date,
|
||||
latest_appointment_end_date: formData.latest_appointment_end_date,
|
||||
latest_appointment_channel_source: formData.latest_appointment_channel_source,
|
||||
latest_assign_start_date: formData.latest_assign_start_date,
|
||||
latest_assign_end_date: formData.latest_assign_end_date
|
||||
}
|
||||
}
|
||||
|
||||
/** 顶部 Tab 角标 count 请求:带上共用筛选,再叠加各 Tab 专属条件 */
|
||||
function buildDateCountRequestPayload(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
return {
|
||||
page_no: 1,
|
||||
page_size: 1,
|
||||
...buildSharedDiagnosisFilterPayload(),
|
||||
appointment_date: '',
|
||||
pending_booking: '',
|
||||
completed_appointment: '',
|
||||
pending_assign: '',
|
||||
pending_assign_order_month: '',
|
||||
pending_assign_keyword: '',
|
||||
sort_unserved_days: '',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
/** 待分配角标 count 请求:与列表同条件,且去掉其它顶部 Tab 残留(如默认「当天挂号」) */
|
||||
function buildPendingAssignCountPayload(): Record<string, unknown> {
|
||||
return buildTcmDiagnosisListRequestPayload(
|
||||
buildDateCountRequestPayload({
|
||||
pending_assign: 1,
|
||||
has_appointment: '',
|
||||
pending_assign_order_month: resolvePendingAssignOrderMonthForRequest(),
|
||||
pending_assign_keyword: formData.pending_assign_keyword
|
||||
}) as Record<string, unknown>
|
||||
) as Record<string, unknown>
|
||||
return buildTcmDiagnosisListRequestPayload({
|
||||
...formData,
|
||||
page_no: 1,
|
||||
page_size: 1,
|
||||
pending_assign: 1,
|
||||
appointment_date: '',
|
||||
has_appointment: '',
|
||||
pending_booking: '',
|
||||
completed_appointment: '',
|
||||
latest_appointment_start_date: '',
|
||||
latest_appointment_end_date: '',
|
||||
latest_appointment_channel_source: '',
|
||||
pending_assign_order_month: resolvePendingAssignOrderMonthForRequest()
|
||||
} as Record<string, unknown>) as Record<string, unknown>
|
||||
}
|
||||
|
||||
const fetchTcmDiagnosisListsForPaging = (req: Record<string, unknown>) =>
|
||||
@@ -943,9 +893,6 @@ function clearSecondaryFiltersWhenPendingAssignWideSearch() {
|
||||
formData.latest_appointment_start_date = ''
|
||||
formData.latest_appointment_end_date = ''
|
||||
formData.latest_appointment_channel_source = ''
|
||||
formData.latest_assign_start_date = ''
|
||||
formData.latest_assign_end_date = ''
|
||||
formData.sort_unserved_days = ''
|
||||
formData.pending_assign_order_month = ''
|
||||
activeTab.value = 'all'
|
||||
if (kw1 !== '') {
|
||||
@@ -1047,26 +994,14 @@ const onPendingAssignOrderMonthChange = async (val: string | null) => {
|
||||
const fetchDateCounts = async () => {
|
||||
try {
|
||||
const [yesterday, dayBefore, today, tomorrow, dayAfter, all, noApt, doneVisit, pending] = await Promise.all([
|
||||
tcmDiagnosisLists(
|
||||
buildDateCountRequestPayload({ appointment_date: yesterdayStr.value, has_appointment: '' }) as any
|
||||
),
|
||||
tcmDiagnosisLists(
|
||||
buildDateCountRequestPayload({ appointment_date: dayBeforeStr.value, has_appointment: '' }) as any
|
||||
),
|
||||
tcmDiagnosisLists(
|
||||
buildDateCountRequestPayload({ appointment_date: todayStr.value, has_appointment: '' }) as any
|
||||
),
|
||||
tcmDiagnosisLists(
|
||||
buildDateCountRequestPayload({ appointment_date: tomorrowStr.value, has_appointment: '' }) as any
|
||||
),
|
||||
tcmDiagnosisLists(
|
||||
buildDateCountRequestPayload({ appointment_date: dayAfterStr.value, has_appointment: '' }) as any
|
||||
),
|
||||
tcmDiagnosisLists(buildDateCountRequestPayload() as any),
|
||||
tcmDiagnosisLists(buildDateCountRequestPayload({ has_appointment: 0 }) as any),
|
||||
tcmDiagnosisLists(
|
||||
buildDateCountRequestPayload({ completed_appointment: 1, has_appointment: '' }) as any
|
||||
),
|
||||
tcmDiagnosisLists({ appointment_date: yesterdayStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: dayBeforeStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: todayStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: tomorrowStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: dayAfterStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists(buildPendingAssignCountPayload() as any)
|
||||
])
|
||||
dateCounts.value = {
|
||||
@@ -1200,11 +1135,6 @@ const clearLatestAppointmentFilters = () => {
|
||||
formData.latest_appointment_channel_source = ''
|
||||
}
|
||||
|
||||
const clearLatestAssignFilters = () => {
|
||||
formData.latest_assign_start_date = ''
|
||||
formData.latest_assign_end_date = ''
|
||||
}
|
||||
|
||||
const hasLatestAppointmentFilter = () =>
|
||||
!!(
|
||||
formData.latest_appointment_start_date ||
|
||||
@@ -1212,9 +1142,6 @@ const hasLatestAppointmentFilter = () =>
|
||||
formData.latest_appointment_channel_source
|
||||
)
|
||||
|
||||
const hasLatestAssignFilter = () =>
|
||||
!!(formData.latest_assign_start_date || formData.latest_assign_end_date)
|
||||
|
||||
const handleLatestAppointmentFilterChange = () => {
|
||||
if (hasLatestAppointmentFilter()) {
|
||||
formData.appointment_date = ''
|
||||
@@ -1227,27 +1154,6 @@ const handleLatestAppointmentFilterChange = () => {
|
||||
doSearch()
|
||||
}
|
||||
|
||||
const handleLatestAssignFilterChange = () => {
|
||||
doSearch()
|
||||
}
|
||||
|
||||
const handleTableSortChange = ({
|
||||
prop,
|
||||
order
|
||||
}: {
|
||||
prop: string
|
||||
order: 'ascending' | 'descending' | null
|
||||
}) => {
|
||||
if (prop === 'unserved_days') {
|
||||
formData.sort_unserved_days =
|
||||
order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : ''
|
||||
} else {
|
||||
formData.sort_unserved_days = ''
|
||||
}
|
||||
pager.page = 1
|
||||
getLists()
|
||||
}
|
||||
|
||||
const latestAppointmentChannelText = (row: any) => {
|
||||
const desc = String(row?.latest_appointment_channel_source_desc || '').trim()
|
||||
const raw = String(row?.latest_appointment_channel_source || '').trim()
|
||||
@@ -1294,9 +1200,6 @@ const handleReset = () => {
|
||||
formData.latest_appointment_start_date = ''
|
||||
formData.latest_appointment_end_date = ''
|
||||
formData.latest_appointment_channel_source = ''
|
||||
formData.latest_assign_start_date = ''
|
||||
formData.latest_assign_end_date = ''
|
||||
formData.sort_unserved_days = ''
|
||||
formData.diagnosis_confirmed = ''
|
||||
formData.appointment_date = ''
|
||||
formData.has_appointment = ''
|
||||
@@ -2395,11 +2298,6 @@ onUnmounted(() => {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.latest-assign-range {
|
||||
width: 260px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.latest-appointment-channel {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</span>
|
||||
<div>
|
||||
<div class="wb-section-name">订单统计</div>
|
||||
<div class="wb-section-sub">{{ orderStatsDateRangeText }} · {{ orderStatsData.order_type_name || '—' }}</div>
|
||||
<div class="wb-section-sub">{{ orderStatsDateRangeText }} · {{ orderStatsData.order_type_name || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wb-toolbar">
|
||||
@@ -821,8 +821,8 @@ onMounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
.workbench-page {
|
||||
min-height: 100%;
|
||||
padding: 20px 20px 40px;
|
||||
background: linear-gradient(160deg, #eef2ff 0%, #f8fafc 38%, #f1f5f9 100%);
|
||||
padding: 4px 0 24px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.wb-hero {
|
||||
@@ -830,56 +830,86 @@ onMounted(() => {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 22px;
|
||||
padding: 22px 26px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.92) 0%, rgba(255, 255, 255, 0.65) 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.06);
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
padding: 28px 28px;
|
||||
border-radius: var(--admin-radius-xl);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
background: var(--admin-surface-glass);
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
linear-gradient(120deg, rgba(6, 182, 212, 0.14) 0%, transparent 45%),
|
||||
linear-gradient(300deg, rgba(16, 185, 129, 0.1) 0%, transparent 40%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
height: 3px;
|
||||
background: var(--admin-brand-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
.wb-hero-text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wb-hero-title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
color: #0f172a;
|
||||
margin: 0 0 8px;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
background: var(--admin-brand-gradient);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.wb-hero-desc {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
font-size: 15px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.wb-refresh-btn {
|
||||
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-shadow: var(--admin-brand-glow);
|
||||
}
|
||||
|
||||
.wb-section {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 8px 32px rgba(15, 23, 42, 0.05);
|
||||
border-radius: var(--admin-radius-xl);
|
||||
background: var(--admin-surface-elevated);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.wb-section--diagnosis {
|
||||
border-top: 3px solid transparent;
|
||||
border-image: linear-gradient(90deg, #3b82f6, #60a5fa) 1;
|
||||
border-image: linear-gradient(90deg, #06b6d4, #10b981) 1;
|
||||
}
|
||||
|
||||
.wb-section--order {
|
||||
border-top: 3px solid transparent;
|
||||
border-image: linear-gradient(90deg, #8b5cf6, #a78bfa) 1;
|
||||
border-image: linear-gradient(90deg, #0891b2, #6366f1) 1;
|
||||
}
|
||||
|
||||
.wb-section--trend {
|
||||
border-top: 3px solid transparent;
|
||||
border-image: linear-gradient(90deg, #14b8a6, #2dd4bf) 1;
|
||||
border-image: linear-gradient(90deg, #10b981, #06b6d4) 1;
|
||||
}
|
||||
|
||||
.wb-section-head {
|
||||
@@ -889,8 +919,8 @@ onMounted(() => {
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 18px 22px;
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.9);
|
||||
background: linear-gradient(180deg, rgba(248, 250, 252, 0.9) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||
background: var(--admin-brand-gradient-soft);
|
||||
}
|
||||
|
||||
.wb-section-title {
|
||||
@@ -903,38 +933,68 @@ onMounted(() => {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 14px;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.wb-section-icon--blue {
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.35);
|
||||
background: var(--admin-brand-gradient);
|
||||
box-shadow: var(--admin-brand-glow);
|
||||
}
|
||||
|
||||
.wb-section-icon--violet {
|
||||
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
||||
box-shadow: 0 8px 20px rgba(124, 58, 237, 0.3);
|
||||
background: linear-gradient(135deg, #0891b2, #6366f1);
|
||||
box-shadow: 0 8px 24px rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
|
||||
.wb-section-icon--teal {
|
||||
background: linear-gradient(135deg, #14b8a6, #0d9488);
|
||||
box-shadow: 0 8px 20px rgba(13, 148, 136, 0.3);
|
||||
background: linear-gradient(135deg, #10b981, #06b6d4);
|
||||
box-shadow: 0 8px 24px rgba(16, 185, 129, 0.25);
|
||||
}
|
||||
|
||||
.wb-kpi--blue {
|
||||
background: linear-gradient(145deg, rgba(6, 182, 212, 0.12), rgba(16, 185, 129, 0.06));
|
||||
border: 1px solid rgba(6, 182, 212, 0.22);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
}
|
||||
|
||||
.wb-kpi--violet {
|
||||
background: linear-gradient(145deg, rgba(99, 102, 241, 0.1), rgba(6, 182, 212, 0.06));
|
||||
border: 1px solid rgba(99, 102, 241, 0.2);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
}
|
||||
|
||||
.wb-kpi--amber {
|
||||
background: linear-gradient(145deg, rgba(245, 158, 11, 0.12), rgba(251, 191, 36, 0.06));
|
||||
border: 1px solid rgba(245, 158, 11, 0.24);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
}
|
||||
|
||||
.wb-rank-strip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 112px;
|
||||
padding: 14px 18px;
|
||||
border-radius: var(--admin-radius-lg);
|
||||
background: var(--admin-brand-gradient-soft);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
}
|
||||
|
||||
.wb-section-name {
|
||||
font-size: 17px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.wb-section-sub {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.wb-toolbar {
|
||||
@@ -968,21 +1028,6 @@ onMounted(() => {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.wb-kpi--blue {
|
||||
background: linear-gradient(145deg, #eff6ff 0%, #dbeafe 100%);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.wb-kpi--violet {
|
||||
background: linear-gradient(145deg, #f5f3ff 0%, #ede9fe 100%);
|
||||
border: 1px solid rgba(139, 92, 246, 0.22);
|
||||
}
|
||||
|
||||
.wb-kpi--amber {
|
||||
background: linear-gradient(145deg, #fffbeb 0%, #fef3c7 100%);
|
||||
border: 1px solid rgba(245, 158, 11, 0.25);
|
||||
}
|
||||
|
||||
.wb-kpi-label {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
@@ -1007,17 +1052,6 @@ onMounted(() => {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.wb-rank-strip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 112px;
|
||||
padding: 14px 18px;
|
||||
border-radius: 16px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.wb-rank-strip--compact {
|
||||
min-height: 112px;
|
||||
}
|
||||
@@ -1080,9 +1114,10 @@ onMounted(() => {
|
||||
.wb-chart-card {
|
||||
height: 100%;
|
||||
padding: 14px 16px 8px;
|
||||
border-radius: 16px;
|
||||
background: #fafbfc;
|
||||
border: 1px solid #eef0f4;
|
||||
border-radius: var(--admin-radius-lg);
|
||||
background: var(--admin-surface-elevated);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
}
|
||||
|
||||
.wb-chart-card-head {
|
||||
|
||||
@@ -77,7 +77,22 @@ module.exports = {
|
||||
mask: 'var(--el-mask-color)'
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['PingFang SC', 'Arial', 'Hiragino Sans GB', 'Microsoft YaHei', 'sans-serif']
|
||||
sans: [
|
||||
'PingFang SC',
|
||||
'SF Pro Text',
|
||||
'Segoe UI',
|
||||
'Arial',
|
||||
'Hiragino Sans GB',
|
||||
'Microsoft YaHei',
|
||||
'sans-serif'
|
||||
]
|
||||
},
|
||||
borderRadius: {
|
||||
sm: 'var(--admin-radius-sm)',
|
||||
DEFAULT: 'var(--admin-radius-md)',
|
||||
md: 'var(--admin-radius-md)',
|
||||
lg: 'var(--admin-radius-lg)',
|
||||
xl: 'var(--admin-radius-xl)'
|
||||
},
|
||||
boxShadow: {
|
||||
DEFAULT: 'var(--el-box-shadow)',
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -94,24 +94,6 @@ class DiagnosisController extends BaseAdminController
|
||||
return $this->fail(DiagnosisLogic::getError());
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置复诊接诊率统计起始偏移(业务订单 tab)
|
||||
*/
|
||||
public function setRevisitSlotStartOffset()
|
||||
{
|
||||
$params = (new DiagnosisValidate())->post()->goCheck('setRevisitSlotStartOffset');
|
||||
$ok = DiagnosisLogic::setRevisitSlotStartOffset(
|
||||
(int) $params['id'],
|
||||
(int) $params['revisit_slot_start_offset'],
|
||||
$this->adminInfo
|
||||
);
|
||||
if (!$ok) {
|
||||
return $this->fail(DiagnosisLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 删除诊单
|
||||
* @return \think\response\Json
|
||||
|
||||
@@ -181,20 +181,6 @@ class PrescriptionOrderController extends BaseAdminController
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改关联处方服用参数(主方/辅方次数与开立天数)及订单服用天数
|
||||
*/
|
||||
public function patchPrescriptionUsage()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('patchPrescriptionUsage');
|
||||
$ok = PrescriptionOrderLogic::patchPrescriptionUsage($params, $this->adminId, $this->adminInfo);
|
||||
if (!$ok) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
public function auditPrescription()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('auditPrescription');
|
||||
@@ -342,20 +328,6 @@ class PrescriptionOrderController extends BaseAdminController
|
||||
return $this->success('', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手工新增操作日志(可选同步调整处方/支付单审核状态)
|
||||
*/
|
||||
public function addLog()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('addLog');
|
||||
$result = PrescriptionOrderLogic::addLog($params, $this->adminId, $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('日志已添加', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为「已发货」订单新增一条关联支付单,并重置支付单审核状态为待审核
|
||||
*/
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
namespace app\adminapi\lists\doctor;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\DiagnosisViewRecord;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Prescription;
|
||||
@@ -77,35 +75,6 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 按部门筛选:接诊医生、诊单医助或挂号医助所属部门命中子树即可(选父级含子级)
|
||||
*
|
||||
* @param mixed $query
|
||||
*/
|
||||
private function applyAssistantDeptIdFilter($query): void
|
||||
{
|
||||
if (!isset($this->params['assistant_dept_id']) || $this->params['assistant_dept_id'] === '' || (int) $this->params['assistant_dept_id'] <= 0) {
|
||||
return;
|
||||
}
|
||||
$rootDeptId = (int) $this->params['assistant_dept_id'];
|
||||
$deptIds = DeptLogic::getSelfAndDescendantIds($rootDeptId);
|
||||
$deptIds = array_values(array_filter(array_map('intval', $deptIds), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
}));
|
||||
if ($deptIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$inList = implode(',', $deptIds);
|
||||
$adTbl = (new AdminDept())->getTable();
|
||||
$query->whereRaw(
|
||||
"(EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = a.`doctor_id` AND ad.`dept_id` IN ({$inList}))"
|
||||
. " OR EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = u.`assistant_id` AND ad.`dept_id` IN ({$inList}))"
|
||||
. " OR EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = a.`assistant_id` AND ad.`dept_id` IN ({$inList})))"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渠道筛选:与 AppointmentLogic 一致,兼容仅有 channel_source、仅有 channels、或两者皆有的表结构
|
||||
*
|
||||
@@ -222,8 +191,6 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
|
||||
$this->applyAssistantIdFilter($query);
|
||||
|
||||
$this->applyAssistantDeptIdFilter($query);
|
||||
|
||||
$this->applyChannelSourceFilter($query, $chFilter);
|
||||
|
||||
// 是否确认诊单:1=已确认 0=未确认
|
||||
@@ -406,8 +373,6 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
|
||||
$this->applyAssistantIdFilter($query);
|
||||
|
||||
$this->applyAssistantDeptIdFilter($query);
|
||||
|
||||
$this->applyChannelSourceFilter($query, $chFilter);
|
||||
|
||||
if ((int) ($this->params['exclude_cancelled'] ?? 0) === 1) {
|
||||
|
||||
@@ -140,7 +140,6 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
}
|
||||
|
||||
$this->applyLatestAppointmentFilters($query, $pendingWideSearch);
|
||||
$this->applyLatestAssignFilters($query, $pendingWideSearch);
|
||||
|
||||
$this->applyPendingAssignBusinessOrderMonthFilter($query);
|
||||
|
||||
@@ -168,7 +167,30 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
}
|
||||
}
|
||||
|
||||
$orderRaw = $this->resolveListOrderRaw($pendingWideSearch);
|
||||
// 按挂号状态优先级排序:已过号(4) > 已预约(1) > 已完成(3),然后按挂号日期+时间升序
|
||||
// 若传了 appointment_date(当天/明天等筛选),只按「该日」的挂号排序与展示
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$aptTbl = (new Appointment())->getTable();
|
||||
$minAptDateCond = '';
|
||||
if (!$pendingWideSearch && !empty($this->params['appointment_date'])) {
|
||||
$sortAptDate = addslashes((string) $this->params['appointment_date']);
|
||||
$minAptDateCond = " AND apt.appointment_date = '{$sortAptDate}'";
|
||||
}
|
||||
|
||||
// 获取最早的挂号状态(用于排序优先级)
|
||||
$minAptStatusExpr = '(SELECT apt.status FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status IN (1,3,4)' . $minAptDateCond . ' ORDER BY CASE apt.status WHEN 4 THEN 1 WHEN 1 THEN 2 WHEN 3 THEN 3 ELSE 4 END, apt.appointment_date ASC, apt.appointment_time ASC LIMIT 1)';
|
||||
|
||||
// 获取最早的挂号时间(用于同状态内排序)
|
||||
$minAptExpr = '(SELECT MIN(CONCAT(apt.appointment_date, \' \', IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\'))) FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status IN (1,3,4)' . $minAptDateCond . ')';
|
||||
|
||||
// 「已完成」Tab:按最近一条「已完成」(status=3) 挂号日期+时间降序…
|
||||
$isCompletedTab = !$pendingWideSearch && isset($this->params['completed_appointment']) && (string) $this->params['completed_appointment'] === '1';
|
||||
if ($isCompletedTab) {
|
||||
$maxCompletedAptExpr = '(SELECT MAX(CONCAT(apt.appointment_date, \' \', IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\'))) FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status = 3' . $minAptDateCond . ')';
|
||||
$orderRaw = $diagTbl . '.assign_read_at IS NULL DESC, IFNULL(' . $maxCompletedAptExpr . ", '1970-01-01 00:00:00') DESC, {$diagTbl}.id DESC";
|
||||
} else {
|
||||
$orderRaw = $diagTbl . '.assign_read_at IS NULL DESC, CASE IFNULL(' . $minAptStatusExpr . ', 999) WHEN 4 THEN 1 WHEN 1 THEN 2 WHEN 3 THEN 3 ELSE 4 END ASC, IFNULL(' . $minAptExpr . ", '9999-12-31 23:59:59') ASC, {$diagTbl}.id DESC";
|
||||
}
|
||||
|
||||
$lists = $query
|
||||
->with(['DiagnosisViewRecord'])
|
||||
@@ -549,7 +571,6 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
}
|
||||
|
||||
$this->applyLatestAppointmentFilters($query, $pendingWideSearch);
|
||||
$this->applyLatestAssignFilters($query, $pendingWideSearch);
|
||||
|
||||
// 仅已开方(待分配+关键词检索时不限制)
|
||||
if (!$pendingWideSearch && isset($this->params['only_has_prescription']) && (string) $this->params['only_has_prescription'] === '1') {
|
||||
@@ -623,99 +644,6 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$query->whereExists("SELECT 1 FROM {$aptTbl} latest_apt WHERE " . implode(' AND ', $conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* 最近一次成功指派过滤:按 create_time DESC, id DESC 取 to_assistant_id>0 的一条。
|
||||
*
|
||||
* @param mixed $query
|
||||
*/
|
||||
private function applyLatestAssignFilters($query, bool $pendingWideSearch): void
|
||||
{
|
||||
if ($pendingWideSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
$startDate = $this->normalizeYmd($this->params['latest_assign_start_date'] ?? '');
|
||||
$endDate = $this->normalizeYmd($this->params['latest_assign_end_date'] ?? '');
|
||||
if ($startDate === '' && $endDate === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$logTbl = Db::name('tcm_diagnosis_assign_log')->getTable();
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$latestIdSql = $this->latestAssignLogIdSubSql($logTbl, $diagTbl);
|
||||
$conditions = ["latest_lg.id = ({$latestIdSql})"];
|
||||
|
||||
if ($startDate !== '') {
|
||||
$startTs = (int) strtotime($startDate . ' 00:00:00');
|
||||
$conditions[] = "latest_lg.create_time >= {$startTs}";
|
||||
}
|
||||
if ($endDate !== '') {
|
||||
$endTs = (int) strtotime($endDate . ' 23:59:59');
|
||||
$conditions[] = "latest_lg.create_time <= {$endTs}";
|
||||
}
|
||||
|
||||
$query->whereExists("SELECT 1 FROM {$logTbl} latest_lg WHERE " . implode(' AND ', $conditions));
|
||||
}
|
||||
|
||||
private function latestAssignLogIdSubSql(string $logTbl, string $diagTbl): string
|
||||
{
|
||||
return "SELECT lg_latest.id FROM {$logTbl} lg_latest "
|
||||
. "WHERE lg_latest.diagnosis_id = {$diagTbl}.id "
|
||||
. 'AND lg_latest.to_assistant_id > 0 '
|
||||
. 'ORDER BY lg_latest.create_time DESC, lg_latest.id DESC LIMIT 1';
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表默认排序;支持 sort_unserved_days=asc|desc 按未服务天数排序。
|
||||
*/
|
||||
private function resolveListOrderRaw(bool $pendingWideSearch): string
|
||||
{
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$sortUnserved = strtolower(trim((string) ($this->params['sort_unserved_days'] ?? '')));
|
||||
if (in_array($sortUnserved, ['asc', 'desc'], true)) {
|
||||
$anchorExpr = $this->unservedAnchorExpr($diagTbl);
|
||||
$nullLast = "CASE WHEN IFNULL({$anchorExpr}, 0) = 0 THEN 1 ELSE 0 END ASC";
|
||||
if ($sortUnserved === 'desc') {
|
||||
return "{$nullLast}, IFNULL({$anchorExpr}, 0) ASC, {$diagTbl}.id DESC";
|
||||
}
|
||||
|
||||
return "{$nullLast}, IFNULL({$anchorExpr}, 0) DESC, {$diagTbl}.id DESC";
|
||||
}
|
||||
|
||||
$aptTbl = (new Appointment())->getTable();
|
||||
$minAptDateCond = '';
|
||||
if (!$pendingWideSearch && !empty($this->params['appointment_date'])) {
|
||||
$sortAptDate = addslashes((string) $this->params['appointment_date']);
|
||||
$minAptDateCond = " AND apt.appointment_date = '{$sortAptDate}'";
|
||||
}
|
||||
|
||||
$minAptStatusExpr = '(SELECT apt.status FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status IN (1,3,4)' . $minAptDateCond . ' ORDER BY CASE apt.status WHEN 4 THEN 1 WHEN 1 THEN 2 WHEN 3 THEN 3 ELSE 4 END, apt.appointment_date ASC, apt.appointment_time ASC LIMIT 1)';
|
||||
$minAptExpr = '(SELECT MIN(CONCAT(apt.appointment_date, \' \', IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\'))) FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status IN (1,3,4)' . $minAptDateCond . ')';
|
||||
|
||||
$isCompletedTab = !$pendingWideSearch && isset($this->params['completed_appointment']) && (string) $this->params['completed_appointment'] === '1';
|
||||
if ($isCompletedTab) {
|
||||
$maxCompletedAptExpr = '(SELECT MAX(CONCAT(apt.appointment_date, \' \', IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\'))) FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status = 3' . $minAptDateCond . ')';
|
||||
|
||||
return $diagTbl . '.assign_read_at IS NULL DESC, IFNULL(' . $maxCompletedAptExpr . ", '1970-01-01 00:00:00') DESC, {$diagTbl}.id DESC";
|
||||
}
|
||||
|
||||
return $diagTbl . '.assign_read_at IS NULL DESC, CASE IFNULL(' . $minAptStatusExpr . ', 999) WHEN 4 THEN 1 WHEN 1 THEN 2 WHEN 3 THEN 3 ELSE 4 END ASC, IFNULL(' . $minAptExpr . ", '9999-12-31 23:59:59') ASC, {$diagTbl}.id DESC";
|
||||
}
|
||||
|
||||
/** 未服务天数锚点:血糖/饮食/运动记录最近 record_date 的最大值 */
|
||||
private function unservedAnchorExpr(string $diagTbl): string
|
||||
{
|
||||
$bloodTbl = (new BloodRecord())->getTable();
|
||||
$dietTbl = (new DietRecord())->getTable();
|
||||
$exerciseTbl = (new ExerciseRecord())->getTable();
|
||||
|
||||
return 'GREATEST('
|
||||
. "COALESCE((SELECT MAX(br.record_date) FROM {$bloodTbl} br WHERE br.diagnosis_id = {$diagTbl}.id AND br.delete_time IS NULL), 0), "
|
||||
. "COALESCE((SELECT MAX(dr.record_date) FROM {$dietTbl} dr WHERE dr.diagnosis_id = {$diagTbl}.id AND dr.delete_time IS NULL), 0), "
|
||||
. "COALESCE((SELECT MAX(er.record_date) FROM {$exerciseTbl} er WHERE er.diagnosis_id = {$diagTbl}.id AND er.delete_time IS NULL), 0)"
|
||||
. ')';
|
||||
}
|
||||
|
||||
private function latestAppointmentIdSubSql(string $aptTbl, string $diagTbl): string
|
||||
{
|
||||
$statuses = implode(',', self::EFFECTIVE_APPOINTMENT_STATUSES);
|
||||
|
||||
@@ -741,10 +741,6 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
}
|
||||
unset($item);
|
||||
|
||||
if ($this->shouldBypassListVisibilityForDiagnosisEdit()) {
|
||||
$this->appendDiagnosisEditVisitSeqFields($lists);
|
||||
}
|
||||
|
||||
$this->appendPrescriptionOrderAssignSnapshotErCenterFlags($lists);
|
||||
|
||||
if ((int) ($this->params['yeji_order_drawer'] ?? 0) === 1) {
|
||||
@@ -824,11 +820,6 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
'export_guahao_channel_source' => '自媒体渠道(挂号渠道来源)',
|
||||
'export_medication_form' => '药品形态',
|
||||
'export_prescription_name' => '药方名称',
|
||||
'export_prescription_herbs' => '处方',
|
||||
'export_main_usage' => '主方服用方式',
|
||||
'export_main_usage_days' => '主方天数',
|
||||
'export_aux_usage' => '辅方服用方式',
|
||||
'export_aux_usage_days' => '辅方天数',
|
||||
'export_service_package' => '服务套餐',
|
||||
'export_medication_days' => '天数',
|
||||
'export_amount' => '总金额',
|
||||
@@ -918,9 +909,6 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
$wrapWideKeys = [
|
||||
'export_linked_pay_records',
|
||||
'export_prescription_name',
|
||||
'export_prescription_herbs',
|
||||
'export_main_usage',
|
||||
'export_aux_usage',
|
||||
'export_guahao_channel_source',
|
||||
'export_assistant_dept',
|
||||
'export_service_package',
|
||||
@@ -931,8 +919,6 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
'export_patient_gender' => 6,
|
||||
'export_patient_age' => 6,
|
||||
'export_medication_days' => 6,
|
||||
'export_main_usage_days' => 8,
|
||||
'export_aux_usage_days' => 8,
|
||||
'export_amount' => 10,
|
||||
'export_paid_amount' => 10,
|
||||
'export_refund_amount' => 10,
|
||||
@@ -941,9 +927,6 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
'export_supply_mode' => 10,
|
||||
'export_linked_pay_records' => 52,
|
||||
'export_prescription_name' => 34,
|
||||
'export_prescription_herbs' => 36,
|
||||
'export_main_usage' => 28,
|
||||
'export_aux_usage' => 28,
|
||||
'export_guahao_channel_source' => 22,
|
||||
'export_assistant_dept' => 24,
|
||||
'export_service_package' => 18,
|
||||
@@ -1421,69 +1404,6 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单编辑-业务订单 tab:标注全局诊次及是否计入复诊接诊率(与 RevisitRateLogic 同口径)
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $lists
|
||||
*/
|
||||
private function appendDiagnosisEditVisitSeqFields(array &$lists): void
|
||||
{
|
||||
if ($lists === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$diagIds = [];
|
||||
foreach ($lists as $row) {
|
||||
$d = (int) ($row['diagnosis_id'] ?? 0);
|
||||
if ($d > 0) {
|
||||
$diagIds[$d] = true;
|
||||
}
|
||||
}
|
||||
$contextDid = (int) ($this->params['context_diagnosis_id'] ?? 0);
|
||||
if ($contextDid > 0) {
|
||||
$diagIds[$contextDid] = true;
|
||||
}
|
||||
$diagIdList = array_keys($diagIds);
|
||||
if ($diagIdList === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$offsetRows = Diagnosis::whereIn('id', $diagIdList)
|
||||
->whereNull('delete_time')
|
||||
->column('revisit_slot_start_offset', 'id');
|
||||
$offsetMap = [];
|
||||
foreach ($offsetRows as $id => $offset) {
|
||||
$offsetMap[(int) $id] = max(0, min(20, (int) $offset));
|
||||
}
|
||||
|
||||
/** @var array<int, int> $seqByOrderId order_id => global seq within diagnosis */
|
||||
$seqByOrderId = [];
|
||||
foreach ($diagIdList as $did) {
|
||||
$q = PrescriptionOrder::where('diagnosis_id', $did)->whereNull('delete_time');
|
||||
YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($q, '');
|
||||
$orderIds = $q
|
||||
->order(['create_time' => 'asc', 'id' => 'asc'])
|
||||
->column('id');
|
||||
$seq = 0;
|
||||
foreach ($orderIds as $oid) {
|
||||
$seq++;
|
||||
$seqByOrderId[(int) $oid] = $seq;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($lists as &$row) {
|
||||
$oid = (int) ($row['id'] ?? 0);
|
||||
$did = (int) ($row['diagnosis_id'] ?? 0);
|
||||
$seq = (int) ($seqByOrderId[$oid] ?? 0);
|
||||
$offset = (int) ($offsetMap[$did] ?? 0);
|
||||
$effectiveSlot = $seq > 0 ? $seq + $offset : 0;
|
||||
$row['global_visit_seq'] = $effectiveSlot > 0 ? $effectiveSlot : null;
|
||||
$row['raw_visit_seq'] = $seq > 0 ? $seq : null;
|
||||
$row['counts_for_revisit_rate'] = $effectiveSlot >= 2 ? 1 : 0;
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表行标注:指派日志快照(related_po_creator_id + related_po_create_time)是否指向本业务单,
|
||||
* 以及该次操作的新医助(to_assistant_id)是否归属「二中心」部门子树(与 DeptLogic / 业绩看板一致)。
|
||||
|
||||
@@ -151,24 +151,6 @@ class DeptLogic extends BaseLogic
|
||||
* @return list<int>
|
||||
*/
|
||||
private static function findErCenterRootDeptIds(): array
|
||||
{
|
||||
return self::findCenterRootDeptIdsByNameKeyword('二中心');
|
||||
}
|
||||
|
||||
/**
|
||||
* 名称含「一中心」的部门 id。
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
private static function findYiCenterRootDeptIds(): array
|
||||
{
|
||||
return self::findCenterRootDeptIdsByNameKeyword('一中心');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
private static function findCenterRootDeptIdsByNameKeyword(string $keyword): array
|
||||
{
|
||||
$rows = Dept::whereNull('delete_time')
|
||||
->field(['id', 'name'])
|
||||
@@ -177,7 +159,7 @@ class DeptLogic extends BaseLogic
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$name = (string) ($r['name'] ?? '');
|
||||
if ($name !== '' && mb_strpos($name, $keyword) !== false) {
|
||||
if ($name !== '' && mb_strpos($name, '二中心') !== false) {
|
||||
$out[] = (int) $r['id'];
|
||||
}
|
||||
}
|
||||
@@ -219,19 +201,6 @@ class DeptLogic extends BaseLogic
|
||||
return array_fill_keys($subtreeIds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 名称含「一中心」的部门及其全部下级 id(map)。
|
||||
*
|
||||
* @return array<int, true>
|
||||
*/
|
||||
public static function getYiCenterSubtreeDeptIdSet(): array
|
||||
{
|
||||
$yiRoots = self::findYiCenterRootDeptIds();
|
||||
$subtreeIds = self::unionErCenterSubtreeDeptIds($yiRoots);
|
||||
|
||||
return array_fill_keys($subtreeIds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 二中心复诊统计用的业务订单行(与 rollup 同源 SQL)。
|
||||
*
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
@@ -13,11 +12,9 @@ use think\facade\Db;
|
||||
* 口径说明:
|
||||
* - 当月被指派总数 = 当月内 `tcm_diagnosis_assign_log`(按 **指派操作时间 lg.create_time** 落月,to_assistant_id>0,
|
||||
* **剔除勾选「继承」的指派 is_inherit=1**,诊单未删除)去重后的「医助 × 诊单」组合;
|
||||
* 同一诊单当月被多次指派给同一医助只计 1 次;
|
||||
* **再剔除**名下存在履约「拒收(9) / 退款(10)」业务订单(未软删)的诊单。
|
||||
* 同一诊单当月被多次指派给同一医助只计 1 次。
|
||||
* - 第 N 次下单 = 诊单(患者)名下计入业绩的业务订单(剔除履约 4/9/10、软删)按 create_time 升序的**全局**序列中第 N 笔;
|
||||
* 诊次**跨月累计不重置**:例如 5 月指派后旗下成交 4 单为二诊~五诊,6 月再成交即为六诊。
|
||||
* 诊单可配置 `revisit_slot_start_offset`(默认 0:第 1 笔实单计为一诊;设为 1 则第 1 笔实单计为二诊;设为 2 则计为三诊,即在实单序号上叠加偏移,5 笔实单+偏移 2 等价于计至七诊)。
|
||||
* - 当月 N 诊单数 = **当月内下单**且全局序号为 N 的订单数,归属下单时点的**持有医助**——
|
||||
* 按指派日志时间线取「订单时间之前最近一次指派」的 to_assistant_id(释放 to=0 即不再归属;
|
||||
* 「继承」指派会转移持有人用于归属,但不计被指派数)。指派可发生在往月。
|
||||
@@ -25,8 +22,7 @@ use think\facade\Db;
|
||||
* 医助当月无新指派但旗下有成交时,被指派数为 0、比率显示为空。
|
||||
* - 分档动态产出:N 从 2 起,至当月命中数据的最大序号(至少展示到四诊,上限 MAX_VISIT_SLOT 防御异常数据),
|
||||
* 返回 `slots` 列表供前端动态渲染「五诊」「六诊」… 列。
|
||||
* - 部门归类:医助按其人事部门(admin_dept 最小 dept_id)归组;**仅统计「二中心」及其组织下级**(与 DeptLogic::getErCenterSubtreeDeptIdSet 一致);
|
||||
* 部门筛选下拉与未选部门时的默认范围均限定在该子树内,选定部门时含其组织下级。
|
||||
* - 部门归类:医助按其人事部门(admin_dept 最小 dept_id)归组;部门筛选(dept_ids,含组织下级)按该归属部门过滤。
|
||||
* - 部门行 / 合计行:被指派数按诊单去重(可能小于下级行相加);N 诊单数为下级行求和(每笔订单唯一归属一名医助)。
|
||||
*/
|
||||
class RevisitRateLogic
|
||||
@@ -298,19 +294,14 @@ class RevisitRateLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门下拉:仅「二中心」及其组织下级(与业绩看板 ErCenter 子树一致)。
|
||||
* 部门下拉(全量未删除部门,前端组树)。
|
||||
*
|
||||
* @return array{rows: list<array{id:int,pid:int,name:string}>}
|
||||
*/
|
||||
public static function deptOptions(): array
|
||||
{
|
||||
$erSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
if ($erSet === []) {
|
||||
return ['rows' => []];
|
||||
}
|
||||
$rows = Db::name('dept')
|
||||
->whereNull('delete_time')
|
||||
->whereIn('id', array_keys($erSet))
|
||||
->field(['id', 'pid', 'name'])
|
||||
->order('sort', 'desc')
|
||||
->order('id', 'asc')
|
||||
@@ -331,9 +322,9 @@ class RevisitRateLogic
|
||||
/**
|
||||
* 核心统计上下文:
|
||||
* 1. 全量指派日志(≤ 月末)构建持有时间线;
|
||||
* 2. 分母:当月非继承指派的「医助 × 诊单」,再剔除名下存在拒收(9)/退款(10) 订单的诊单;
|
||||
* 3. 分子:曾被指派诊单的当月订单,统计诊次 = 实单序号 + 诊单偏移(默认第 1 笔实单为一诊);
|
||||
* 4. 应用部门筛选:默认限定「二中心」子树;选定部门时再收窄到该部门及其下级(且须落在二中心子树内)。
|
||||
* 2. 分母:当月非继承指派的「医助 × 诊单」;
|
||||
* 3. 分子:曾被指派诊单的当月订单按全局序号 ≥2 归属持有医助;
|
||||
* 4. 应用部门筛选(含组织下级)。
|
||||
*
|
||||
* @param array{month?:string,dept_ids?:int[]|string} $params
|
||||
*
|
||||
@@ -384,35 +375,9 @@ class RevisitRateLogic
|
||||
}
|
||||
}
|
||||
|
||||
// 分母:剔除名下存在拒收(9)/退款(10) 业务订单的诊单(与明细 assignLines 同口径)
|
||||
$assignedDiagIds = [];
|
||||
foreach ($diagsByAssistant as $diagSet) {
|
||||
foreach ($diagSet as $did => $_) {
|
||||
$assignedDiagIds[(int) $did] = true;
|
||||
}
|
||||
}
|
||||
$refundRejectDiagSet = self::fetchRefundOrRejectDiagnosisSet(array_keys($assignedDiagIds));
|
||||
if ($refundRejectDiagSet !== []) {
|
||||
foreach ($diagsByAssistant as $aid => $diagSet) {
|
||||
foreach ($diagSet as $did => $_) {
|
||||
if (isset($refundRejectDiagSet[$did])) {
|
||||
unset($diagsByAssistant[$aid][$did]);
|
||||
}
|
||||
}
|
||||
if ($diagsByAssistant[$aid] === []) {
|
||||
unset($diagsByAssistant[$aid]);
|
||||
}
|
||||
}
|
||||
$pairsRaw = array_values(array_filter(
|
||||
$pairsRaw,
|
||||
static fn (array $p): bool => !isset($refundRejectDiagSet[(int) $p['diagnosis_id']])
|
||||
));
|
||||
}
|
||||
|
||||
// 分子:曾被指派诊单的当月订单,统计诊次 = 实单全局序号 + 诊单偏移(默认偏移 0 → 第 1 笔实单为一诊)
|
||||
// 分子:曾被指派诊单的当月订单(全局序号 ≥2),归属下单时点的持有医助
|
||||
/** @var array<int, array<int, list<array<string, mixed>>>> $slotOrdersByAssistant */
|
||||
$slotOrdersByAssistant = [];
|
||||
$offsetMap = self::fetchRevisitSlotStartOffsetMap(array_keys($candidateDiagSet));
|
||||
foreach (array_chunk(array_keys($candidateDiagSet), 2000) as $chunk) {
|
||||
$orderRows = self::fetchOrderSeqRows(
|
||||
$chunk,
|
||||
@@ -422,7 +387,6 @@ class RevisitRateLogic
|
||||
$seq = 0;
|
||||
$ptr = 0;
|
||||
$holder = 0;
|
||||
$offset = 0;
|
||||
foreach ($orderRows as $r) {
|
||||
$did = (int) ($r['diagnosis_id'] ?? 0);
|
||||
if ($did <= 0) {
|
||||
@@ -433,10 +397,8 @@ class RevisitRateLogic
|
||||
$seq = 0;
|
||||
$ptr = 0;
|
||||
$holder = 0;
|
||||
$offset = self::resolveRevisitSlotStartOffset($did, $offsetMap);
|
||||
}
|
||||
$seq++;
|
||||
$effectiveSlot = $seq + $offset;
|
||||
$ct = (int) ($r['create_time'] ?? 0);
|
||||
// 推进时间线指针:订单时间之前(含同刻)最近一次指派的持有人
|
||||
$tl = $timeline[$did] ?? [];
|
||||
@@ -445,27 +407,24 @@ class RevisitRateLogic
|
||||
$holder = (int) $tl[$ptr]['to'];
|
||||
$ptr++;
|
||||
}
|
||||
if ($effectiveSlot < 2 || $effectiveSlot > self::MAX_VISIT_SLOT) {
|
||||
if ($seq < 2 || $seq > self::MAX_VISIT_SLOT) {
|
||||
continue;
|
||||
}
|
||||
if ($ct < $startTs || $ct > $endTs) {
|
||||
continue;
|
||||
}
|
||||
if ($holder > 0) {
|
||||
$slotOrdersByAssistant[$holder][$effectiveSlot][] = $r;
|
||||
$slotOrdersByAssistant[$holder][$seq][] = $r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 医助归属部门 + 部门筛选(默认仅二中心子树;选定部门时再收窄,含组织下级)
|
||||
// 医助归属部门 + 部门筛选(含组织下级)
|
||||
$universeIds = array_keys($diagsByAssistant + $slotOrdersByAssistant);
|
||||
[$assistantDept, $deptNames] = self::buildAssistantDeptIndex($universeIds);
|
||||
$subtreeSet = self::resolveDeptFilterSet($params['dept_ids'] ?? null);
|
||||
if ($subtreeSet === []) {
|
||||
// 无二中心部门时整表为空,避免误展示其它中心数据
|
||||
$diagsByAssistant = [];
|
||||
$slotOrdersByAssistant = [];
|
||||
} else {
|
||||
$deptFilterIds = self::parseDeptIds($params['dept_ids'] ?? null);
|
||||
if ($deptFilterIds !== []) {
|
||||
$subtreeSet = self::expandDeptSubtreeSet($deptFilterIds);
|
||||
foreach ($universeIds as $aid) {
|
||||
$deptId = (int) ($assistantDept[$aid] ?? 0);
|
||||
if ($deptId <= 0 || !isset($subtreeSet[$deptId])) {
|
||||
@@ -584,45 +543,6 @@ class RevisitRateLogic
|
||||
return [$canonical, $names];
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门筛选集合:始终落在「二中心」子树内。
|
||||
* - 未传 dept_ids:整棵二中心子树
|
||||
* - 已传:所选部门及其下级 ∩ 二中心子树(非法/非二中心 id 被忽略)
|
||||
*
|
||||
* @param mixed $raw
|
||||
*
|
||||
* @return array<int, true>
|
||||
*/
|
||||
private static function resolveDeptFilterSet(mixed $raw): array
|
||||
{
|
||||
$erSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
if ($erSet === []) {
|
||||
return [];
|
||||
}
|
||||
$deptFilterIds = self::parseDeptIds($raw);
|
||||
if ($deptFilterIds === []) {
|
||||
return $erSet;
|
||||
}
|
||||
$allowedRoots = [];
|
||||
foreach ($deptFilterIds as $id) {
|
||||
if (isset($erSet[$id])) {
|
||||
$allowedRoots[] = $id;
|
||||
}
|
||||
}
|
||||
if ($allowedRoots === []) {
|
||||
return [];
|
||||
}
|
||||
$expanded = self::expandDeptSubtreeSet($allowedRoots);
|
||||
$out = [];
|
||||
foreach ($expanded as $id => $_) {
|
||||
if (isset($erSet[$id])) {
|
||||
$out[$id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw int[] | 逗号分隔字符串
|
||||
*
|
||||
@@ -676,35 +596,6 @@ class RevisitRateLogic
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* 名下存在履约「拒收(9) / 退款(10)」业务订单(未软删)的诊单集合。
|
||||
* 用于「当月被指派总数」分母过滤;不限订单创建月份。
|
||||
*
|
||||
* @param list<int> $diagIds
|
||||
*
|
||||
* @return array<int, true>
|
||||
*/
|
||||
private static function fetchRefundOrRejectDiagnosisSet(array $diagIds): array
|
||||
{
|
||||
if ($diagIds === []) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (array_chunk($diagIds, 2000) as $chunk) {
|
||||
$ids = Db::name('tcm_prescription_order')
|
||||
->whereIn('diagnosis_id', $chunk)
|
||||
->whereNull('delete_time')
|
||||
->whereIn('fulfillment_status', [9, 10])
|
||||
->group('diagnosis_id')
|
||||
->column('diagnosis_id');
|
||||
foreach ($ids as $id) {
|
||||
$out[(int) $id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单订单序列源查询(与业绩口径一致),统一排序保证序号稳定。
|
||||
*
|
||||
@@ -728,48 +619,6 @@ class RevisitRateLogic
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $diagIds
|
||||
*
|
||||
* @return array<int, int> diagnosis_id => revisit_slot_start_offset
|
||||
*/
|
||||
private static function fetchRevisitSlotStartOffsetMap(array $diagIds): array
|
||||
{
|
||||
if ($diagIds === []) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (array_chunk($diagIds, 2000) as $chunk) {
|
||||
$rows = Db::name('tcm_diagnosis')
|
||||
->whereIn('id', $chunk)
|
||||
->whereNull('delete_time')
|
||||
->column('revisit_slot_start_offset', 'id');
|
||||
foreach ($rows as $id => $offset) {
|
||||
$out[(int) $id] = (int) $offset;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单复诊统计起始偏移(默认 0:第 1 笔实单计为一诊;统计诊次 = 实单序号 + 偏移)
|
||||
*
|
||||
* @param array<int, int> $offsetMap
|
||||
*/
|
||||
private static function resolveRevisitSlotStartOffset(int $diagId, array $offsetMap): int
|
||||
{
|
||||
$offset = (int) ($offsetMap[$diagId] ?? 0);
|
||||
if ($offset < 0) {
|
||||
$offset = 0;
|
||||
}
|
||||
if ($offset > 20) {
|
||||
$offset = 20;
|
||||
}
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $diagIds
|
||||
*
|
||||
|
||||
@@ -748,49 +748,6 @@ class DiagnosisLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 诊单指派医助操作记录列表
|
||||
*/
|
||||
public static function setRevisitSlotStartOffset(int $diagnosisId, int $offset, array $adminInfo): bool
|
||||
{
|
||||
self::$error = '';
|
||||
if ($diagnosisId <= 0) {
|
||||
self::setError('诊单不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($offset < 0 || $offset > 20) {
|
||||
self::setError('起始偏移须在 0~20 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
$diagnosis = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->find();
|
||||
if (!$diagnosis) {
|
||||
self::setError('诊单不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
$old = (int) ($diagnosis->revisit_slot_start_offset ?? 0);
|
||||
if ($old < 0) {
|
||||
$old = 0;
|
||||
}
|
||||
if ($old > 20) {
|
||||
$old = 20;
|
||||
}
|
||||
if ($old === $offset) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
$diagnosis->save(['revisit_slot_start_offset' => $offset]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 诊单指派医助操作记录列表
|
||||
*/
|
||||
|
||||
@@ -2375,123 +2375,6 @@ class PrescriptionOrderLogic
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手工新增操作日志;可选单独调整处方审核 / 支付单审核状态(不触发常规审核流程副作用)
|
||||
*
|
||||
* @param array<string,mixed> $params id, summary, prescription_audit_status?, payment_slip_audit_status?, prescription_audit_remark?, payment_slip_audit_remark?
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public static function addLog(array $params, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
$id = (int) ($params['id'] ?? 0);
|
||||
$summary = mb_substr(trim((string) ($params['summary'] ?? '')), 0, 500);
|
||||
if ($summary === '') {
|
||||
self::$error = '请填写日志内容';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::$error = '订单不存在';
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!self::canAccessOrder($order, $adminId, $adminInfo)) {
|
||||
self::$error = '无权限操作';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$changeParts = [];
|
||||
$hasRxChange = array_key_exists('prescription_audit_status', $params)
|
||||
&& $params['prescription_audit_status'] !== ''
|
||||
&& $params['prescription_audit_status'] !== null;
|
||||
$hasPayChange = array_key_exists('payment_slip_audit_status', $params)
|
||||
&& $params['payment_slip_audit_status'] !== ''
|
||||
&& $params['payment_slip_audit_status'] !== null;
|
||||
|
||||
if ($hasRxChange) {
|
||||
if (!self::canAuditPrescriptionOrder($adminInfo)) {
|
||||
self::$error = '无处方审核权限,不能调整处方审核状态';
|
||||
|
||||
return false;
|
||||
}
|
||||
$newRx = (int) $params['prescription_audit_status'];
|
||||
if (!in_array($newRx, [0, 1, 2], true)) {
|
||||
self::$error = '处方审核状态无效';
|
||||
|
||||
return false;
|
||||
}
|
||||
$oldRx = (int) $order->prescription_audit_status;
|
||||
if ($newRx !== $oldRx) {
|
||||
$order->prescription_audit_status = $newRx;
|
||||
$changeParts[] = '处方审核:' . self::auditStatusLabelForLog($oldRx)
|
||||
. ' → ' . self::auditStatusLabelForLog($newRx);
|
||||
}
|
||||
if (array_key_exists('prescription_audit_remark', $params)) {
|
||||
$order->prescription_audit_remark = mb_substr(trim((string) $params['prescription_audit_remark']), 0, 500);
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasPayChange) {
|
||||
if (!self::canAuditPaymentSlipOrder($adminInfo)) {
|
||||
self::$error = '无支付单审核权限,不能调整支付单审核状态';
|
||||
|
||||
return false;
|
||||
}
|
||||
$newPay = (int) $params['payment_slip_audit_status'];
|
||||
if (!in_array($newPay, [0, 1, 2], true)) {
|
||||
self::$error = '支付单审核状态无效';
|
||||
|
||||
return false;
|
||||
}
|
||||
$oldPay = (int) $order->payment_slip_audit_status;
|
||||
if ($newPay !== $oldPay) {
|
||||
$order->payment_slip_audit_status = $newPay;
|
||||
$changeParts[] = '支付单审核:' . self::auditStatusLabelForLog($oldPay)
|
||||
. ' → ' . self::auditStatusLabelForLog($newPay);
|
||||
}
|
||||
if (array_key_exists('payment_slip_audit_remark', $params)) {
|
||||
$order->payment_slip_audit_remark = mb_substr(trim((string) $params['payment_slip_audit_remark']), 0, 500);
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasRxChange || $hasPayChange) {
|
||||
self::syncFulfillmentStatus($order);
|
||||
try {
|
||||
$order->save();
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$logSummary = $summary;
|
||||
if ($changeParts !== []) {
|
||||
$logSummary .= '(' . implode(';', $changeParts) . ')';
|
||||
}
|
||||
self::writeLog($id, $adminId, $adminInfo, 'manual_log', $logSummary);
|
||||
|
||||
$out = $order->toArray();
|
||||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||||
self::maskRemarkExtraIfNeeded($out, $adminInfo);
|
||||
self::attachLinkedPayOrders($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function auditStatusLabelForLog(int $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
1 => '已通过',
|
||||
2 => '已驳回',
|
||||
default => '待审核',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 为「已发货/已签收」(fulfillment_status=5/6) 的业务订单新增一条关联支付单(zyt_order),
|
||||
* 创建后将支付单链接到业务订单,并将处方/支付审核状态重置为待审核以启动再次审核流程。
|
||||
@@ -3224,207 +3107,6 @@ class PrescriptionOrderLogic
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:处方药材明细(主方/辅方分行,与处方笺一致)
|
||||
*
|
||||
* @param array<string, mixed> $rx
|
||||
*/
|
||||
public static function formatPrescriptionHerbsForExport(array $rx): string
|
||||
{
|
||||
[$mainHerbs, $auxHerbs] = self::splitPrescriptionHerbsFromRx($rx);
|
||||
$formatList = static function (array $herbs): string {
|
||||
$parts = [];
|
||||
foreach ($herbs as $h) {
|
||||
if (!\is_array($h)) {
|
||||
continue;
|
||||
}
|
||||
$name = trim((string) ($h['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$parts[] = $name . ' ' . self::formatExportDosageNumber((float) ($h['dosage'] ?? 0)) . 'g';
|
||||
}
|
||||
|
||||
return implode('、', $parts);
|
||||
};
|
||||
|
||||
$sections = [];
|
||||
$mainText = $formatList($mainHerbs);
|
||||
if ($mainText !== '') {
|
||||
$sections[] = '主方:' . $mainText;
|
||||
}
|
||||
$auxText = $formatList($auxHerbs);
|
||||
if ($auxText !== '') {
|
||||
$sections[] = '辅方:' . $auxText;
|
||||
}
|
||||
|
||||
return implode("\n", $sections);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:主方/辅方服用方式(与前端 buildUsageSegmentText / 处方笺同口径)
|
||||
*
|
||||
* @param array<string, mixed> $usage
|
||||
*/
|
||||
public static function formatUsageSegmentForExport(
|
||||
array $usage,
|
||||
string $prescriptionType = '浓缩水丸',
|
||||
string $fallbackWay = '',
|
||||
string $fallbackTime = ''
|
||||
): string {
|
||||
$pt = trim($prescriptionType) !== '' ? trim($prescriptionType) : '浓缩水丸';
|
||||
$times = (int) ($usage['times_per_day'] ?? 0);
|
||||
if ($times <= 0) {
|
||||
$times = 3;
|
||||
}
|
||||
$amount = isset($usage['dosage_amount']) && $usage['dosage_amount'] !== '' && $usage['dosage_amount'] !== null
|
||||
? (float) $usage['dosage_amount']
|
||||
: 10.0;
|
||||
$unit = trim((string) ($usage['usage_dosage_unit'] ?? ($usage['dosage_unit'] ?? '')));
|
||||
if ($unit === '') {
|
||||
$unit = $pt === '饮片' ? 'ml' : 'g';
|
||||
}
|
||||
$usageWay = trim((string) ($usage['usage_way'] ?? ''));
|
||||
if ($usageWay === '') {
|
||||
$usageWay = $fallbackWay !== '' ? $fallbackWay : '温水送服';
|
||||
}
|
||||
$usageTime = trim((string) ($usage['usage_time'] ?? ''));
|
||||
if ($usageTime === '') {
|
||||
$usageTime = $fallbackTime;
|
||||
}
|
||||
|
||||
$seg = ['每天' . $times . '次'];
|
||||
if ($pt === '浓缩水丸') {
|
||||
$bags = (int) ($usage['dosage_bag_count'] ?? 0);
|
||||
if ($bags <= 0) {
|
||||
$bags = 1;
|
||||
}
|
||||
$seg[] = '一次' . $bags . '袋';
|
||||
$seg[] = '每袋' . self::formatExportDosageNumber($amount) . $unit;
|
||||
} else {
|
||||
$seg[] = '一次' . self::formatExportDosageNumber($amount) . $unit;
|
||||
}
|
||||
$seg[] = $usageWay;
|
||||
if ($usageTime !== '') {
|
||||
$seg[] = $usageTime;
|
||||
}
|
||||
|
||||
return implode(', ', $seg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:辅方用法 JSON 规范化(与前端 normalizeSlipAuxUsageForm 默认值一致)
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function normalizeAuxUsageForExport($raw, string $prescriptionType): array
|
||||
{
|
||||
$pt = trim($prescriptionType) !== '' ? trim($prescriptionType) : '浓缩水丸';
|
||||
if ($pt === '饮片') {
|
||||
$base = [
|
||||
'dosage_amount' => 50.0,
|
||||
'dosage_bag_count' => 1,
|
||||
'times_per_day' => 3,
|
||||
'usage_days' => 7,
|
||||
];
|
||||
} elseif ($pt === '浓缩水丸') {
|
||||
$base = [
|
||||
'dosage_amount' => 5.0,
|
||||
'dosage_bag_count' => 1,
|
||||
'times_per_day' => 3,
|
||||
'usage_days' => 7,
|
||||
];
|
||||
} else {
|
||||
$base = [
|
||||
'dosage_amount' => 1.0,
|
||||
'dosage_bag_count' => 1,
|
||||
'times_per_day' => 3,
|
||||
'usage_days' => 7,
|
||||
];
|
||||
}
|
||||
|
||||
if (\is_string($raw) && $raw !== '') {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = \is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
if (!\is_array($raw)) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
return [
|
||||
'dosage_amount' => isset($raw['dosage_amount']) && $raw['dosage_amount'] !== '' && $raw['dosage_amount'] !== null
|
||||
? (float) $raw['dosage_amount']
|
||||
: $base['dosage_amount'],
|
||||
'dosage_bag_count' => (int) ($raw['dosage_bag_count'] ?? 0) > 0
|
||||
? (int) $raw['dosage_bag_count']
|
||||
: $base['dosage_bag_count'],
|
||||
'times_per_day' => (int) ($raw['times_per_day'] ?? 0) > 0
|
||||
? (int) $raw['times_per_day']
|
||||
: $base['times_per_day'],
|
||||
'usage_days' => (int) ($raw['usage_days'] ?? 0) > 0
|
||||
? (int) $raw['usage_days']
|
||||
: $base['usage_days'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>}
|
||||
*/
|
||||
private static function splitPrescriptionHerbsFromRx(array $rx): array
|
||||
{
|
||||
$herbs = $rx['herbs'] ?? null;
|
||||
if (\is_string($herbs) && $herbs !== '') {
|
||||
$decoded = json_decode($herbs, true);
|
||||
$herbs = \is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!\is_array($herbs)) {
|
||||
$herbs = [];
|
||||
}
|
||||
|
||||
$mainHerbs = [];
|
||||
$auxHerbs = [];
|
||||
foreach ($herbs as $h) {
|
||||
if (!\is_array($h)) {
|
||||
continue;
|
||||
}
|
||||
if (((string) ($h['formula_type'] ?? '')) === '辅方') {
|
||||
$auxHerbs[] = $h;
|
||||
} else {
|
||||
$mainHerbs[] = $h;
|
||||
}
|
||||
}
|
||||
|
||||
return [$mainHerbs, $auxHerbs];
|
||||
}
|
||||
|
||||
private static function formatExportDosageNumber(float $dosage): string
|
||||
{
|
||||
if (floor($dosage) === $dosage) {
|
||||
return (string) (int) $dosage;
|
||||
}
|
||||
|
||||
return rtrim(rtrim(number_format($dosage, 4, '.', ''), '0'), '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:主方/辅方开立天数(与详情侧栏「处方开立」同口径:主方取处方 usage_days,辅方取 aux_usage.usage_days)
|
||||
* 订单服用天数单独导出在 export_medication_days 列,不在此混用。
|
||||
*
|
||||
* @param array<string, mixed> $rx
|
||||
* @param array<string, mixed>|null $auxUsage
|
||||
*/
|
||||
private static function resolveExportUsageDays(array $rx, ?array $auxUsage, bool $isAux): string
|
||||
{
|
||||
if ($isAux) {
|
||||
$days = (int) ($auxUsage['usage_days'] ?? 0);
|
||||
|
||||
return $days > 0 ? (string) $days : '';
|
||||
}
|
||||
$days = (int) ($rx['usage_days'] ?? 0);
|
||||
|
||||
return $days > 0 ? (string) $days : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出列:挂号表渠道来源展示(与 AppointmentLists channel_source_desc 同字典口径)
|
||||
*
|
||||
@@ -3721,12 +3403,7 @@ class PrescriptionOrderLogic
|
||||
$rxById = [];
|
||||
if ($rxIdList !== []) {
|
||||
$rxRows = Prescription::whereIn('id', $rxIdList)->whereNull('delete_time')
|
||||
->field([
|
||||
'id', 'prescription_type', 'need_decoction', 'dose_unit', 'assistant_id', 'appointment_id',
|
||||
'prescription_name', 'aux_usage', 'herbs', 'creator_id',
|
||||
'dosage_amount', 'dosage_unit', 'dosage_bag_count', 'times_per_day', 'usage_days',
|
||||
'usage_way', 'usage_time',
|
||||
])
|
||||
->field(['id', 'prescription_type', 'need_decoction', 'dose_unit', 'assistant_id', 'appointment_id', 'prescription_name', 'aux_usage', 'herbs', 'creator_id'])
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rxRows as $xr) {
|
||||
@@ -3960,38 +3637,6 @@ class PrescriptionOrderLogic
|
||||
}
|
||||
$item['export_prescription_name'] = implode(' ', $rxNameParts);
|
||||
|
||||
$rxArr = \is_array($rx) ? $rx : [];
|
||||
$rxType = trim((string) ($rxArr['prescription_type'] ?? '')) ?: '浓缩水丸';
|
||||
$item['export_prescription_herbs'] = self::formatPrescriptionHerbsForExport($rxArr);
|
||||
$item['export_main_usage'] = $rxArr !== []
|
||||
? self::formatUsageSegmentForExport($rxArr, $rxType)
|
||||
: '';
|
||||
[, $auxHerbs] = self::splitPrescriptionHerbsFromRx($rxArr);
|
||||
$auxUsageNorm = $auxHerbs !== []
|
||||
? self::normalizeAuxUsageForExport($rxArr['aux_usage'] ?? null, $rxType)
|
||||
: null;
|
||||
if ($auxHerbs !== [] && $auxUsageNorm !== null) {
|
||||
$item['export_aux_usage'] = self::formatUsageSegmentForExport(
|
||||
[
|
||||
'dosage_amount' => $auxUsageNorm['dosage_amount'],
|
||||
'dosage_bag_count' => $auxUsageNorm['dosage_bag_count'],
|
||||
'times_per_day' => $auxUsageNorm['times_per_day'],
|
||||
'usage_dosage_unit' => $rxArr['dosage_unit'] ?? '',
|
||||
'usage_way' => $rxArr['usage_way'] ?? '',
|
||||
'usage_time' => $rxArr['usage_time'] ?? '',
|
||||
],
|
||||
$rxType,
|
||||
(string) ($rxArr['usage_way'] ?? ''),
|
||||
(string) ($rxArr['usage_time'] ?? '')
|
||||
);
|
||||
} else {
|
||||
$item['export_aux_usage'] = '';
|
||||
}
|
||||
$item['export_main_usage_days'] = self::resolveExportUsageDays($rxArr, $auxUsageNorm, false);
|
||||
$item['export_aux_usage_days'] = $auxHerbs !== []
|
||||
? self::resolveExportUsageDays($rxArr, $auxUsageNorm, true)
|
||||
: '';
|
||||
|
||||
$item['export_service_package'] = self::formatServicePackageForExport(
|
||||
$item['service_package'] ?? '',
|
||||
$packageNameByValue
|
||||
@@ -4162,7 +3807,27 @@ class PrescriptionOrderLogic
|
||||
|
||||
$doctorId = (int) ($rx['creator_id'] ?? 0);
|
||||
|
||||
[$mainHerbs, $auxHerbs] = self::splitPrescriptionHerbsFromRx($rx);
|
||||
$herbs = $rx['herbs'] ?? null;
|
||||
if (\is_string($herbs) && $herbs !== '') {
|
||||
$decoded = json_decode($herbs, true);
|
||||
$herbs = \is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!\is_array($herbs)) {
|
||||
$herbs = [];
|
||||
}
|
||||
|
||||
$mainHerbs = [];
|
||||
$auxHerbs = [];
|
||||
foreach ($herbs as $h) {
|
||||
if (!\is_array($h)) {
|
||||
continue;
|
||||
}
|
||||
if (((string) ($h['formula_type'] ?? '')) === '辅方') {
|
||||
$auxHerbs[] = $h;
|
||||
} else {
|
||||
$mainHerbs[] = $h;
|
||||
}
|
||||
}
|
||||
|
||||
$lookup = static function (string $ft, array $hs) use ($doctorId, $libByDoctor, $libPublic): string {
|
||||
if ($hs === []) {
|
||||
@@ -4881,175 +4546,4 @@ class PrescriptionOrderLogic
|
||||
{
|
||||
self::writeLog($prescriptionOrderId, $adminId, $adminInfo, 'patch_rx_patient', $summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务订单详情场景:更新主方/辅方服用次数与开立天数,以及订单服用天数
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public static function patchPrescriptionUsage(array $params, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
self::$error = '';
|
||||
$prescriptionOrderId = (int) ($params['id'] ?? 0);
|
||||
$order = PrescriptionOrder::where('id', $prescriptionOrderId)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::setError('订单不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!self::canAccessOrder($order, $adminId, $adminInfo)) {
|
||||
self::setError('无权限操作');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ((int) $order->fulfillment_status === 4) {
|
||||
self::setError('已取消的订单不可修改');
|
||||
|
||||
return false;
|
||||
}
|
||||
$rxId = (int) ($order->prescription_id ?? 0);
|
||||
if ($rxId <= 0) {
|
||||
self::setError('该订单未关联处方');
|
||||
|
||||
return false;
|
||||
}
|
||||
$rx = Prescription::where('id', $rxId)->whereNull('delete_time')->find();
|
||||
if (!$rx) {
|
||||
self::setError('处方不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!PrescriptionLogic::canViewPrescription($rx, $adminId, $adminInfo)) {
|
||||
self::setError('无权限修改此处方');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$timesPerDay = (int) ($params['times_per_day'] ?? 0);
|
||||
$usageDays = (int) ($params['usage_days'] ?? 0);
|
||||
$medDays = (int) ($params['medication_days'] ?? 0);
|
||||
if ($timesPerDay < 1 || $timesPerDay > 6) {
|
||||
self::setError('主方每天次数须在 1~6 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($usageDays < 1 || $usageDays > 999) {
|
||||
self::setError('主方开立天数须在 1~999 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($medDays < 1 || $medDays > 999) {
|
||||
self::setError('订单服用天数须在 1~999 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasAux = self::prescriptionHasAuxFormula($rx);
|
||||
$auxTimesPerDay = null;
|
||||
$auxUsageDays = null;
|
||||
if ($hasAux) {
|
||||
if (!array_key_exists('aux_times_per_day', $params) || !array_key_exists('aux_usage_days', $params)) {
|
||||
self::setError('含辅方处方须填写辅方服用参数');
|
||||
|
||||
return false;
|
||||
}
|
||||
$auxTimesPerDay = (int) $params['aux_times_per_day'];
|
||||
$auxUsageDays = (int) $params['aux_usage_days'];
|
||||
if ($auxTimesPerDay < 1 || $auxTimesPerDay > 6) {
|
||||
self::setError('辅方每天次数须在 1~6 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($auxUsageDays < 1 || $auxUsageDays > 999) {
|
||||
self::setError('辅方开立天数须在 1~999 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$oldTimes = (int) ($rx->times_per_day ?? 0);
|
||||
$oldUsageDays = (int) ($rx->usage_days ?? 0);
|
||||
$oldMedDays = (int) ($order->medication_days ?? 0);
|
||||
$oldAuxUsage = $rx->aux_usage;
|
||||
if (is_string($oldAuxUsage)) {
|
||||
$decoded = json_decode($oldAuxUsage, true);
|
||||
$oldAuxUsage = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($oldAuxUsage)) {
|
||||
$oldAuxUsage = [];
|
||||
}
|
||||
$oldAuxTimes = (int) ($oldAuxUsage['times_per_day'] ?? 0);
|
||||
$oldAuxUsageDays = (int) ($oldAuxUsage['usage_days'] ?? 0);
|
||||
|
||||
try {
|
||||
$rxUpdates = [
|
||||
'times_per_day' => $timesPerDay,
|
||||
'usage_days' => $usageDays,
|
||||
];
|
||||
if ($hasAux) {
|
||||
$auxUsage = $oldAuxUsage;
|
||||
$auxUsage['times_per_day'] = $auxTimesPerDay;
|
||||
$auxUsage['usage_days'] = $auxUsageDays;
|
||||
$rxUpdates['aux_usage'] = $auxUsage;
|
||||
}
|
||||
$rx->save($rxUpdates);
|
||||
|
||||
$order->medication_days = $medDays;
|
||||
$order->save();
|
||||
|
||||
$parts = [
|
||||
sprintf(
|
||||
'主方 每天%d次/开立%d天 → 每天%d次/开立%d天',
|
||||
$oldTimes > 0 ? $oldTimes : 0,
|
||||
$oldUsageDays > 0 ? $oldUsageDays : 0,
|
||||
$timesPerDay,
|
||||
$usageDays
|
||||
),
|
||||
];
|
||||
if ($hasAux) {
|
||||
$parts[] = sprintf(
|
||||
'辅方 每天%d次/开立%d天 → 每天%d次/开立%d天',
|
||||
$oldAuxTimes > 0 ? $oldAuxTimes : 0,
|
||||
$oldAuxUsageDays > 0 ? $oldAuxUsageDays : 0,
|
||||
$auxTimesPerDay,
|
||||
$auxUsageDays
|
||||
);
|
||||
}
|
||||
$parts[] = sprintf(
|
||||
'订单设置 %d天 → %d天',
|
||||
$oldMedDays > 0 ? $oldMedDays : 0,
|
||||
$medDays
|
||||
);
|
||||
$summary = '服用参数:' . implode(';', $parts);
|
||||
self::writeLog($prescriptionOrderId, $adminId, $adminInfo, 'patch_rx_usage', $summary);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处方是否含辅方药材(与列表/详情 has_aux_formula 口径一致)
|
||||
*/
|
||||
private static function prescriptionHasAuxFormula(Prescription $rx): bool
|
||||
{
|
||||
$herbs = $rx->herbs;
|
||||
if (is_string($herbs)) {
|
||||
$decoded = json_decode($herbs, true);
|
||||
$herbs = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($herbs)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($herbs as $h) {
|
||||
if (is_array($h) && (string) ($h['formula_type'] ?? '') === '辅方') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ class DiagnosisValidate extends BaseValidate
|
||||
'end_date' => 'date|checkDateRange',
|
||||
'diagnosis_id' => 'require|integer|checkDiagnosisId',
|
||||
'tracking_content' => 'require|length:1,1000',
|
||||
'revisit_slot_start_offset' => 'integer|between:0,20',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
@@ -140,13 +139,6 @@ class DiagnosisValidate extends BaseValidate
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
/** 业务订单 tab:设置复诊接诊率统计起始偏移 */
|
||||
public function sceneSetRevisitSlotStartOffset()
|
||||
{
|
||||
return $this->only(['id', 'revisit_slot_start_offset'])
|
||||
->append('revisit_slot_start_offset', 'require|integer|between:0,20');
|
||||
}
|
||||
|
||||
protected function checkDiagnosis($value)
|
||||
{
|
||||
$diagnosis = Diagnosis::findOrEmpty($value);
|
||||
|
||||
@@ -32,11 +32,6 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
'remark_assistant' => 'max:500',
|
||||
'action' => 'require|in:approve,reject',
|
||||
'remark' => 'max:500',
|
||||
'summary' => 'require|max:500',
|
||||
'prescription_audit_status' => 'in:0,1,2',
|
||||
'payment_slip_audit_status' => 'in:0,1,2',
|
||||
'prescription_audit_remark' => 'max:500',
|
||||
'payment_slip_audit_remark' => 'max:500',
|
||||
'fulfillment_status' => 'require|integer|in:3,7,8,9,11,12',
|
||||
'reason' => 'require|max:500',
|
||||
'refund_amount' => 'float|egt:0',
|
||||
@@ -79,7 +74,6 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
'withdraw' => ['id'],
|
||||
'ship' => ['id', 'tracking_number', 'express_company', 'ship_mode'],
|
||||
'logs' => ['id'],
|
||||
'addLog' => ['id', 'summary'],
|
||||
'paidPayOrders' => ['diagnosis_id'],
|
||||
'addPayOrder' => ['id', 'order_type', 'pay_amount', 'pay_remark'],
|
||||
'linkPayOrder' => ['id', 'pay_order_id'],
|
||||
@@ -89,7 +83,6 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
'submitGancaoRecipel' => ['id'],
|
||||
'previewGancaoRecipel' => ['id'],
|
||||
'patchPrescriptionPatient' => ['id', 'patient_name', 'phone'],
|
||||
'patchPrescriptionUsage' => ['id', 'times_per_day', 'usage_days', 'medication_days', 'aux_times_per_day', 'aux_usage_days'],
|
||||
'updateAmount' => ['id', 'amount'],
|
||||
'setShipMode' => ['id', 'ship_mode'],
|
||||
];
|
||||
@@ -100,15 +93,4 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
->append('id', 'require|integer|gt:0')
|
||||
->append('amount', 'require|float|egt:0');
|
||||
}
|
||||
|
||||
public function patchPrescriptionUsage(): PrescriptionOrderValidate
|
||||
{
|
||||
return $this->only(['id', 'times_per_day', 'usage_days', 'medication_days', 'aux_times_per_day', 'aux_usage_days'])
|
||||
->append('id', 'require|integer|gt:0')
|
||||
->append('times_per_day', 'require|integer|between:1,6')
|
||||
->append('usage_days', 'require|integer|between:1,999')
|
||||
->append('medication_days', 'require|integer|between:1,999')
|
||||
->append('aux_times_per_day', 'integer|between:1,6')
|
||||
->append('aux_usage_days', 'integer|between:1,999');
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB |
+1
-1
@@ -1 +1 @@
|
||||
import r from"./error-DFSD5l9g.js";import{f as p,ak as i,I as m,a as e,aN as s,J as o}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-d3j0BX4t.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const a="/admin/assets/no_perms-jDxcYpYC.png",n={class:"error404"},W=p({__name:"403",setup(c){return(_,t)=>(i(),m("div",n,[e(r,{code:"403",title:"您的账号权限不足,请联系管理员添加权限!","show-btn":!1},{content:s(()=>[...t[0]||(t[0]=[o("div",{class:"flex justify-center"},[o("img",{class:"w-[150px] h-[150px]",src:a,alt:""})],-1)])]),_:1})]))}});export{W as default};
|
||||
import r from"./error-BUBKvVs4.js";import{f as p,ak as i,I as m,a as e,aN as s,J as o}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-B6p-ZV3k.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const a="/admin/assets/no_perms-jDxcYpYC.png",n={class:"error404"},W=p({__name:"403",setup(c){return(_,t)=>(i(),m("div",n,[e(r,{code:"403",title:"您的账号权限不足,请联系管理员添加权限!","show-btn":!1},{content:s(()=>[...t[0]||(t[0]=[o("div",{class:"flex justify-center"},[o("img",{class:"w-[150px] h-[150px]",src:a,alt:""})],-1)])]),_:1})]))}});export{W as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import o from"./error-DFSD5l9g.js";import{f as r,ak as t,I as m,a as p}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-d3j0BX4t.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const i={class:"error404"},T=r({__name:"404",setup(e){return(a,s)=>(t(),m("div",i,[p(o,{code:"404",title:"哎呀,出错了!您访问的页面不存在…"})]))}});export{T as default};
|
||||
import o from"./error-BUBKvVs4.js";import{f as r,ak as t,I as m,a as p}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-B6p-ZV3k.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const i={class:"error404"},T=r({__name:"404",setup(e){return(a,s)=>(t(),m("div",i,[p(o,{code:"404",title:"哎呀,出错了!您访问的页面不存在…"})]))}});export{T as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{M as T,N as C,T as $,r as D,d as L,L as k}from"./element-plus-Bolc0EfP.js";import{Y as E}from"./@element-plus/icons-vue-B0jSCQ-G.js";import{a8 as P}from"./tcm-Dsvdp1dm.js";import{f as A,w as B,ak as p,I as b,aP as F,G as h,aN as n,a as i,O as m,J as M}from"./@vue/runtime-core-C6bnekPw.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{y as V,n as w}from"./@vue/reactivity-DiY1c2vO.js";import{_ as K}from"./index-d3j0BX4t.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const Y={class:"assign-log-panel"},q={key:1,class:"text-gray-400"},z=A({__name:"AssignLogPanel",props:{diagnosisId:{}},setup(N,{expose:v}){const _=N,d=w(!1),u=w([]);function y(o){const e=o.related_po_creator_name;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_creator_id);return Number.isFinite(t)&&t>0?`ID:${t}`:"—"}function x(o){const e=o.related_po_create_time_text;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_create_time);if(!Number.isFinite(t)||t<=0)return"—";const a=new Date(t*1e3);if(Number.isNaN(a.getTime()))return"—";const r=l=>String(l).padStart(2,"0");return`${a.getFullYear()}-${r(a.getMonth()+1)}-${r(a.getDate())} ${r(a.getHours())}:${r(a.getMinutes())}:${r(a.getSeconds())}`}function f(o,e){const t=e==="from"?"from_assistant_name":"to_assistant_name",a=e==="from"?"from_assistant_id":"to_assistant_id",r=o[t];if(r!=null&&String(r).trim()!==""&&String(r)!=="—")return String(r);const l=Number(o[a]);return Number.isFinite(l)&&l>0?`ID:${l}`:"—"}const g=async()=>{if(_.diagnosisId){d.value=!0;try{const o=await P({id:_.diagnosisId}),e=Array.isArray(o)?o:[];u.value=e}catch(o){console.error(o),u.value=[]}finally{d.value=!1}}};return B(()=>_.diagnosisId,()=>{g()},{immediate:!0}),v({refresh:g}),(o,e)=>{const t=C,a=$,r=L,l=D,S=T,I=k;return p(),b("div",Y,[F((p(),h(S,{data:u.value,border:"",stripe:"","empty-text":"暂无指派记录"},{default:n(()=>[i(t,{label:"操作时间",width:"175",prop:"create_time_text"}),i(t,{label:"原医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"from")),1)]),_:1}),i(t,{label:"新医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"to")),1)]),_:1}),i(t,{label:"继承",width:"72",align:"center"},{default:n(({row:s})=>[Number(s.is_inherit)===1?(p(),h(a,{key:0,type:"success",size:"small"},{default:n(()=>[...e[0]||(e[0]=[m("是",-1)])]),_:1})):(p(),b("span",q,"否"))]),_:1}),i(t,{label:"快照·业务单创建人","min-width":"130","show-overflow-tooltip":""},{default:n(({row:s})=>[m(c(y(s)),1)]),_:1}),i(t,{label:"快照·业务单创建时间",width:"190"},{header:n(()=>[e[1]||(e[1]=M("span",null,"快照·业务单创建时间",-1)),i(l,{placement:"top",content:"related_po_create_time:触发本次操作的处方业务订单 create_time;与原文助一致时表示医助创建订单时间"},{default:n(()=>[i(r,{class:"assign-log-col-hint"},{default:n(()=>[i(V(E))]),_:1})]),_:1})]),default:n(({row:s})=>[m(c(x(s)),1)]),_:1}),i(t,{label:"操作人",width:"110",prop:"operator_name"}),i(t,{label:"操作账号",width:"120",prop:"operator_account","show-overflow-tooltip":""}),i(t,{label:"IP",width:"130",prop:"ip","show-overflow-tooltip":""})]),_:1},8,["data"])),[[I,d.value]])])}}}),Ct=K(z,[["__scopeId","data-v-f670e3e6"]]);export{Ct as default};
|
||||
import{M as T,N as C,T as $,r as D,d as L,L as k}from"./element-plus-Bolc0EfP.js";import{Y as E}from"./@element-plus/icons-vue-B0jSCQ-G.js";import{a6 as P}from"./tcm-Ba4j6pRH.js";import{f as A,w as B,ak as p,I as b,aP as F,G as h,aN as n,a as i,O as m,J as M}from"./@vue/runtime-core-C6bnekPw.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{y as V,n as w}from"./@vue/reactivity-DiY1c2vO.js";import{_ as K}from"./index-B6p-ZV3k.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const Y={class:"assign-log-panel"},q={key:1,class:"text-gray-400"},z=A({__name:"AssignLogPanel",props:{diagnosisId:{}},setup(N,{expose:v}){const _=N,d=w(!1),u=w([]);function y(o){const e=o.related_po_creator_name;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_creator_id);return Number.isFinite(t)&&t>0?`ID:${t}`:"—"}function x(o){const e=o.related_po_create_time_text;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_create_time);if(!Number.isFinite(t)||t<=0)return"—";const a=new Date(t*1e3);if(Number.isNaN(a.getTime()))return"—";const r=l=>String(l).padStart(2,"0");return`${a.getFullYear()}-${r(a.getMonth()+1)}-${r(a.getDate())} ${r(a.getHours())}:${r(a.getMinutes())}:${r(a.getSeconds())}`}function f(o,e){const t=e==="from"?"from_assistant_name":"to_assistant_name",a=e==="from"?"from_assistant_id":"to_assistant_id",r=o[t];if(r!=null&&String(r).trim()!==""&&String(r)!=="—")return String(r);const l=Number(o[a]);return Number.isFinite(l)&&l>0?`ID:${l}`:"—"}const g=async()=>{if(_.diagnosisId){d.value=!0;try{const o=await P({id:_.diagnosisId}),e=Array.isArray(o)?o:[];u.value=e}catch(o){console.error(o),u.value=[]}finally{d.value=!1}}};return B(()=>_.diagnosisId,()=>{g()},{immediate:!0}),v({refresh:g}),(o,e)=>{const t=C,a=$,r=L,l=D,S=T,I=k;return p(),b("div",Y,[F((p(),h(S,{data:u.value,border:"",stripe:"","empty-text":"暂无指派记录"},{default:n(()=>[i(t,{label:"操作时间",width:"175",prop:"create_time_text"}),i(t,{label:"原医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"from")),1)]),_:1}),i(t,{label:"新医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"to")),1)]),_:1}),i(t,{label:"继承",width:"72",align:"center"},{default:n(({row:s})=>[Number(s.is_inherit)===1?(p(),h(a,{key:0,type:"success",size:"small"},{default:n(()=>[...e[0]||(e[0]=[m("是",-1)])]),_:1})):(p(),b("span",q,"否"))]),_:1}),i(t,{label:"快照·业务单创建人","min-width":"130","show-overflow-tooltip":""},{default:n(({row:s})=>[m(c(y(s)),1)]),_:1}),i(t,{label:"快照·业务单创建时间",width:"190"},{header:n(()=>[e[1]||(e[1]=M("span",null,"快照·业务单创建时间",-1)),i(l,{placement:"top",content:"related_po_create_time:触发本次操作的处方业务订单 create_time;与原文助一致时表示医助创建订单时间"},{default:n(()=>[i(r,{class:"assign-log-col-hint"},{default:n(()=>[i(V(E))]),_:1})]),_:1})]),default:n(({row:s})=>[m(c(x(s)),1)]),_:1}),i(t,{label:"操作人",width:"110",prop:"operator_name"}),i(t,{label:"操作账号",width:"120",prop:"operator_account","show-overflow-tooltip":""}),i(t,{label:"IP",width:"130",prop:"ip","show-overflow-tooltip":""})]),_:1},8,["data"])),[[I,d.value]])])}}}),Ct=K(z,[["__scopeId","data-v-f670e3e6"]]);export{Ct as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{i as L,L as T,N as V,T as D,M as z,W as M}from"./element-plus-Bolc0EfP.js";import{ag as O}from"./tcm-Dsvdp1dm.js";import{f as P,b as F,w as j,ak as a,I as n,a as i,aN as s,O as m,H as w,aP as A,G as g,F as H,J as R}from"./@vue/runtime-core-C6bnekPw.js";import{y as d,n as k}from"./@vue/reactivity-DiY1c2vO.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{_ as G}from"./index-d3j0BX4t.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const J={class:"case-record-list"},Q={key:0,class:"mb-3 flex justify-end"},W={key:0},Y={key:1,class:"text-gray-400"},q={class:"void-detail text-xs text-gray-500 mt-1"},K=P({__name:"CaseRecordList",props:{diagnosisId:{type:Number,default:0},readOnly:{type:Boolean,default:!1}},emits:["view","openPrescription"],setup(y,{expose:x,emit:C}){const _=y,h=C,l=k([]),p=k(!1),u=async()=>{if(_.diagnosisId){p.value=!0;try{const e=await O({diagnosis_id:_.diagnosisId});l.value=Array.isArray(e)?e:[]}catch(e){console.error("获取病历记录失败:",e),l.value=[]}finally{p.value=!1}}},S=e=>{if(!e)return"";const t=new Date(e*1e3);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`},$=e=>{h("view",e)},B=()=>{h("openPrescription")};return F(()=>{u()}),j(()=>_.diagnosisId,()=>{u()}),x({refresh:u}),(e,t)=>{const b=L,r=V,v=D,E=z,N=M,I=T;return a(),n("div",J,[y.readOnly?w("",!0):(a(),n("div",Q,[i(b,{type:"primary",size:"small",onClick:B},{default:s(()=>[...t[0]||(t[0]=[m("开方",-1)])]),_:1})])),A((a(),g(E,{data:d(l),border:""},{default:s(()=>[i(r,{prop:"prescription_date",label:"就诊日期",width:"120"}),i(r,{prop:"visit_no",label:"门诊号",width:"120"}),i(r,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),i(r,{label:"处方摘要","min-width":"180"},{default:s(({row:o})=>[o.herbs&&o.herbs.length?(a(),n("span",W,c(o.herbs.slice(0,3).map(f=>`${f.name}${f.dosage}克`).join("、"))+c(o.herbs.length>3?"...":""),1)):(a(),n("span",Y,"—"))]),_:1}),i(r,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),i(r,{label:"状态",width:"140",align:"center"},{default:s(({row:o})=>[o.void_status===1?(a(),n(H,{key:0},[i(v,{type:"danger",size:"small"},{default:s(()=>[...t[1]||(t[1]=[m("已作废",-1)])]),_:1}),R("div",q,c(o.void_by_name||"—")+" "+c(S(o.void_time)),1)],64)):(a(),g(v,{key:1,type:"success",size:"small"},{default:s(()=>[...t[2]||(t[2]=[m("正常",-1)])]),_:1}))]),_:1}),i(r,{label:"操作",width:"120",fixed:"right"},{default:s(({row:o})=>[i(b,{link:"",type:"primary",size:"small",onClick:f=>$(o)},{default:s(()=>[...t[3]||(t[3]=[m(" 查看 ",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[I,d(p)]]),!d(p)&&d(l).length===0?(a(),g(N,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):w("",!0)])}}}),zt=G(K,[["__scopeId","data-v-043d2738"]]);export{zt as default};
|
||||
import{i as L,L as T,N as V,T as D,M as z,W as M}from"./element-plus-Bolc0EfP.js";import{ae as O}from"./tcm-Ba4j6pRH.js";import{f as P,b as F,w as j,ak as a,I as n,a as i,aN as s,O as m,H as w,aP as A,G as g,F as H,J as R}from"./@vue/runtime-core-C6bnekPw.js";import{y as d,n as k}from"./@vue/reactivity-DiY1c2vO.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{_ as G}from"./index-B6p-ZV3k.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const J={class:"case-record-list"},Q={key:0,class:"mb-3 flex justify-end"},W={key:0},Y={key:1,class:"text-gray-400"},q={class:"void-detail text-xs text-gray-500 mt-1"},K=P({__name:"CaseRecordList",props:{diagnosisId:{type:Number,default:0},readOnly:{type:Boolean,default:!1}},emits:["view","openPrescription"],setup(y,{expose:x,emit:C}){const _=y,h=C,l=k([]),p=k(!1),u=async()=>{if(_.diagnosisId){p.value=!0;try{const e=await O({diagnosis_id:_.diagnosisId});l.value=Array.isArray(e)?e:[]}catch(e){console.error("获取病历记录失败:",e),l.value=[]}finally{p.value=!1}}},S=e=>{if(!e)return"";const t=new Date(e*1e3);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`},$=e=>{h("view",e)},B=()=>{h("openPrescription")};return F(()=>{u()}),j(()=>_.diagnosisId,()=>{u()}),x({refresh:u}),(e,t)=>{const b=L,r=V,v=D,E=z,N=M,I=T;return a(),n("div",J,[y.readOnly?w("",!0):(a(),n("div",Q,[i(b,{type:"primary",size:"small",onClick:B},{default:s(()=>[...t[0]||(t[0]=[m("开方",-1)])]),_:1})])),A((a(),g(E,{data:d(l),border:""},{default:s(()=>[i(r,{prop:"prescription_date",label:"就诊日期",width:"120"}),i(r,{prop:"visit_no",label:"门诊号",width:"120"}),i(r,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),i(r,{label:"处方摘要","min-width":"180"},{default:s(({row:o})=>[o.herbs&&o.herbs.length?(a(),n("span",W,c(o.herbs.slice(0,3).map(f=>`${f.name}${f.dosage}克`).join("、"))+c(o.herbs.length>3?"...":""),1)):(a(),n("span",Y,"—"))]),_:1}),i(r,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),i(r,{label:"状态",width:"140",align:"center"},{default:s(({row:o})=>[o.void_status===1?(a(),n(H,{key:0},[i(v,{type:"danger",size:"small"},{default:s(()=>[...t[1]||(t[1]=[m("已作废",-1)])]),_:1}),R("div",q,c(o.void_by_name||"—")+" "+c(S(o.void_time)),1)],64)):(a(),g(v,{key:1,type:"success",size:"small"},{default:s(()=>[...t[2]||(t[2]=[m("正常",-1)])]),_:1}))]),_:1}),i(r,{label:"操作",width:"120",fixed:"right"},{default:s(({row:o})=>[i(b,{link:"",type:"primary",size:"small",onClick:f=>$(o)},{default:s(()=>[...t[3]||(t[3]=[m(" 查看 ",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[I,d(p)]]),!d(p)&&d(l).length===0?(a(),g(N,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):w("",!0)])}}}),zt=G(K,[["__scopeId","data-v-043d2738"]]);export{zt as default};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{L as d}from"./element-plus-Bolc0EfP.js";import{f as i,aP as r,ak as e,I as o,aq as s,H as t,J as _}from"./@vue/runtime-core-C6bnekPw.js";const c={class:"admin-data-panel"},m={key:0,class:"admin-data-panel__toolbar"},p={class:"admin-data-panel__body"},f={key:1,class:"admin-data-panel__footer"},k=i({__name:"DataPanel",props:{loading:{type:Boolean,default:!1}},setup(n){return(a,u)=>{const l=d;return r((e(),o("section",c,[a.$slots.toolbar?(e(),o("div",m,[s(a.$slots,"toolbar")])):t("",!0),_("div",p,[s(a.$slots,"default")]),a.$slots.footer?(e(),o("div",f,[s(a.$slots,"footer")])):t("",!0)])),[[l,n.loading]])}}});export{k as _};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{i as d}from"./element-plus-Bolc0EfP.js";import{f as p,ak as s,I as o,J as n,a as m,aN as _,O as f,H as u,aq as y}from"./@vue/runtime-core-C6bnekPw.js";import{o as k,Q as i}from"./@vue/shared-mAAVTE9n.js";import{y as a,n as b}from"./@vue/reactivity-DiY1c2vO.js";const B={key:0,class:"admin-filter-panel__head"},C={class:"admin-filter-panel__label"},N={class:"admin-filter-panel__body"},E=p({__name:"FilterPanel",props:{collapsible:{type:Boolean,default:!0},title:{default:"筛选条件"}},setup(t){const e=b(!1);return(r,l)=>{const c=d;return s(),o("section",{class:k(["admin-filter-panel",{"is-collapsed":a(e)}])},[t.collapsible?(s(),o("div",B,[n("span",C,i(t.title),1),m(c,{class:"admin-filter-panel__toggle",link:"",type:"primary",onClick:l[0]||(l[0]=h=>e.value=!a(e))},{default:_(()=>[f(i(a(e)?"展开筛选":"收起筛选"),1)]),_:1})])):u("",!0),n("div",N,[y(r.$slots,"default")])],2)}}});export{E as _};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,2 +1,2 @@
|
||||
import{i as se,W as ne,C as le,V as ae,v as re,d as me,a as de}from"./element-plus-Bolc0EfP.js";import{_ as pe}from"./picker-ClTaiCz-.js";import{e as ue,c as ce,i as g,_ as ge}from"./index-d3j0BX4t.js";import{s as U}from"./@vue/runtime-dom-DDAG46FW.js";import{b as z,G as fe}from"./@element-plus/icons-vue-B0jSCQ-G.js";import{a as P,d as ve}from"./patient-Dq9eLSqu.js";import{h as _e}from"./perm-Cypp2gRL.js";import{f as ye,w as he,ak as o,I as n,G as k,aN as r,O as M,H as p,a as l,J as m,F as w,ap as E,A as ke}from"./@vue/runtime-core-C6bnekPw.js";import{n as f,y as N}from"./@vue/reactivity-DiY1c2vO.js";import{Q as T}from"./@vue/shared-mAAVTE9n.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./lodash-D3kF6u-c.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./index-DSYwOnd_.js";import"./index-DyY9GvDW.js";import"./index.vue_vue_type_script_setup_true_lang-EJPdzUeK.js";import"./index-CCEDJ4IW.js";import"./index-BS_MESkE.js";import"./cos-js-sdk-v5-DSMN0LUT.js";import"./file-HzaZx3TF.js";import"./index.vue_vue_type_script_setup_true_lang-Lcfr_gGB.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./usePaging-VsbTxSU0.js";import"./vuedraggable-5bKFmC7X.js";import"./vue-BVs68q8v.js";import"./@vue/compiler-dom-CqayqxS7.js";import"./@vue/compiler-core-CvbQOXIO.js";import"./sortablejs-dsEAUXlE.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const we={class:"note-timeline-wrap"},Ie={key:0,class:"timeline-actions"},Ce={class:"upload-trigger"},xe={class:"upload-trigger"},Ve={key:1,class:"note-timeline"},Ee={class:"timeline-date"},Ne={class:"timeline-body"},be={key:0,class:"timeline-content"},Se={key:1,class:"timeline-images"},Ae={key:2,class:"timeline-images"},De={key:0,class:"thumb-wrap"},Be={key:1,class:"file-wrap"},Ue=["href","title"],ze={class:"file-name"},O=8e3,Pe=ye({__name:"NoteTimeline",props:{notes:{},diagnosisId:{},readonly:{type:Boolean}},emits:["refresh"],setup(d,{emit:j}){const u=d,I=j,X=ke(()=>_e(["doctor.appointment/addDoctorNote"])),v=f(!1),C=f(""),b=f(!1),H=ue(),_=t=>H.getImageUrl(t),S=f([]),A=f([]),y=f(0),h=f(0),J=["jpg","jpeg","png","gif","bmp","webp","svg"],D=t=>{var s;const e=((s=t.split(".").pop())==null?void 0:s.toLowerCase().split("?")[0])||"";return J.includes(e)},$=t=>{var s;const e=t.split("/");return decodeURIComponent(((s=e[e.length-1])==null?void 0:s.split("?")[0])||"文件")},Q=t=>t.filter(D).map(_),Z=(t,e)=>{const s=t.filter(D),x=t[e];return s.indexOf(x)},q=t=>t?t.split(`
|
||||
import{i as se,W as ne,C as le,V as ae,v as re,d as me,a as de}from"./element-plus-Bolc0EfP.js";import{_ as pe}from"./picker-CB1VezxD.js";import{e as ue,c as ce,i as g,_ as ge}from"./index-B6p-ZV3k.js";import{s as U}from"./@vue/runtime-dom-DDAG46FW.js";import{b as z,G as fe}from"./@element-plus/icons-vue-B0jSCQ-G.js";import{a as P,d as ve}from"./patient-DcVrNNOW.js";import{h as _e}from"./perm-BkbQ-MqJ.js";import{f as ye,w as he,ak as o,I as n,G as k,aN as r,O as M,H as p,a as l,J as m,F as w,ap as E,A as ke}from"./@vue/runtime-core-C6bnekPw.js";import{n as f,y as N}from"./@vue/reactivity-DiY1c2vO.js";import{Q as T}from"./@vue/shared-mAAVTE9n.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./lodash-D3kF6u-c.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./index-BEsrPt83.js";import"./index-BidU9ngF.js";import"./index.vue_vue_type_script_setup_true_lang-EJPdzUeK.js";import"./index-CGJON9HS.js";import"./index-BK2tOS24.js";import"./cos-js-sdk-v5-DSMN0LUT.js";import"./file-BE-zRsES.js";import"./index.vue_vue_type_script_setup_true_lang-Lcfr_gGB.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./usePaging-VsbTxSU0.js";import"./vuedraggable-5bKFmC7X.js";import"./vue-BVs68q8v.js";import"./@vue/compiler-dom-CqayqxS7.js";import"./@vue/compiler-core-CvbQOXIO.js";import"./sortablejs-dsEAUXlE.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const we={class:"note-timeline-wrap"},Ie={key:0,class:"timeline-actions"},Ce={class:"upload-trigger"},xe={class:"upload-trigger"},Ve={key:1,class:"note-timeline"},Ee={class:"timeline-date"},Ne={class:"timeline-body"},be={key:0,class:"timeline-content"},Se={key:1,class:"timeline-images"},Ae={key:2,class:"timeline-images"},De={key:0,class:"thumb-wrap"},Be={key:1,class:"file-wrap"},Ue=["href","title"],ze={class:"file-name"},O=8e3,Pe=ye({__name:"NoteTimeline",props:{notes:{},diagnosisId:{},readonly:{type:Boolean}},emits:["refresh"],setup(d,{emit:j}){const u=d,I=j,X=ke(()=>_e(["doctor.appointment/addDoctorNote"])),v=f(!1),C=f(""),b=f(!1),H=ue(),_=t=>H.getImageUrl(t),S=f([]),A=f([]),y=f(0),h=f(0),J=["jpg","jpeg","png","gif","bmp","webp","svg"],D=t=>{var s;const e=((s=t.split(".").pop())==null?void 0:s.toLowerCase().split("?")[0])||"";return J.includes(e)},$=t=>{var s;const e=t.split("/");return decodeURIComponent(((s=e[e.length-1])==null?void 0:s.split("?")[0])||"文件")},Q=t=>t.filter(D).map(_),Z=(t,e)=>{const s=t.filter(D),x=t[e];return s.indexOf(x)},q=t=>t?t.split(`
|
||||
`).filter(Boolean):[],K=t=>{if(!u.diagnosisId)return;const e=Array.isArray(t)?t:[t];if(e.length<=y.value){y.value=e.length;return}const s=e.slice(y.value);y.value=e.length,s.length>0&&P({diagnosis_id:u.diagnosisId,tongue_images:s}).then(()=>{g.msgSuccess("舌苔照片已添加"),I("refresh")})},Y=t=>{if(!u.diagnosisId)return;const e=Array.isArray(t)?t:[t];if(e.length<=h.value){h.value=e.length;return}const s=e.slice(h.value);h.value=e.length,s.length>0&&P({diagnosis_id:u.diagnosisId,report_files:s}).then(()=>{g.msgSuccess("检查报告已添加"),I("refresh")})};he(()=>u.notes,()=>{S.value=[],A.value=[],y.value=0,h.value=0});const ee=async()=>{if(!u.diagnosisId){g.msgWarning("当前无诊单,无法添加备注");return}const t=C.value.trim();if(!t){g.msgWarning("请输入备注内容");return}b.value=!0;try{await P({diagnosis_id:u.diagnosisId,content:t}),g.msgSuccess("备注保存成功"),C.value="",v.value=!1,I("refresh")}catch(e){g.msgError((e==null?void 0:e.message)||"保存失败")}finally{b.value=!1}},B=async(t,e,s)=>{try{await de.confirm("确认删除?","提示",{type:"warning"})}catch{return}await ve({note_id:t,image_type:e,image_path:s}),g.msgSuccess("已删除"),I("refresh")};return(t,e)=>{const s=se,x=ce,F=pe,G=re,V=me,te=ne,ie=le,oe=ae;return o(),n("div",we,[!d.readonly&&d.diagnosisId?(o(),n("div",Ie,[X.value?(o(),k(s,{key:0,type:"primary",plain:"",size:"small",onClick:e[0]||(e[0]=i=>v.value=!0)},{default:r(()=>[...e[6]||(e[6]=[M(" 添加备注 ",-1)])]),_:1})):p("",!0),l(F,{modelValue:S.value,"onUpdate:modelValue":e[1]||(e[1]=i=>S.value=i),limit:99,type:"image","exclude-domain":!0,onChange:K},{upload:r(()=>[m("div",Ce,[l(x,{size:20,name:"el-icon-Plus"}),e[7]||(e[7]=m("span",null,"舌苔照片",-1))])]),_:1},8,["modelValue"]),l(F,{modelValue:A.value,"onUpdate:modelValue":e[2]||(e[2]=i=>A.value=i),limit:99,type:"file","exclude-domain":!0,onChange:Y},{upload:r(()=>[m("div",xe,[l(x,{size:20,name:"el-icon-Plus"}),e[8]||(e[8]=m("span",null,"检查报告",-1))])]),_:1},8,["modelValue"])])):p("",!0),d.notes.length?(o(),n("div",Ve,[(o(!0),n(w,null,E(d.notes,i=>{var L,R;return o(),n("div",{key:i.id,class:"timeline-node"},[e[11]||(e[11]=m("div",{class:"timeline-dot"},null,-1)),m("div",Ee,T(i.note_date),1),m("div",Ne,[i.content?(o(),n("div",be,[(o(!0),n(w,null,E(q(i.content),(a,c)=>(o(),n("div",{key:c,class:"content-line"},T(a),1))),128))])):p("",!0),(L=i.tongue_images)!=null&&L.length?(o(),n("div",Se,[e[9]||(e[9]=m("span",{class:"images-label"},"舌苔照片",-1)),(o(!0),n(w,null,E(i.tongue_images,(a,c)=>(o(),n("div",{key:c,class:"thumb-wrap"},[l(G,{src:_(a),"preview-src-list":i.tongue_images.map(_),"initial-index":c,"z-index":O,fit:"cover",class:"timeline-thumb","preview-teleported":""},null,8,["src","preview-src-list","initial-index"]),d.readonly?p("",!0):(o(),k(V,{key:0,class:"thumb-delete",onClick:U(W=>B(i.id,"tongue_images",a),["stop"])},{default:r(()=>[l(N(z))]),_:1},8,["onClick"]))]))),128))])):p("",!0),(R=i.report_files)!=null&&R.length?(o(),n("div",Ae,[e[10]||(e[10]=m("span",{class:"images-label"},"检查报告",-1)),(o(!0),n(w,null,E(i.report_files,(a,c)=>(o(),n(w,{key:c},[D(a)?(o(),n("div",De,[l(G,{src:_(a),"preview-src-list":Q(i.report_files),"initial-index":Z(i.report_files,c),"z-index":O,fit:"cover",class:"timeline-thumb","preview-teleported":""},null,8,["src","preview-src-list","initial-index"]),d.readonly?p("",!0):(o(),k(V,{key:0,class:"thumb-delete",onClick:U(W=>B(i.id,"report_files",a),["stop"])},{default:r(()=>[l(N(z))]),_:1},8,["onClick"]))])):(o(),n("div",Be,[m("a",{href:_(a),target:"_blank",class:"file-link",title:$(a)},[l(V,{size:20},{default:r(()=>[l(N(fe))]),_:1}),m("span",ze,T($(a)),1)],8,Ue),d.readonly?p("",!0):(o(),k(V,{key:0,class:"file-delete",onClick:U(W=>B(i.id,"report_files",a),["stop"])},{default:r(()=>[l(N(z))]),_:1},8,["onClick"]))]))],64))),128))])):p("",!0)])])}),128))])):p("",!0),!d.notes.length&&d.readonly?(o(),k(te,{key:2,description:"暂无备注","image-size":48})):p("",!0),l(oe,{modelValue:v.value,"onUpdate:modelValue":e[5]||(e[5]=i=>v.value=i),title:"添加备注",width:"480px","append-to-body":""},{footer:r(()=>[l(s,{onClick:e[4]||(e[4]=i=>v.value=!1)},{default:r(()=>[...e[12]||(e[12]=[M("取消",-1)])]),_:1}),l(s,{type:"primary",loading:b.value,onClick:ee},{default:r(()=>[...e[13]||(e[13]=[M("保存",-1)])]),_:1},8,["loading"])]),default:r(()=>[l(ie,{modelValue:C.value,"onUpdate:modelValue":e[3]||(e[3]=i=>C.value=i),type:"textarea",rows:4,placeholder:"输入今日备注...",maxlength:"500","show-word-limit":""},null,8,["modelValue"])]),_:1},8,["modelValue"])])}}}),Mt=ge(Pe,[["__scopeId","data-v-530ad386"]]);export{Mt as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{m as x,f as y,a as B}from"./diag-display-DCz_VAqj.js";import{f as w,ak as I,I as P,J as a,H as N}from"./@vue/runtime-core-C6bnekPw.js";import{Q as i}from"./@vue/shared-mAAVTE9n.js";import{y as e}from"./@vue/reactivity-DiY1c2vO.js";import{_ as V}from"./index-d3j0BX4t.js";import"./lodash-D3kF6u-c.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const b={class:"card patient-card"},D={class:"patient-hero"},E={class:"patient-name"},G={class:"patient-meta"},H={key:0},J=w({__name:"PatientInfoCard",props:{apt:{default:()=>({})},diag:{default:()=>({})}},setup(t){return(Q,o)=>{var m,r,n,d,p,s,c,l,g,f,u,h,v,k,C;return I(),P("div",b,[o[0]||(o[0]=a("div",{class:"card-title"},"患者信息",-1)),a("div",D,[a("div",E,i(((m=t.apt)==null?void 0:m.patient_name)||"—"),1),a("div",G,[a("div",null,i(e(x)((r=t.apt)==null?void 0:r.patient_phone))+" · "+i(e(y)((n=t.diag)==null?void 0:n.gender))+" · "+i(((d=t.diag)==null?void 0:d.age)!=null?t.diag.age+"岁":"—"),1),a("div",null,i((p=t.diag)!=null&&p.height?t.diag.height+"cm":"—")+" / "+i((s=t.diag)!=null&&s.weight?t.diag.weight+"kg":"—")+" · "+i(((c=t.diag)==null?void 0:c.region)||"—"),1),a("div",null," 预约:"+i((l=t.apt)==null?void 0:l.appointment_date)+" "+i((g=t.apt)==null?void 0:g.appointment_time)+" · "+i(e(B)((f=t.apt)==null?void 0:f.period)),1),a("div",null,"医生:"+i(((u=t.apt)==null?void 0:u.doctor_name)||"—")+" 客服:"+i(((h=t.apt)==null?void 0:h.assistant_name)||"—"),1),a("div",null," 状态:"+i(((v=t.apt)==null?void 0:v.status_desc)||"—")+" · "+i((k=t.apt)!=null&&k.has_prescription?"已开方":"未开方"),1),(C=t.apt)!=null&&C.remark?(I(),P("div",H,"备注:"+i(t.apt.remark),1)):N("",!0)])])])}}}),Ct=V(J,[["__scopeId","data-v-1b0de09c"]]);export{Ct as default};
|
||||
import{m as x,f as y,a as B}from"./diag-display-DCz_VAqj.js";import{f as w,ak as I,I as P,J as a,H as N}from"./@vue/runtime-core-C6bnekPw.js";import{Q as i}from"./@vue/shared-mAAVTE9n.js";import{y as e}from"./@vue/reactivity-DiY1c2vO.js";import{_ as V}from"./index-B6p-ZV3k.js";import"./lodash-D3kF6u-c.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const b={class:"card patient-card"},D={class:"patient-hero"},E={class:"patient-name"},G={class:"patient-meta"},H={key:0},J=w({__name:"PatientInfoCard",props:{apt:{default:()=>({})},diag:{default:()=>({})}},setup(t){return(Q,o)=>{var m,r,n,d,p,s,c,l,g,f,u,h,v,k,C;return I(),P("div",b,[o[0]||(o[0]=a("div",{class:"card-title"},"患者信息",-1)),a("div",D,[a("div",E,i(((m=t.apt)==null?void 0:m.patient_name)||"—"),1),a("div",G,[a("div",null,i(e(x)((r=t.apt)==null?void 0:r.patient_phone))+" · "+i(e(y)((n=t.diag)==null?void 0:n.gender))+" · "+i(((d=t.diag)==null?void 0:d.age)!=null?t.diag.age+"岁":"—"),1),a("div",null,i((p=t.diag)!=null&&p.height?t.diag.height+"cm":"—")+" / "+i((s=t.diag)!=null&&s.weight?t.diag.weight+"kg":"—")+" · "+i(((c=t.diag)==null?void 0:c.region)||"—"),1),a("div",null," 预约:"+i((l=t.apt)==null?void 0:l.appointment_date)+" "+i((g=t.apt)==null?void 0:g.appointment_time)+" · "+i(e(B)((f=t.apt)==null?void 0:f.period)),1),a("div",null,"医生:"+i(((u=t.apt)==null?void 0:u.doctor_name)||"—")+" 客服:"+i(((h=t.apt)==null?void 0:h.assistant_name)||"—"),1),a("div",null," 状态:"+i(((v=t.apt)==null?void 0:v.status_desc)||"—")+" · "+i((k=t.apt)!=null&&k.has_prescription?"已开方":"未开方"),1),(C=t.apt)!=null&&C.remark?(I(),P("div",H,"备注:"+i(t.apt.remark),1)):N("",!0)])])])}}}),Ct=V(J,[["__scopeId","data-v-1b0de09c"]]);export{Ct as default};
|
||||
@@ -0,0 +1 @@
|
||||
.patient-order-list[data-v-4a0ef613]{padding:4px 0}.po-empty-tip[data-v-4a0ef613]{padding:24px 0}
|
||||
@@ -1 +0,0 @@
|
||||
.patient-order-list[data-v-fe72f000]{padding:4px 0}.po-empty-tip[data-v-fe72f000]{padding:24px 0}.po-revisit-offset-bar[data-v-fe72f000]{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:12px 14px;border:1px solid var(--el-border-color-lighter);border-radius:8px;background:var(--el-fill-color-lighter)}.po-revisit-offset-bar__main[data-v-fe72f000]{display:flex;align-items:center;flex-wrap:wrap;gap:4px}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user