更新
This commit is contained in:
@@ -99,11 +99,13 @@ import EditPopup from './edit.vue'
|
||||
|
||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||
|
||||
// 表单数据 - 固定role_id=1查询医助
|
||||
// 表单数据 - 固定role_id=2查询医助
|
||||
const formData = reactive({
|
||||
account: '',
|
||||
name: '',
|
||||
role_id: 2 // 固定为医助角色
|
||||
role_id: 2, // 固定为医助角色
|
||||
// 启用数据范围隔离:上级管理员只看到自己可见的医助;root/全部范围不受影响
|
||||
apply_data_scope: 1
|
||||
})
|
||||
|
||||
const showEdit = ref(false)
|
||||
|
||||
@@ -36,6 +36,18 @@
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据范围" prop="data_scope">
|
||||
<el-select
|
||||
v-model="formData.data_scope"
|
||||
placeholder="请选择数据范围"
|
||||
class="ls-input"
|
||||
>
|
||||
<el-option :value="1" label="全部数据(免隔离)" />
|
||||
<el-option :value="2" label="本部门及下级部门" />
|
||||
<el-option :value="3" label="仅本部门" />
|
||||
<el-option :value="4" label="仅本人" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</popup>
|
||||
</div>
|
||||
@@ -58,6 +70,7 @@ const formData = reactive({
|
||||
name: '',
|
||||
desc: '',
|
||||
sort: 0,
|
||||
data_scope: 1,
|
||||
menu_id: []
|
||||
})
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
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">
|
||||
@@ -98,6 +103,18 @@ const handleAuth = async (data: any) => {
|
||||
authRef.value?.setFormData(data)
|
||||
}
|
||||
|
||||
const dataScopeLabel = (scope: number | string | null | undefined) => {
|
||||
const s = Number(scope) || 1
|
||||
return (
|
||||
({
|
||||
1: '全部数据(免隔离)',
|
||||
2: '本部门及下级部门',
|
||||
3: '仅本部门',
|
||||
4: '仅本人'
|
||||
} as Record<number, string>)[s] || '全部数据(免隔离)'
|
||||
)
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -130,7 +130,7 @@ class OrderController extends BaseAdminController
|
||||
public function orderStats()
|
||||
{
|
||||
$params = $this->request->get();
|
||||
$result = OrderLogic::orderStats($params);
|
||||
$result = OrderLogic::orderStats($params, (int) $this->adminId, $this->adminInfo);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class DiagnosisController extends BaseAdminController
|
||||
public function assistantDiagnosisStats()
|
||||
{
|
||||
$params = $this->request->get();
|
||||
$result = DiagnosisLogic::assistantDiagnosisStats($params);
|
||||
$result = DiagnosisLogic::assistantDiagnosisStats($params, (int) $this->adminId, $this->adminInfo);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ class DiagnosisController extends BaseAdminController
|
||||
*/
|
||||
public function getAssistants()
|
||||
{
|
||||
$result = DiagnosisLogic::getAssistants();
|
||||
$result = DiagnosisLogic::getAssistants((int) $this->adminId, $this->adminInfo);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,15 @@ use app\common\lists\ListsExcelInterface;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\ListsSortInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\model\auth\SystemRole;
|
||||
use app\common\model\dept\Dept;
|
||||
use app\common\model\dept\Jobs;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 管理员列表
|
||||
@@ -32,6 +36,7 @@ use app\common\model\dept\Jobs;
|
||||
*/
|
||||
class AdminLists extends BaseAdminDataLists implements ListsExtendInterface, ListsSearchInterface, ListsSortInterface,ListsExcelInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
/**
|
||||
* @notes 设置导出字段
|
||||
* @return string[]
|
||||
@@ -116,9 +121,37 @@ class AdminLists extends BaseAdminDataLists implements ListsExtendInterface, Lis
|
||||
|
||||
if ($progressBoard) {
|
||||
// 面诊进度:固定只拉「医生」角色且未禁用,忽略客户端篡改的 role_id
|
||||
$adminIds = AdminRole::where('role_id', 1)->column('admin_id');
|
||||
$adminIds = array_map('intval', AdminRole::where('role_id', 1)->column('admin_id'));
|
||||
|
||||
// 数据范围:医生与业务/医助通常不在同一部门,按 admin id 直接取交集会全空。
|
||||
// 这里改为「按当前账号可见的挂号反查涉及的医生」,与 AppointmentLists 的可见性一致:
|
||||
// 可见挂号 = a.doctor_id ∈ visible OR diagnosis.assistant_id ∈ visible
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
if ($visibleIds !== null) {
|
||||
if ($visibleIds === [] || $adminIds === []) {
|
||||
$adminIds = [];
|
||||
} else {
|
||||
$aTbl = (new Appointment())->getTable();
|
||||
$dTbl = (new Diagnosis())->getTable();
|
||||
$inList = implode(',', array_map('intval', $visibleIds));
|
||||
$rows = Db::query(
|
||||
"SELECT DISTINCT a.doctor_id FROM `{$aTbl}` a "
|
||||
. "LEFT JOIN `{$dTbl}` u ON a.patient_id = u.id "
|
||||
. "WHERE (a.doctor_id IN ({$inList}) "
|
||||
. " OR (u.id IS NOT NULL AND u.delete_time IS NULL AND u.assistant_id IN ({$inList})))"
|
||||
);
|
||||
$apptDoctorIds = array_filter(array_map(static function ($r): int {
|
||||
return (int) ($r['doctor_id'] ?? 0);
|
||||
}, $rows ?: []));
|
||||
$adminIds = array_values(array_intersect($adminIds, $apptDoctorIds));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($adminIds)) {
|
||||
$where[] = ['id', 'in', $adminIds];
|
||||
} else {
|
||||
// 数据范围下没有可见医生:强制空集
|
||||
$where[] = ['id', '=', 0];
|
||||
}
|
||||
$where[] = ['disable', '=', 0];
|
||||
} else {
|
||||
@@ -134,6 +167,19 @@ class AdminLists extends BaseAdminDataLists implements ListsExtendInterface, Lis
|
||||
}
|
||||
}
|
||||
|
||||
// 数据范围:仅当调用方主动传 apply_data_scope=1 时启用
|
||||
// (管理员表被多场景共用:医生/医助选择器、看板、组织架构等;不主动启用避免误伤)
|
||||
if ((int) ($this->params['apply_data_scope'] ?? 0) === 1) {
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
if ($visibleIds !== null) {
|
||||
if ($visibleIds === []) {
|
||||
$where[] = ['id', '=', 0]; // 强制空
|
||||
} else {
|
||||
$where[] = ['id', 'in', $visibleIds];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class RoleLists extends BaseAdminDataLists
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = SystemRole::with(['role_menu_index'])
|
||||
->field('id,name,desc,sort,create_time')
|
||||
->field('id,name,desc,sort,data_scope,create_time')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->select()
|
||||
|
||||
@@ -9,6 +9,7 @@ use app\common\model\tcm\Prescription;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
|
||||
/**
|
||||
* 医生预约列表
|
||||
@@ -17,6 +18,31 @@ use app\common\lists\ListsSearchInterface;
|
||||
*/
|
||||
class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
|
||||
/**
|
||||
* 数据隔离:医生或医助(u.assistant_id = diag.assistant_id)命中可见集合;progress_board 场景放开(看板跨医生查看)
|
||||
*/
|
||||
private function applyDataScopeForAppointment($query, bool $progressBoard): void
|
||||
{
|
||||
if ($progressBoard) {
|
||||
return;
|
||||
}
|
||||
if (!$this->dataScopeShouldApply()) {
|
||||
return;
|
||||
}
|
||||
$ids = $this->getDataScopeVisibleAdminIds();
|
||||
if ($ids === null) {
|
||||
return;
|
||||
}
|
||||
if ($ids === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$inList = implode(',', $ids);
|
||||
$query->whereRaw("(a.doctor_id IN ({$inList}) OR u.assistant_id IN ({$inList}))");
|
||||
}
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return array
|
||||
@@ -114,6 +140,8 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyDataScopeForAppointment($query, $progressBoard);
|
||||
|
||||
// 诊单软删除后不再展示对应挂号(leftJoin 时无诊单或诊单未删)
|
||||
$query->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
|
||||
|
||||
@@ -269,6 +297,8 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyDataScopeForAppointment($query, $progressBoard);
|
||||
|
||||
$query->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace app\adminapi\lists\order;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\auth\AdminRole;
|
||||
|
||||
@@ -16,6 +17,7 @@ use app\common\model\auth\AdminRole;
|
||||
*/
|
||||
class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
/**
|
||||
* 将列表「创建时间」参数规范为可解析的日期时间(datetimerange 已是 Y-m-d H:i:s,不能再去拼接 00:00:00,否则会变成非法字符串)
|
||||
*/
|
||||
@@ -214,7 +216,9 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
|
||||
$this->appendCreateTimeWhere($where);
|
||||
|
||||
return Order::where($where)
|
||||
$query = Order::where($where);
|
||||
$this->applyDataScopeByOwner($query, 'creator_id');
|
||||
return $query
|
||||
->with(['patient', 'creator'])
|
||||
->order(['create_time' => 'desc'])
|
||||
->limit($this->limitOffset, $this->pageSize)
|
||||
@@ -251,6 +255,8 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
|
||||
$this->appendCreateTimeWhere($where);
|
||||
|
||||
return Order::where($where)->count();
|
||||
$query = Order::where($where);
|
||||
$this->applyDataScopeByOwner($query, 'creator_id');
|
||||
return $query->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
|
||||
/**
|
||||
* 中医辨房病因诊单列表
|
||||
@@ -33,6 +34,7 @@ use app\common\lists\ListsSearchInterface;
|
||||
*/
|
||||
class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return array
|
||||
@@ -70,6 +72,10 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$query->where('assistant_id', $this->adminId);
|
||||
}
|
||||
|
||||
if (!$pendingAssign) {
|
||||
$this->applyDataScopeByOwner($query, 'assistant_id');
|
||||
}
|
||||
|
||||
// 关键字搜索:支持患者姓名或手机号(模糊匹配)
|
||||
if (isset($this->params['keyword']) && trim((string) $this->params['keyword']) !== '') {
|
||||
$keyword = trim((string) $this->params['keyword']);
|
||||
@@ -459,6 +465,10 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$query->where('assistant_id', $this->adminId);
|
||||
}
|
||||
|
||||
if (!$pendingAssign) {
|
||||
$this->applyDataScopeByOwner($query, 'assistant_id');
|
||||
}
|
||||
|
||||
// 关键字搜索:支持患者姓名或手机号(模糊匹配)
|
||||
if (isset($this->params['keyword']) && trim((string) $this->params['keyword']) !== '') {
|
||||
$keyword = trim((string) $this->params['keyword']);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace app\adminapi\lists\tcm;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\adminapi\logic\tcm\PrescriptionLogic;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
@@ -15,6 +16,7 @@ use app\common\model\tcm\PrescriptionOrder;
|
||||
*/
|
||||
class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
*/
|
||||
@@ -138,6 +140,7 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
||||
$this->applySourceFilter($query);
|
||||
$this->applyVisibilityScope($query);
|
||||
$this->applyCreatorIdsFilter($query);
|
||||
$this->applyDataScopeByOwnerColumns($query, ['creator_id', 'assistant_id']);
|
||||
|
||||
$lists = $query
|
||||
->whereNull('delete_time')
|
||||
@@ -231,6 +234,7 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
||||
$this->applySourceFilter($query);
|
||||
$this->applyVisibilityScope($query);
|
||||
$this->applyCreatorIdsFilter($query);
|
||||
$this->applyDataScopeByOwnerColumns($query, ['creator_id', 'assistant_id']);
|
||||
|
||||
return $query->whereNull('delete_time')->count();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\auth\AdminDept;
|
||||
@@ -21,6 +22,8 @@ use think\db\Query;
|
||||
|
||||
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
@@ -76,6 +79,36 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
}
|
||||
});
|
||||
}
|
||||
$this->applyDataScopeForPrescriptionOrder($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据隔离:创建者 ∈ 可见 admin,或 关联诊单医助 ∈ 可见 admin
|
||||
*/
|
||||
private function applyDataScopeForPrescriptionOrder($query): void
|
||||
{
|
||||
if (!$this->dataScopeShouldApply()) {
|
||||
return;
|
||||
}
|
||||
$ids = $this->getDataScopeVisibleAdminIds();
|
||||
if ($ids === null) {
|
||||
return;
|
||||
}
|
||||
if ($ids === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$poTbl = (new PrescriptionOrder())->getTable();
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$inList = implode(',', $ids);
|
||||
$query->where(function ($q) use ($poTbl, $diagTbl, $inList) {
|
||||
$q->whereIn('creator_id', explode(',', $inList));
|
||||
$q->whereOrRaw(
|
||||
"EXISTS (SELECT 1 FROM `{$diagTbl}` dg WHERE dg.`id` = `{$poTbl}`.`diagnosis_id` "
|
||||
. "AND dg.`delete_time` IS NULL AND dg.`assistant_id` IN ({$inList}))"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,12 +133,14 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
$this->applyDoctorAssistantFilters($query);
|
||||
$this->applyPatientKeywordFilter($query);
|
||||
if (PrescriptionOrderLogic::canViewOrderListStatsAllScope($this->adminInfo)) {
|
||||
$this->applyDataScopeForPrescriptionOrder($query);
|
||||
return $query;
|
||||
}
|
||||
$assistantRid = (int) Config::get('project.prescription_order_stats_assistant_role_id', 2);
|
||||
$myRoles = array_map('intval', $this->adminInfo['role_id'] ?? []);
|
||||
if ($assistantRid > 0 && in_array($assistantRid, $myRoles, true)) {
|
||||
$this->applyAssistantDiagnosisOnlyFilter($query);
|
||||
$this->applyDataScopeForPrescriptionOrder($query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
@@ -118,6 +153,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
}
|
||||
});
|
||||
}
|
||||
$this->applyDataScopeForPrescriptionOrder($query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class RoleLogic extends BaseLogic
|
||||
'name' => $params['name'],
|
||||
'desc' => $params['desc'] ?? '',
|
||||
'sort' => $params['sort'] ?? 0,
|
||||
'data_scope' => self::normalizeDataScope($params['data_scope'] ?? null),
|
||||
]);
|
||||
|
||||
$data = [];
|
||||
@@ -90,6 +91,7 @@ class RoleLogic extends BaseLogic
|
||||
'name' => $params['name'],
|
||||
'desc' => $params['desc'] ?? '',
|
||||
'sort' => $params['sort'] ?? 0,
|
||||
'data_scope' => self::normalizeDataScope($params['data_scope'] ?? null),
|
||||
]);
|
||||
|
||||
if (!empty($menuId)) {
|
||||
@@ -142,13 +144,26 @@ class RoleLogic extends BaseLogic
|
||||
*/
|
||||
public static function detail(int $id): array
|
||||
{
|
||||
$detail = SystemRole::field('id,name,desc,sort')->find($id);
|
||||
$detail = SystemRole::field('id,name,desc,sort,data_scope')->find($id);
|
||||
$authList = $detail->roleMenuIndex()->select()->toArray();
|
||||
$menuId = array_column($authList, 'menu_id');
|
||||
$detail['menu_id'] = $menuId;
|
||||
return $detail->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化 data_scope:合法值 1-4,非法或缺省统一回退为 1(全部),保持与历史默认一致不增加风险。
|
||||
*/
|
||||
private static function normalizeDataScope($value): int
|
||||
{
|
||||
$v = (int) $value;
|
||||
if ($v >= 1 && $v <= 4) {
|
||||
return $v;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 角色数据
|
||||
|
||||
@@ -730,7 +730,7 @@ class OrderLogic
|
||||
* @param array $params order_type(-1全部已支付类型合计,0退款,1挂号费,2问诊费,3药品费用,4首付,5尾款,6其他), days(0今天,7,30)
|
||||
* @return array
|
||||
*/
|
||||
public static function orderStats(array $params = [])
|
||||
public static function orderStats(array $params = [], int $adminId = 0, ?array $adminInfo = null)
|
||||
{
|
||||
$allPaidOrderTypes = [1, 2, 3, 4, 5, 6];
|
||||
$orderType = array_key_exists('order_type', $params) ? (int) $params['order_type'] : 1;
|
||||
@@ -740,6 +740,11 @@ class OrderLogic
|
||||
$orderTypeName = $orderType === -1 ? '全部(已支付)' : self::$orderTypeNames[$orderType];
|
||||
$days = isset($params['days']) ? (int)$params['days'] : 7;
|
||||
|
||||
// 数据范围:当前用户可见 admin id 集合;null 表示全部
|
||||
$visibleAdminIds = ($adminInfo !== null && $adminId > 0)
|
||||
? \app\common\service\DataScope\DataScopeService::getVisibleAdminIds($adminId, $adminInfo)
|
||||
: null;
|
||||
|
||||
$endTime = !empty($params['end_time']) ? strtotime($params['end_time']) : time();
|
||||
if ($days === 0) {
|
||||
$startTime = strtotime(date('Y-m-d'));
|
||||
@@ -764,6 +769,13 @@ class OrderLogic
|
||||
} else {
|
||||
$query->where('order_type', $orderType)->where('status', 2);
|
||||
}
|
||||
if ($visibleAdminIds !== null) {
|
||||
if ($visibleAdminIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
} else {
|
||||
$query->whereIn('creator_id', $visibleAdminIds);
|
||||
}
|
||||
}
|
||||
$orderRows = $query
|
||||
->field('creator_id, count(*) as cnt, sum(amount) as total_amount')
|
||||
->group('creator_id')
|
||||
@@ -822,6 +834,13 @@ class OrderLogic
|
||||
} else {
|
||||
$todayQuery->where('order_type', $orderType)->where('status', 2);
|
||||
}
|
||||
if ($visibleAdminIds !== null) {
|
||||
if ($visibleAdminIds === []) {
|
||||
$todayQuery->whereRaw('0 = 1');
|
||||
} else {
|
||||
$todayQuery->whereIn('creator_id', $visibleAdminIds);
|
||||
}
|
||||
}
|
||||
$todayRows = $todayQuery
|
||||
->field('creator_id, count(*) as cnt, sum(amount) as total_amount')
|
||||
->group('creator_id')
|
||||
@@ -850,6 +869,9 @@ class OrderLogic
|
||||
}
|
||||
usort($todayRanking, fn($a, $b) => $b['count'] <=> $a['count']);
|
||||
|
||||
// 数据范围启用时(非 root/全部范围),无可见成员的部门不渲染
|
||||
$hideEmptyDept = $visibleAdminIds !== null;
|
||||
|
||||
$deptData = [];
|
||||
$assignedIds = [];
|
||||
foreach ($depts as $deptId => $deptName) {
|
||||
@@ -866,6 +888,9 @@ class OrderLogic
|
||||
'amount' => number_format((float)($amounts[$adminId] ?? 0), 2, '.', ''),
|
||||
];
|
||||
}
|
||||
if ($hideEmptyDept && $members === []) {
|
||||
continue;
|
||||
}
|
||||
$deptTotal = array_sum(array_column($members, 'count'));
|
||||
$deptAmount = array_sum(array_map(fn($m) => (float)$m['amount'], $members));
|
||||
$deptData[] = [
|
||||
|
||||
@@ -2115,19 +2115,34 @@ class DiagnosisLogic extends BaseLogic
|
||||
|
||||
/**
|
||||
* @notes 获取医助列表
|
||||
* @param int $adminId 当前管理员 id(用于数据范围;0 表示不过滤)
|
||||
* @param array|null $adminInfo 当前管理员信息(用于数据范围;null 表示不过滤)
|
||||
* @return array
|
||||
*/
|
||||
public static function getAssistants()
|
||||
public static function getAssistants(int $adminId = 0, ?array $adminInfo = null)
|
||||
{
|
||||
try {
|
||||
// 这里不要用 Admin 模型查询,否则会触发其 append(role_id/dept_id/jobs_id)
|
||||
// 每行再发多次查询,形成严重 N+1,统计页会被拖慢到十几秒。
|
||||
$assistants = \think\facade\Db::name('admin')
|
||||
$query = \think\facade\Db::name('admin')
|
||||
->alias('a')
|
||||
->join('admin_role ar', 'a.id = ar.admin_id')
|
||||
->where('ar.role_id', 2)
|
||||
->where('a.disable', 0)
|
||||
->whereNull('a.delete_time')
|
||||
->whereNull('a.delete_time');
|
||||
|
||||
// 数据范围:仅展示当前管理员可见的医助;null = 全部不过滤
|
||||
if ($adminInfo !== null && $adminId > 0) {
|
||||
$visibleIds = \app\common\service\DataScope\DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds !== null) {
|
||||
if ($visibleIds === []) {
|
||||
return [];
|
||||
}
|
||||
$query->whereIn('a.id', $visibleIds);
|
||||
}
|
||||
}
|
||||
|
||||
$assistants = $query
|
||||
->field(['a.id', 'a.name', 'a.account'])
|
||||
->order('a.id', 'asc')
|
||||
->distinct(true)
|
||||
@@ -3105,10 +3120,15 @@ class DiagnosisLogic extends BaseLogic
|
||||
* @param array $params start_time, end_time, days(可选,默认7)
|
||||
* @return array
|
||||
*/
|
||||
public static function assistantDiagnosisStats(array $params = [])
|
||||
public static function assistantDiagnosisStats(array $params = [], int $adminId = 0, ?array $adminInfo = null)
|
||||
{
|
||||
$days = isset($params['days']) ? (int)$params['days'] : 7;
|
||||
|
||||
// 数据范围:当前用户可见 admin id 集合;null 表示全部
|
||||
$visibleAdminIds = ($adminInfo !== null && $adminId > 0)
|
||||
? \app\common\service\DataScope\DataScopeService::getVisibleAdminIds($adminId, $adminInfo)
|
||||
: null;
|
||||
|
||||
$endTime = !empty($params['end_time']) ? strtotime($params['end_time']) : time();
|
||||
if ($days === 0) {
|
||||
$startTime = strtotime(date('Y-m-d'));
|
||||
@@ -3127,6 +3147,13 @@ class DiagnosisLogic extends BaseLogic
|
||||
|
||||
$assistantIds = \app\common\model\auth\AdminRole::where('role_id', 2)
|
||||
->column('admin_id');
|
||||
// 数据范围交集:仅展示「医助 ∩ 当前用户可见」
|
||||
if ($visibleAdminIds !== null) {
|
||||
$assistantIds = array_values(array_intersect(
|
||||
array_map('intval', $assistantIds),
|
||||
array_map('intval', $visibleAdminIds)
|
||||
));
|
||||
}
|
||||
if (empty($assistantIds)) {
|
||||
return [
|
||||
'date_range' => [date('Y-m-d', $startTime), date('Y-m-d', $endTime)],
|
||||
@@ -3192,6 +3219,9 @@ class DiagnosisLogic extends BaseLogic
|
||||
usort($todayRanking, fn($a, $b) => $b['count'] <=> $a['count']);
|
||||
$todayRanking = array_values(array_filter($todayRanking, fn($a) => $a['count'] > 0));
|
||||
|
||||
// 数据范围启用时(非 root/全部范围),无可见成员的部门不渲染
|
||||
$hideEmptyDept = $visibleAdminIds !== null;
|
||||
|
||||
$deptData = [];
|
||||
$assignedAdminIds = [];
|
||||
foreach ($depts as $deptId => $deptName) {
|
||||
@@ -3208,6 +3238,9 @@ class DiagnosisLogic extends BaseLogic
|
||||
'count' => $cnt,
|
||||
];
|
||||
}
|
||||
if ($hideEmptyDept && $assistants === []) {
|
||||
continue;
|
||||
}
|
||||
$deptData[] = [
|
||||
'dept_id' => (int)$deptId,
|
||||
'dept_name' => $deptName,
|
||||
|
||||
@@ -29,6 +29,7 @@ class RoleValidate extends BaseValidate
|
||||
'id' => 'require|checkRole',
|
||||
'name' => 'require|max:64|unique:' . SystemRole::class . ',name',
|
||||
'menu_id' => 'array',
|
||||
'data_scope' => 'in:1,2,3,4',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
@@ -36,7 +37,8 @@ class RoleValidate extends BaseValidate
|
||||
'name.require' => '请输入角色名称',
|
||||
'name.max' => '角色名称最长为16个字符',
|
||||
'name.unique' => '角色名称已存在',
|
||||
'menu_id.array' => '权限格式错误'
|
||||
'menu_id.array' => '权限格式错误',
|
||||
'data_scope.in' => '数据范围取值不合法',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -47,7 +49,7 @@ class RoleValidate extends BaseValidate
|
||||
*/
|
||||
public function sceneAdd()
|
||||
{
|
||||
return $this->only(['name', 'menu_id']);
|
||||
return $this->only(['name', 'menu_id', 'data_scope']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\lists\Traits;
|
||||
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use think\db\Query;
|
||||
|
||||
/**
|
||||
* 列表按「数据范围」过滤。
|
||||
*
|
||||
* 使用前置条件:宿主类须通过 BaseAdminDataLists 获得 $this->adminId 与 $this->adminInfo。
|
||||
*
|
||||
* 三种用法:
|
||||
* - applyDataScopeByOwner($q, 'creator_id') 直接按某列 IN
|
||||
* - applyDataScopeByOwnerColumns($q, ['creator_id', 'doctor_id']) 多列 OR
|
||||
* - applyDataScopeByExists($q, $sqlTemplate, 'owner_expr') 通过 exists 子查询(跨表)
|
||||
*/
|
||||
trait HasDataScopeFilter
|
||||
{
|
||||
/**
|
||||
* 单列过滤。null 表示豁免(ALL)。
|
||||
*/
|
||||
protected function applyDataScopeByOwner($query, string $ownerField): bool
|
||||
{
|
||||
if (!$this->dataScopeShouldApply()) {
|
||||
return false;
|
||||
}
|
||||
$ids = $this->getDataScopeVisibleAdminIds();
|
||||
if ($ids === null) {
|
||||
return false;
|
||||
}
|
||||
if ($ids === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return true;
|
||||
}
|
||||
$query->whereIn($ownerField, $ids);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多列 OR 过滤(任意属主列命中即可)。
|
||||
*
|
||||
* @param string[] $ownerFields
|
||||
*/
|
||||
protected function applyDataScopeByOwnerColumns($query, array $ownerFields): bool
|
||||
{
|
||||
if (!$this->dataScopeShouldApply()) {
|
||||
return false;
|
||||
}
|
||||
$ids = $this->getDataScopeVisibleAdminIds();
|
||||
if ($ids === null) {
|
||||
return false;
|
||||
}
|
||||
if ($ids === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return true;
|
||||
}
|
||||
$query->where(function ($q) use ($ownerFields, $ids) {
|
||||
$first = true;
|
||||
foreach ($ownerFields as $f) {
|
||||
if ($first) {
|
||||
$q->whereIn($f, $ids);
|
||||
$first = false;
|
||||
} else {
|
||||
$q->whereOr(function ($qq) use ($f, $ids) {
|
||||
$qq->whereIn($f, $ids);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* exists 子查询方式。
|
||||
* $subSqlTemplate 内部可使用占位符 `__OWNER_IDS__`,将被替换成逗号分隔的整数列表。
|
||||
*/
|
||||
protected function applyDataScopeByExistsSql($query, string $subSqlTemplate): bool
|
||||
{
|
||||
if (!$this->dataScopeShouldApply()) {
|
||||
return false;
|
||||
}
|
||||
$ids = $this->getDataScopeVisibleAdminIds();
|
||||
if ($ids === null) {
|
||||
return false;
|
||||
}
|
||||
if ($ids === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return true;
|
||||
}
|
||||
$inList = implode(',', $ids);
|
||||
$sql = str_replace('__OWNER_IDS__', $inList, $subSqlTemplate);
|
||||
$query->whereExists($sql);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可见 admin id;null = 全部。
|
||||
*
|
||||
* @return array<int>|null
|
||||
*/
|
||||
protected function getDataScopeVisibleAdminIds(): ?array
|
||||
{
|
||||
$adminId = property_exists($this, 'adminId') ? (int) $this->adminId : 0;
|
||||
$adminInfo = property_exists($this, 'adminInfo') && is_array($this->adminInfo) ? $this->adminInfo : [];
|
||||
|
||||
return DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
}
|
||||
|
||||
protected function dataScopeShouldApply(): bool
|
||||
{
|
||||
return DataScopeService::isEnabled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service\DataScope;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\model\auth\SystemRole;
|
||||
use think\facade\Config;
|
||||
|
||||
/**
|
||||
* 数据范围(数据隔离)工具服务。
|
||||
*
|
||||
* 设计约定:
|
||||
* - ALL (1) = 全部数据,不附加过滤
|
||||
* - DEPT_AND_CHILD (2) = 本部门及所有子部门(取 admin 全部部门的并集)
|
||||
* - DEPT (3) = 仅本部门(取 admin 全部部门的并集,不含子孙)
|
||||
* - SELF (4) = 仅本人
|
||||
*
|
||||
* 多角色时取「范围最大」= data_scope 最小值。
|
||||
* root 管理员固定为 ALL。未挂任何部门时,范围退化为 SELF(可由 config 关闭)。
|
||||
*
|
||||
* 关键返回:`getVisibleAdminIds` 返回 int[](可见 admin_id 集合)或 null(ALL = 不过滤)。
|
||||
*/
|
||||
class DataScopeService
|
||||
{
|
||||
public const SCOPE_ALL = 1;
|
||||
public const SCOPE_DEPT_AND_CHILD = 2;
|
||||
public const SCOPE_DEPT = 3;
|
||||
public const SCOPE_SELF = 4;
|
||||
|
||||
/**
|
||||
* 计算当前 admin 的有效数据范围。
|
||||
*/
|
||||
public static function getEffectiveScope(array $adminInfo): int
|
||||
{
|
||||
if (!self::isEnabled()) {
|
||||
return self::SCOPE_ALL;
|
||||
}
|
||||
if ((int) ($adminInfo['root'] ?? 0) === 1) {
|
||||
return self::SCOPE_ALL;
|
||||
}
|
||||
$roleIds = array_values(array_filter(array_map(
|
||||
'intval',
|
||||
is_array($adminInfo['role_id'] ?? null) ? $adminInfo['role_id'] : []
|
||||
)));
|
||||
$exempt = array_map('intval', Config::get('project.data_scope.exempt_roles', []) ?: []);
|
||||
if ($roleIds !== [] && array_intersect($roleIds, $exempt) !== []) {
|
||||
return self::SCOPE_ALL;
|
||||
}
|
||||
if ($roleIds === []) {
|
||||
return self::SCOPE_SELF;
|
||||
}
|
||||
$scopes = SystemRole::whereIn('id', $roleIds)
|
||||
->whereNull('delete_time')
|
||||
->column('data_scope');
|
||||
$scopes = array_values(array_filter(array_map('intval', $scopes), static function (int $v): bool {
|
||||
return $v >= self::SCOPE_ALL && $v <= self::SCOPE_SELF;
|
||||
}));
|
||||
if ($scopes === []) {
|
||||
return self::SCOPE_ALL;
|
||||
}
|
||||
|
||||
return (int) min($scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可见 admin id 集合;null = 不过滤(ALL)
|
||||
*
|
||||
* @return array<int>|null
|
||||
*/
|
||||
public static function getVisibleAdminIds(int $adminId, array $adminInfo): ?array
|
||||
{
|
||||
$scope = self::getEffectiveScope($adminInfo);
|
||||
if ($scope === self::SCOPE_ALL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($scope === self::SCOPE_SELF) {
|
||||
return $adminId > 0 ? [$adminId] : [];
|
||||
}
|
||||
|
||||
$myDeptIds = AdminDept::where('admin_id', $adminId)->column('dept_id');
|
||||
$myDeptIds = array_values(array_filter(array_map('intval', $myDeptIds), static function (int $v): bool {
|
||||
return $v > 0;
|
||||
}));
|
||||
|
||||
if ($myDeptIds === []) {
|
||||
$fallback = (bool) Config::get('project.data_scope.no_dept_fallback_self', true);
|
||||
|
||||
return $fallback ? [$adminId] : [];
|
||||
}
|
||||
|
||||
$targetDeptIds = [];
|
||||
if ($scope === self::SCOPE_DEPT) {
|
||||
$targetDeptIds = $myDeptIds;
|
||||
} else {
|
||||
foreach ($myDeptIds as $did) {
|
||||
foreach (DeptLogic::getSelfAndDescendantIds($did) as $id) {
|
||||
$id = (int) $id;
|
||||
if ($id > 0) {
|
||||
$targetDeptIds[$id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$targetDeptIds = array_keys($targetDeptIds);
|
||||
}
|
||||
|
||||
if ($targetDeptIds === []) {
|
||||
return $adminId > 0 ? [$adminId] : [];
|
||||
}
|
||||
|
||||
$ids = AdminDept::whereIn('dept_id', $targetDeptIds)->column('admin_id');
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $ids), static function (int $v): bool {
|
||||
return $v > 0;
|
||||
})));
|
||||
if ($adminId > 0 && !in_array($adminId, $ids, true)) {
|
||||
$ids[] = $adminId;
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
public static function isEnabled(): bool
|
||||
{
|
||||
return (bool) Config::get('project.data_scope.enabled', true);
|
||||
}
|
||||
|
||||
public static function isAll(array $adminInfo): bool
|
||||
{
|
||||
return self::getEffectiveScope($adminInfo) === self::SCOPE_ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字描述(日志 / 接口返回可选使用)
|
||||
*/
|
||||
public static function scopeLabel(int $scope): string
|
||||
{
|
||||
return [
|
||||
self::SCOPE_ALL => '全部',
|
||||
self::SCOPE_DEPT_AND_CHILD => '本部门及下级',
|
||||
self::SCOPE_DEPT => '仅本部门',
|
||||
self::SCOPE_SELF => '仅本人',
|
||||
][$scope] ?? '全部';
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,20 @@ return [
|
||||
// 业务订单列表金额统计:医助角色 ID;非支付审核白名单的医助仅统计其诊单 assistant_id=本人 的业绩
|
||||
'prescription_order_stats_assistant_role_id' => 2,
|
||||
|
||||
/*
|
||||
* 数据隔离(按部门):全局可用,角色上的 data_scope 控制范围
|
||||
* 1=全部 2=本部门及下级 3=仅本部门 4=仅本人
|
||||
* root 管理员永远视为「全部」;多角色取最大范围(数值最小)
|
||||
*/
|
||||
'data_scope' => [
|
||||
// 顶层总开关:false 时所有列表不应用按部门的数据隔离(仍保留老的白名单规则)
|
||||
'enabled' => true,
|
||||
// 兜底:当用户未挂任何部门、且角色非「全部」时,只能看到自己(true=只看自己;false=返回空)
|
||||
'no_dept_fallback_self' => true,
|
||||
// 跳过 DataScope 的豁免角色 ID(冗余开关;一般优先用角色 data_scope=1 表达)
|
||||
'exempt_roles' => [],
|
||||
],
|
||||
|
||||
// 腾讯云实时音视频(TRTC)配置
|
||||
'trtc' => [
|
||||
'sdkAppId' => (int)env('trtc.sdk_app_id', 1600127710),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 角色表新增数据范围(数据隔离)字段
|
||||
-- 1=全部数据(免隔离,root 默认全部)
|
||||
-- 2=本部门及子部门(按 admin_dept 关联 + dept 树的后代汇总可见 admin_id)
|
||||
-- 3=仅本部门
|
||||
-- 4=仅本人
|
||||
-- 迁移后保持旧行为:所有现有角色默认 1(全部),需要管理员按需逐角色改为 2/3/4 激活隔离
|
||||
ALTER TABLE `zyt_system_role`
|
||||
ADD COLUMN `data_scope` tinyint(1) NOT NULL DEFAULT 1
|
||||
COMMENT '数据范围:1全部 2本部门及下级 3仅本部门 4仅本人' AFTER `sort`;
|
||||
Reference in New Issue
Block a user