数据字典集中治理
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+20
-20
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CTMS</title>
|
||||
<script type="module" crossorigin src="/assets/index-BA1yIlA_.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DMvYZYMw.css">
|
||||
<script type="module" crossorigin src="/assets/index-DWloMSfp.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D1uShwvc.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Dict } from "./types";
|
||||
import { createDict } from "./utils";
|
||||
|
||||
const seriousnessItems = [
|
||||
{ value: "NON_SERIOUS", label: "非严重", order: 1 },
|
||||
{ value: "SERIOUS", label: "严重", order: 2, color: "danger" },
|
||||
];
|
||||
|
||||
const severityItems = [
|
||||
{ value: "MILD", label: "轻度", order: 1 },
|
||||
{ value: "MODERATE", label: "中度", order: 2 },
|
||||
{ value: "SEVERE", label: "重度", order: 3, color: "danger" },
|
||||
];
|
||||
|
||||
export const aeSeriousnessDict: Dict = createDict(seriousnessItems);
|
||||
export const aeSeverityDict: Dict = createDict(severityItems);
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { Dict } from "./types";
|
||||
import { createDict } from "./utils";
|
||||
import { statusDict } from "./status.dict";
|
||||
|
||||
const categoryItems = [
|
||||
{ value: "SITE_FEE", label: "中心费用", order: 1 },
|
||||
{ value: "SUBJECT_FEE", label: "受试者费用", order: 2 },
|
||||
{ value: "TRAVEL_FEE", label: "差旅费用", order: 3 },
|
||||
{ value: "OTHER", label: "其他", order: 99 },
|
||||
];
|
||||
|
||||
export const financeCategoryDict: Dict = createDict(categoryItems);
|
||||
export const financeStatusDict = statusDict;
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Dict } from "./types";
|
||||
import { createDict } from "./utils";
|
||||
|
||||
const transactionItems = [
|
||||
{ value: "RECEIPT", label: "接收", order: 1 },
|
||||
{ value: "DISPENSE", label: "发药", order: 2 },
|
||||
{ value: "RETURN", label: "回收", order: 3 },
|
||||
{ value: "DESTROY", label: "销毁", order: 4 },
|
||||
{ value: "TRANSFER", label: "转移", order: 5 },
|
||||
];
|
||||
|
||||
export const impTransactionTypeDict: Dict = createDict(transactionItems);
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from "./types";
|
||||
export * from "./utils";
|
||||
export * from "./status.dict";
|
||||
export * from "./priority.dict";
|
||||
export * from "./role.dict";
|
||||
export * from "./ae.dict";
|
||||
export * from "./finance.dict";
|
||||
export * from "./imp.dict";
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Dict } from "./types";
|
||||
import { createDict } from "./utils";
|
||||
|
||||
const items = [
|
||||
{ value: "LOW", label: "低", color: "info", order: 1 },
|
||||
{ value: "MEDIUM", label: "中", color: "warning", order: 2 },
|
||||
{ value: "HIGH", label: "高", color: "danger", order: 3 },
|
||||
];
|
||||
|
||||
export const priorityDict: Dict = createDict(items);
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Dict } from "./types";
|
||||
import { createDict } from "./utils";
|
||||
|
||||
const items = [
|
||||
{ value: "ADMIN", label: "管理员", order: 1 },
|
||||
{ value: "PM", label: "项目负责人", order: 2 },
|
||||
{ value: "CRA", label: "CRA", order: 3 },
|
||||
{ value: "PV", label: "PV", order: 4 },
|
||||
{ value: "IMP", label: "药品管理员", order: 5 },
|
||||
];
|
||||
|
||||
export const roleDict: Dict = createDict(items);
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Dict } from "./types";
|
||||
import { createDict } from "./utils";
|
||||
|
||||
const items = [
|
||||
{ value: "TODO", label: "待处理", color: "info", order: 1 },
|
||||
{ value: "DOING", label: "进行中", color: "warning", order: 2 },
|
||||
{ value: "DONE", label: "已完成", color: "success", order: 3 },
|
||||
{ value: "BLOCKED", label: "阻塞", color: "danger", order: 4 },
|
||||
{ value: "DRAFT", label: "草稿", color: "info", order: 10 },
|
||||
{ value: "SUBMITTED", label: "已提交", color: "warning", order: 11 },
|
||||
{ value: "APPROVED", label: "已审批", color: "success", order: 12 },
|
||||
{ value: "REJECTED", label: "已驳回", color: "danger", order: 13 },
|
||||
{ value: "PAID", label: "已支付", color: "success", order: 14 },
|
||||
{ value: "CLOSED", label: "已关闭", color: "success", order: 20 },
|
||||
{ value: "OPEN", label: "开放", color: "info", order: 19 },
|
||||
{ value: "FOLLOW_UP", label: "随访中", color: "warning", order: 18 },
|
||||
{ value: "OVERDUE", label: "已逾期", color: "danger", order: 30 },
|
||||
{ value: "SCREENING", label: "筛选中", color: "info", order: 40 },
|
||||
{ value: "ENROLLED", label: "已入组", color: "success", order: 41 },
|
||||
{ value: "COMPLETED", label: "已完成", color: "success", order: 42 },
|
||||
{ value: "DROPPED", label: "已脱落", color: "danger", order: 43 },
|
||||
];
|
||||
|
||||
export const statusDict: Dict = createDict(items);
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface DictItem {
|
||||
value: string;
|
||||
label: string;
|
||||
color?: string;
|
||||
order?: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface Dict {
|
||||
items: DictItem[];
|
||||
get: (value?: string | null) => DictItem | undefined;
|
||||
list: () => DictItem[];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { Dict, DictItem } from "./types";
|
||||
|
||||
export const createDict = (items: DictItem[]): Dict => {
|
||||
const normalized = items.map((item, idx) => ({ order: idx, ...item }));
|
||||
const get = (value?: string | null) => normalized.find((i) => i.value === value);
|
||||
const list = () =>
|
||||
[...normalized].sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
return { items: normalized, get, list };
|
||||
};
|
||||
|
||||
export const getDictItem = (dict: Dict, value?: string | null) => dict.get(value);
|
||||
export const getDictLabel = (dict: Dict, value?: string | null) => dict.get(value)?.label || value || "";
|
||||
export const getDictColor = (dict: Dict, value?: string | null) => dict.get(value)?.color;
|
||||
export const getDictOptions = (dict: Dict) =>
|
||||
dict.list().map((i) => ({ label: i.label, value: i.value, disabled: false }));
|
||||
@@ -62,8 +62,9 @@ import CommentList from "../components/CommentList.vue";
|
||||
import AttachmentList from "../components/attachments/AttachmentList.vue";
|
||||
import PermissionAction from "../components/PermissionAction.vue";
|
||||
import { usePermission } from "../utils/permission";
|
||||
import { aeMachine, getAvailableActions, getStateColor, getStateLabel } from "../state-machine";
|
||||
import { aeMachine, getAvailableActions } from "../state-machine";
|
||||
import type { ActionConfig } from "../state-machine";
|
||||
import { statusDict, getDictColor, getDictLabel } from "../dictionaries";
|
||||
|
||||
const route = useRoute();
|
||||
const study = useStudyStore();
|
||||
@@ -75,8 +76,8 @@ const studyId = computed(() => study.currentStudy?.id || "");
|
||||
const { can } = usePermission();
|
||||
const aeState = computed(() => (ae.value?.status as string) || "NEW");
|
||||
const availableActions = computed(() => getAvailableActions(aeMachine, aeState.value));
|
||||
const stateLabel = (v?: string | null) => getStateLabel(aeMachine, v);
|
||||
const stateColor = (v?: string | null) => getStateColor(aeMachine, v) || "info";
|
||||
const stateLabel = (v?: string | null) => getDictLabel(statusDict, v || "");
|
||||
const stateColor = (v?: string | null) => getDictColor(statusDict, v || "") || "info";
|
||||
|
||||
const enrichOverdue = (item: any) => {
|
||||
if (!item) return item;
|
||||
|
||||
@@ -53,7 +53,8 @@ import { fetchFinanceItem } from "../api/finance";
|
||||
import { useStudyStore } from "../store/study";
|
||||
import { useAuthStore } from "../store/auth";
|
||||
import { usePermission } from "../utils/permission";
|
||||
import { financeMachine, getAvailableActions, getStateColor, getStateLabel } from "../state-machine";
|
||||
import { financeMachine, getAvailableActions } from "../state-machine";
|
||||
import { financeStatusDict, getDictColor, getDictLabel } from "../dictionaries";
|
||||
|
||||
const study = useStudyStore();
|
||||
const auth = useAuthStore();
|
||||
@@ -83,8 +84,8 @@ const loadItem = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const stateLabel = (v?: string | null) => getStateLabel(financeMachine, v);
|
||||
const stateColor = (v?: string | null) => getStateColor(financeMachine, v) || "info";
|
||||
const stateLabel = (v?: string | null) => getDictLabel(financeStatusDict, v || "");
|
||||
const stateColor = (v?: string | null) => getDictColor(financeStatusDict, v || "") || "info";
|
||||
|
||||
const formatAmount = (num?: number | string) => {
|
||||
const n = Number(num);
|
||||
|
||||
@@ -42,6 +42,7 @@ import { usePermission } from "../utils/permission";
|
||||
import VisitTable from "../components/VisitTable.vue";
|
||||
import { getAvailableActions, getStateColor, getStateLabel, subjectMachine } from "../state-machine";
|
||||
import type { ActionConfig } from "../state-machine";
|
||||
import { statusDict, getDictLabel, getDictColor } from "../dictionaries";
|
||||
|
||||
const route = useRoute();
|
||||
const study = useStudyStore();
|
||||
@@ -58,8 +59,8 @@ const permissionMap: Record<string, string> = {
|
||||
drop: "subject.drop",
|
||||
};
|
||||
const availableActions = computed(() => getAvailableActions(subjectMachine, subject.value?.status));
|
||||
const stateLabel = (v?: string | null) => getStateLabel(subjectMachine, v);
|
||||
const stateColor = (v?: string | null) => getStateColor(subjectMachine, v) || "info";
|
||||
const stateLabel = (v?: string | null) => getDictLabel(statusDict, v || "");
|
||||
const stateColor = (v?: string | null) => getDictColor(statusDict, v || "") || "info";
|
||||
|
||||
const loadSubject = async () => {
|
||||
if (!study.currentStudy) return;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-card class="mb-12">
|
||||
<div class="filters">
|
||||
<el-select v-model="filters.status" placeholder="状态" clearable @change="loadTasks">
|
||||
<el-option v-for="s in statuses" :key="s" :label="s" :value="s" />
|
||||
<el-option v-for="opt in statusOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
<el-select v-model="filters.milestone_id" placeholder="里程碑" clearable @change="loadTasks">
|
||||
<el-option v-for="m in milestones" :key="m.id" :label="m.name" :value="m.id" />
|
||||
@@ -25,12 +25,14 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assignee_id" label="负责人" width="160" />
|
||||
<el-table-column prop="priority" label="优先级" width="100" />
|
||||
<el-table-column prop="priority" label="优先级" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="priorityColor(scope.row.priority)">{{ priorityLabel(scope.row.priority) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 'DONE' ? 'success' : scope.row.status === 'BLOCKED' ? 'danger' : 'info'">
|
||||
{{ statusLabel(scope.row.status) }}
|
||||
</el-tag>
|
||||
<el-tag :type="statusColor(scope.row.status)">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="due_date" label="截止日期" width="140" />
|
||||
@@ -66,6 +68,7 @@ import { useAuthStore } from "../store/auth";
|
||||
import PermissionAction from "../components/PermissionAction.vue";
|
||||
import { usePermission } from "../utils/permission";
|
||||
import TaskForm from "../components/TaskForm.vue";
|
||||
import { getDictColor, getDictLabel, getDictOptions, statusDict, priorityDict } from "../dictionaries";
|
||||
|
||||
const study = useStudyStore();
|
||||
const auth = useAuthStore();
|
||||
@@ -77,7 +80,7 @@ const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = 10;
|
||||
|
||||
const statuses = ["TODO", "DOING", "DONE", "BLOCKED"];
|
||||
const statusOptions = getDictOptions(statusDict).filter((opt) => ["TODO", "DOING", "DONE", "BLOCKED"].includes(opt.value));
|
||||
|
||||
const filters = ref({
|
||||
status: "",
|
||||
@@ -179,8 +182,10 @@ const loadSavedFilters = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const statusLabel = (v: string) =>
|
||||
({ TODO: "待处理", DOING: "进行中", DONE: "已完成", BLOCKED: "阻塞" }[v] || v);
|
||||
const statusLabel = (v: string) => getDictLabel(statusDict, v);
|
||||
const statusColor = (v: string) => getDictColor(statusDict, v) || "info";
|
||||
const priorityLabel = (v: string) => getDictLabel(priorityDict, v);
|
||||
const priorityColor = (v: string) => getDictColor(priorityDict, v) || "info";
|
||||
|
||||
const milestoneName = (id: string) => milestoneMap.value[id] || id || "-";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user