Merge branch 'long-0507'

This commit is contained in:
2026-05-07 15:04:23 +08:00
7 changed files with 9807 additions and 19 deletions
+9731
View File
File diff suppressed because it is too large Load Diff
@@ -23,10 +23,10 @@
</div> </div>
<div class="daily-matrix__toolbar-right"> <div class="daily-matrix__toolbar-right">
<el-button :disabled="readOnly" type="primary" @click="openQuickAdd('blood')">+血糖</el-button> <el-button :disabled="!canEditDailyRecord" type="primary" @click="openQuickAdd('blood')">+血糖</el-button>
<el-button :disabled="readOnly" @click="openQuickAdd('diet')">+饮食</el-button> <el-button :disabled="!canEditDailyRecord" @click="openQuickAdd('diet')">+饮食</el-button>
<el-button :disabled="readOnly" @click="openQuickAdd('exercise')">+运动</el-button> <el-button :disabled="!canEditDailyRecord" @click="openQuickAdd('exercise')">+运动</el-button>
<el-button :disabled="readOnly" @click="openQuickAdd('tracking')">+备注</el-button> <el-button :disabled="!canEditDailyRecord" @click="openQuickAdd('tracking')">+备注</el-button>
<el-button @click="fetchTracking">刷新</el-button> <el-button @click="fetchTracking">刷新</el-button>
</div> </div>
</div> </div>
@@ -160,7 +160,7 @@
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="bloodDialogVisible = false">取消</el-button> <el-button @click="bloodDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="bloodSubmitting" @click="submitBlood">确定</el-button> <el-button type="primary" :loading="bloodSubmitting" :disabled="!canEditDailyRecord" @click="submitBlood">确定</el-button>
</template> </template>
</el-dialog> </el-dialog>
@@ -202,7 +202,7 @@
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="dietDialogVisible = false">取消</el-button> <el-button @click="dietDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="dietSubmitting" @click="submitDiet">确定</el-button> <el-button type="primary" :loading="dietSubmitting" :disabled="!canEditDailyRecord" @click="submitDiet">确定</el-button>
</template> </template>
</el-dialog> </el-dialog>
@@ -247,7 +247,7 @@
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="exerciseDialogVisible = false">取消</el-button> <el-button @click="exerciseDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="exerciseSubmitting" @click="submitExercise">确定</el-button> <el-button type="primary" :loading="exerciseSubmitting" :disabled="!canEditDailyRecord" @click="submitExercise">确定</el-button>
</template> </template>
</el-dialog> </el-dialog>
@@ -385,6 +385,7 @@ interface TrackingNoteRecord {
type RangeMode = '7' | '30' | 'custom' type RangeMode = '7' | '30' | 'custom'
type DialogKind = 'blood' | 'diet' | 'exercise' | 'tracking' type DialogKind = 'blood' | 'diet' | 'exercise' | 'tracking'
const DAILY_RECORD_PERMS = ['tcm.diagnosis/dailyRecord']
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
age: undefined, age: undefined,
@@ -396,7 +397,9 @@ const diagnosisId = computed(() => Number(props.diagnosisId || 0))
const patientId = computed(() => Number(props.patientId || 0)) const patientId = computed(() => Number(props.patientId || 0))
const readOnly = computed(() => !!props.readOnly) const readOnly = computed(() => !!props.readOnly)
const patientName = computed(() => props.patientName || '') const patientName = computed(() => props.patientName || '')
const canViewTodoList = computed(() => hasPermission(['tcm.diagnosisTodo/lists'])) const hasDailyRecordPermission = computed(() => hasPermission(DAILY_RECORD_PERMS))
const canEditDailyRecord = computed(() => hasDailyRecordPermission.value && !readOnly.value)
const canViewTodoList = computed(() => hasDailyRecordPermission.value)
const loading = ref(false) const loading = ref(false)
const rangeMode = ref<RangeMode>('7') const rangeMode = ref<RangeMode>('7')
@@ -849,6 +852,7 @@ function handleCustomRangeChange() {
} }
function openQuickAdd(kind: DialogKind) { function openQuickAdd(kind: DialogKind) {
if (!canEditDailyRecord.value) return
const date = visibleDates.value[0] || formatDate(new Date()) const date = visibleDates.value[0] || formatDate(new Date())
if (kind === 'blood') { if (kind === 'blood') {
bloodForm.value = createBloodForm(date) bloodForm.value = createBloodForm(date)
@@ -876,7 +880,7 @@ function openQuickAdd(kind: DialogKind) {
} }
function handleCellClick(metric: string, date: string) { function handleCellClick(metric: string, date: string) {
if (readOnly.value) return if (!canEditDailyRecord.value) return
if (['fasting', 'postprandial', 'other', 'bp', 'western', 'insulin'].includes(metric)) { if (['fasting', 'postprandial', 'other', 'bp', 'western', 'insulin'].includes(metric)) {
const source = bloodByDate.value.get(date) const source = bloodByDate.value.get(date)
bloodForm.value = source ? { ...createBloodForm(date), ...source } : createBloodForm(date) bloodForm.value = source ? { ...createBloodForm(date), ...source } : createBloodForm(date)
@@ -1014,7 +1018,7 @@ function getTrackingPreview(content: string) {
} }
function isCellClickable(metric: string, date: string) { function isCellClickable(metric: string, date: string) {
if (readOnly.value) return false if (!canEditDailyRecord.value) return false
if (metric !== 'tracking') return true if (metric !== 'tracking') return true
return date === todayDate.value || !!getTrackingContent(date) return date === todayDate.value || !!getTrackingContent(date)
} }
@@ -3,9 +3,9 @@
<div class="toolbar"> <div class="toolbar">
<el-button <el-button
type="primary" type="primary"
:disabled="!props.diagnosisId || !!props.readOnly" :disabled="!props.diagnosisId || !!props.readOnly || !hasDailyRecordPermission"
@click="handleAdd" @click="handleAdd"
v-perms="['tcm.diagnosisTodo/add']" v-perms="DAILY_RECORD_PERMS"
> >
+ 新增待办 + 新增待办
</el-button> </el-button>
@@ -55,7 +55,7 @@
link link
type="danger" type="danger"
@click="handleCancel(row)" @click="handleCancel(row)"
v-perms="['tcm.diagnosisTodo/cancel']" v-perms="DAILY_RECORD_PERMS"
> >
取消 取消
</el-button> </el-button>
@@ -114,8 +114,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { diagnosisTodoLists, diagnosisTodoAdd, diagnosisTodoCancel } from '@/api/tcm' import { diagnosisTodoLists, diagnosisTodoAdd, diagnosisTodoCancel } from '@/api/tcm'
import { computed } from 'vue'
import { hasPermission } from '@/utils/perm'
import feedback from '@/utils/feedback' import feedback from '@/utils/feedback'
const DAILY_RECORD_PERMS = ['tcm.diagnosis/dailyRecord']
interface Props { interface Props {
diagnosisId: number | null | undefined diagnosisId: number | null | undefined
patientId?: number | null patientId?: number | null
@@ -124,6 +128,7 @@ interface Props {
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const hasDailyRecordPermission = computed(() => hasPermission(DAILY_RECORD_PERMS))
// pager 与项目内 <pagination v-model="pager" /> 约定的字段保持一致 // pager 与项目内 <pagination v-model="pager" /> 约定的字段保持一致
const pager = reactive({ const pager = reactive({
@@ -137,7 +142,7 @@ const pager = reactive({
const filterStatus = ref<number | ''>('') const filterStatus = ref<number | ''>('')
const fetchList = async () => { const fetchList = async () => {
if (!props.diagnosisId || Number(props.diagnosisId) <= 0) { if (!props.diagnosisId || Number(props.diagnosisId) <= 0 || !hasDailyRecordPermission.value) {
pager.lists = [] pager.lists = []
pager.count = 0 pager.count = 0
return return
@@ -221,7 +226,7 @@ const disablePastDate = (date: Date) => {
} }
const handleAdd = () => { const handleAdd = () => {
if (!props.diagnosisId || props.readOnly) { if (!props.diagnosisId || props.readOnly || !hasDailyRecordPermission.value) {
feedback.msgWarning('诊单未保存,无法添加待办') feedback.msgWarning('诊单未保存,无法添加待办')
return return
} }
@@ -260,6 +265,7 @@ const handleAddSubmit = async () => {
// ========== 取消 ========== // ========== 取消 ==========
const handleCancel = async (row: any) => { const handleCancel = async (row: any) => {
if (!hasDailyRecordPermission.value || props.readOnly) return
try { try {
await feedback.confirm(`确认取消「${row.remind_time_text} · ${truncate(row.content, 30)}」吗?`) await feedback.confirm(`确认取消「${row.remind_time_text} · ${truncate(row.content, 30)}」吗?`)
} catch { } catch {
+11 -1
View File
@@ -573,7 +573,13 @@
</div> </div>
<el-empty v-else description="请先保存诊单后查看" /> <el-empty v-else description="请先保存诊单后查看" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="日常记录" name="daily" :disabled="!formData.id" lazy> <el-tab-pane
v-if="hasDailyRecordPermission"
label="日常记录"
name="daily"
:disabled="!formData.id"
lazy
>
<daily-matrix <daily-matrix
v-if="formData.id" v-if="formData.id"
:diagnosis-id="Number(formData.id)" :diagnosis-id="Number(formData.id)"
@@ -736,6 +742,7 @@ const submitting = ref(false)
/** 拥有后手机号可正常编辑,失焦不再强制脱敏 */ /** 拥有后手机号可正常编辑,失焦不再强制脱敏 */
const hasPhonePlainPermission = computed(() => hasPermission(['tcm.diagnosis/phonePlain'])) const hasPhonePlainPermission = computed(() => hasPermission(['tcm.diagnosis/phonePlain']))
const hasDailyRecordPermission = computed(() => hasPermission(['tcm.diagnosis/dailyRecord']))
/** 编辑且无明文权限:只读脱敏,点击切换查看完整号 */ /** 编辑且无明文权限:只读脱敏,点击切换查看完整号 */
const showPhoneMaskedEdit = computed(() => mode.value === 'edit' && !hasPhonePlainPermission.value) const showPhoneMaskedEdit = computed(() => mode.value === 'edit' && !hasPhonePlainPermission.value)
const phoneRevealUnlocked = ref(false) const phoneRevealUnlocked = ref(false)
@@ -790,6 +797,9 @@ watch([visible, activeTab], () => {
if (activeTab.value === 'appointmentRecords' && !hasPermission(['doctor.appointment/lists'])) { if (activeTab.value === 'appointmentRecords' && !hasPermission(['doctor.appointment/lists'])) {
activeTab.value = 'basic' activeTab.value = 'basic'
} }
if (activeTab.value === 'daily' && !hasDailyRecordPermission.value) {
activeTab.value = 'basic'
}
}) })
@@ -82,7 +82,7 @@ class AuthMiddleware
$AdminUris = $adminAuthCache->getAdminUri() ?? []; $AdminUris = $adminAuthCache->getAdminUri() ?? [];
$AdminUris = $this->formatUrl($AdminUris); $AdminUris = $this->formatUrl($AdminUris);
if (in_array($accessUri, $AdminUris)) { if (in_array($accessUri, $AdminUris) || $this->matchPermissionAlias($accessUri, $AdminUris)) {
return $next($request); return $next($request);
} }
return JsonService::fail('权限不足,无法访问或操作'); return JsonService::fail('权限不足,无法访问或操作');
@@ -103,6 +103,25 @@ class AuthMiddleware
}, $data); }, $data);
} }
/**
* 日常记录权限域:前端统一收口到 tcm.diagnosis/dailyRecord
* 但待办/跟踪备注接口仍保留历史路由名,故在鉴权层做精确别名映射。
*/
private function matchPermissionAlias(string $accessUri, array $adminUris): bool
{
if (!in_array('tcm.diagnosis/dailyrecord', $adminUris, true)) {
return false;
}
return in_array($accessUri, [
'tcm.diagnosistodo/lists',
'tcm.diagnosistodo/add',
'tcm.diagnosistodo/cancel',
'tcm.diagnosis/trackingnotes',
'tcm.diagnosis/addtrackingnote',
], true);
}
/** /**
* 面诊进度专用:auth.admin/lists、doctor.appointment/lists + progress_board=1,不校验菜单权限 * 面诊进度专用:auth.admin/lists、doctor.appointment/lists + progress_board=1,不校验菜单权限
*/ */
@@ -31,7 +31,8 @@ use app\common\model\tcm\DiagnosisTodo;
* cancel() → status=2 (已取消,仅 status=0 可取消) * cancel() → status=2 (已取消,仅 status=0 可取消)
* *
* 鉴权: * 鉴权:
* add:诊单需存在;数据权限交由路由层「tcm.diagnosisTodo/add」节点 + 前端 tab disabled 控制 * add:诊单需存在;数据权限交由路由层统一「tcm.diagnosis/dailyRecord」权限域控制
* 并兼容历史 diagnosisTodo/* 接口路由。
* cancel:仅创建人本人或超级管理员 (role_id=1) 可取消,且仅 status=0 时。 * cancel:仅创建人本人或超级管理员 (role_id=1) 可取消,且仅 status=0 时。
* *
* @package app\adminapi\logic\tcm * @package app\adminapi\logic\tcm
@@ -0,0 +1,17 @@
# 2026-05-07
# (perms=tcm.diagnosis/lists)
# tcm.diagnosis/dailyRecord ////
SET @diag_menu_id := (SELECT id FROM zyt_system_menu WHERE perms = 'tcm.diagnosis/lists' LIMIT 1);
INSERT INTO zyt_system_menu (
pid, type, name, icon, sort, perms, paths, component,
selected, params, is_cache, is_show, is_disable, create_time, update_time
)
SELECT
@diag_menu_id, 'A', '日常记录', '', 59,
'tcm.diagnosis/dailyRecord', '', '',
'', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
FROM DUAL
WHERE @diag_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM zyt_system_menu WHERE perms = 'tcm.diagnosis/dailyRecord');