Step F7:数据管理
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { apiGet, apiPost, apiPatch } from "./axios";
|
||||
import type { ApiListResponse } from "../types/api";
|
||||
|
||||
export const fetchDataQueries = (studyId: string, params?: Record<string, any>) =>
|
||||
apiGet<ApiListResponse<any>>(`/api/v1/studies/${studyId}/data-queries/`, { params });
|
||||
|
||||
export const createDataQuery = (studyId: string, payload: Record<string, any>) =>
|
||||
apiPost(`/api/v1/studies/${studyId}/data-queries/`, payload);
|
||||
|
||||
export const updateDataQuery = (studyId: string, queryId: string, payload: Record<string, any>) =>
|
||||
apiPatch(`/api/v1/studies/${studyId}/data-queries/${queryId}`, payload);
|
||||
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<el-dialog :title="isEdit ? '编辑数据问题' : '新建数据问题'" v-model="visible" width="600px" @close="onClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="受试者" prop="subject_id">
|
||||
<el-select v-model="form.subject_id" placeholder="可留空" clearable filterable>
|
||||
<el-option v-for="s in subjects || []" :key="s.id" :label="s.subject_no || s.id" :value="s.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别" prop="category">
|
||||
<el-select v-model="form.category" placeholder="请选择">
|
||||
<el-option label="MISSING" value="MISSING" />
|
||||
<el-option label="INCONSISTENT" value="INCONSISTENT" />
|
||||
<el-option label="OUT_OF_RANGE" value="OUT_OF_RANGE" />
|
||||
<el-option label="OTHER" value="OTHER" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级" prop="priority">
|
||||
<el-select v-model="form.priority" placeholder="请选择">
|
||||
<el-option label="LOW" value="LOW" />
|
||||
<el-option label="MEDIUM" value="MEDIUM" />
|
||||
<el-option label="HIGH" value="HIGH" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="指派给" prop="assigned_to">
|
||||
<el-input v-model="form.assigned_to" placeholder="用户ID,可留空" />
|
||||
</el-form-item>
|
||||
<el-form-item label="截止日期" prop="due_date">
|
||||
<el-date-picker v-model="form.due_date" type="date" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { createDataQuery, updateDataQuery } from "../api/dataQueries";
|
||||
import { useStudyStore } from "../store/study";
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
query?: Record<string, any>;
|
||||
subjects?: any[];
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits(["update:modelValue", "success"]);
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (v: boolean) => emit("update:modelValue", v),
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const submitting = ref(false);
|
||||
const study = useStudyStore();
|
||||
const isEdit = computed(() => !!props.query);
|
||||
|
||||
const form = reactive({
|
||||
title: "",
|
||||
subject_id: "",
|
||||
category: "MISSING",
|
||||
priority: "MEDIUM",
|
||||
assigned_to: "",
|
||||
due_date: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
title: [{ required: true, message: "请输入标题", trigger: "blur" }],
|
||||
category: [{ required: true, message: "请选择类别", trigger: "change" }],
|
||||
priority: [{ required: true, message: "请选择优先级", trigger: "change" }],
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.query,
|
||||
(val) => {
|
||||
if (val) {
|
||||
Object.assign(form, {
|
||||
title: val.title || "",
|
||||
subject_id: val.subject_id || "",
|
||||
category: val.category || "MISSING",
|
||||
priority: val.priority || "MEDIUM",
|
||||
assigned_to: val.assigned_to || "",
|
||||
due_date: val.due_date || "",
|
||||
description: val.description || "",
|
||||
});
|
||||
} else {
|
||||
Object.assign(form, {
|
||||
title: "",
|
||||
subject_id: "",
|
||||
category: "MISSING",
|
||||
priority: "MEDIUM",
|
||||
assigned_to: "",
|
||||
due_date: "",
|
||||
description: "",
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const onClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!formRef.value) return;
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (!study.currentStudy) {
|
||||
ElMessage.error("未选择项目");
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
try {
|
||||
const payload: Record<string, any> = {};
|
||||
Object.entries(form).forEach(([k, v]) => {
|
||||
if (v !== "" && v !== null && v !== undefined) payload[k] = v;
|
||||
});
|
||||
if (isEdit.value && props.query) {
|
||||
await updateDataQuery(study.currentStudy.id, props.query.id, payload);
|
||||
} else {
|
||||
await createDataQuery(study.currentStudy.id, payload);
|
||||
}
|
||||
ElMessage.success("提交成功");
|
||||
emit("success");
|
||||
onClose();
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || "提交失败");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -22,6 +22,8 @@
|
||||
<el-menu-item index="/study/subjects">受试者</el-menu-item>
|
||||
<el-menu-item index="/study/aes">AE</el-menu-item>
|
||||
<el-menu-item index="/study/issues">风险/问题</el-menu-item>
|
||||
<el-menu-item index="/study/data-queries">数据问题</el-menu-item>
|
||||
<el-menu-item index="/study/verifications">SDV/SDR</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<el-table :data="verifications" v-loading="loading" style="width: 100%">
|
||||
<el-table-column prop="subject_id" label="受试者ID" />
|
||||
<el-table-column prop="level" label="类型" width="100" />
|
||||
<el-table-column label="完成度" width="180">
|
||||
<template #default="scope">
|
||||
<el-progress :percentage="scope.row.percent || 0" :stroke-width="12" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_verified_at" label="最近核查" width="140" />
|
||||
<el-table-column prop="verifier_id" label="核查人" width="160" />
|
||||
<el-table-column v-if="canEdit" label="操作" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link size="small" @click="$emit('edit', scope.row)">更新进度</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
verifications: any[];
|
||||
canEdit: boolean;
|
||||
loading: boolean;
|
||||
}>();
|
||||
defineEmits(["edit"]);
|
||||
</script>
|
||||
@@ -13,11 +13,13 @@ import SubjectDetail from "../views/SubjectDetail.vue";
|
||||
import Aes from "../views/Aes.vue";
|
||||
import AeDetail from "../views/AeDetail.vue";
|
||||
import DataQueries from "../views/DataQueries.vue";
|
||||
import DataQueryDetail from "../views/DataQueryDetail.vue";
|
||||
import Imp from "../views/Imp.vue";
|
||||
import Finance from "../views/Finance.vue";
|
||||
import Faq from "../views/Faq.vue";
|
||||
import Issues from "../views/Issues.vue";
|
||||
import IssueDetail from "../views/IssueDetail.vue";
|
||||
import Verification from "../views/Verification.vue";
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
@@ -85,6 +87,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: DataQueries,
|
||||
meta: { title: "数据问题", requiresStudy: true },
|
||||
},
|
||||
{
|
||||
path: "study/data-queries/:queryId",
|
||||
name: "StudyDataQueryDetail",
|
||||
component: DataQueryDetail,
|
||||
meta: { title: "数据问题详情", requiresStudy: true },
|
||||
},
|
||||
{
|
||||
path: "study/issues",
|
||||
name: "StudyIssues",
|
||||
@@ -115,6 +123,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: Faq,
|
||||
meta: { title: "FAQ", requiresStudy: true },
|
||||
},
|
||||
{
|
||||
path: "study/verifications",
|
||||
name: "StudyVerifications",
|
||||
component: Verification,
|
||||
meta: { title: "核查进度", requiresStudy: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
<el-card>
|
||||
<el-table :data="aes" v-loading="loading" style="width: 100%" @row-click="goDetail">
|
||||
<el-table-column prop="term" label="事件" />
|
||||
<el-table-column prop="subject_id" label="受试者ID" width="200" />
|
||||
<el-table-column label="受试者" width="200">
|
||||
<template #default="scope">
|
||||
{{ subjectMap[scope.row.subject_id] || scope.row.subject_id || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="seriousness" label="严重性" width="140" />
|
||||
<el-table-column prop="onset_date" label="发生日期" width="140" />
|
||||
<el-table-column prop="report_due_date" label="报告截止" width="140" />
|
||||
@@ -62,6 +66,12 @@ const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = 10;
|
||||
const subjects = ref<any[]>([]);
|
||||
const subjectMap = computed(() =>
|
||||
subjects.value.reduce<Record<string, string>>((acc, cur) => {
|
||||
acc[cur.id] = cur.subject_no || cur.id;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const statuses = ["NEW", "FOLLOW_UP", "CLOSED"];
|
||||
|
||||
|
||||
@@ -1,13 +1,158 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<el-card><h2>数据问题</h2></el-card>
|
||||
<el-card class="mb-12">
|
||||
<div class="filters">
|
||||
<el-select v-model="filters.status" placeholder="状态" clearable @change="loadQueries">
|
||||
<el-option v-for="s in statuses" :key="s" :label="s" :value="s" />
|
||||
</el-select>
|
||||
<el-switch v-model="filters.overdue" active-text="仅逾期" @change="loadQueries" />
|
||||
<el-input v-model="filters.assigned_to" placeholder="指派人ID" style="width: 180px" @change="loadQueries" />
|
||||
<div class="spacer" />
|
||||
<el-button type="primary" v-if="canEdit" @click="showForm = true">新建 Query</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<el-table :data="queries" v-loading="loading" style="width: 100%" @row-click="goDetail">
|
||||
<el-table-column prop="title" label="标题" />
|
||||
<el-table-column label="受试者">
|
||||
<template #default="scope">
|
||||
{{ subjectMap[scope.row.subject_id] || scope.row.subject_id || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="category" label="类别" width="140" />
|
||||
<el-table-column prop="priority" label="优先级" width="120" />
|
||||
<el-table-column prop="due_date" label="截止日期" width="140" />
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.is_overdue ? 'danger' : 'info'">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pagination"
|
||||
layout="prev, pager, next"
|
||||
:page-size="pageSize"
|
||||
:current-page="page"
|
||||
:total="total"
|
||||
@current-change="onPageChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<DataQueryForm v-model="showForm" :subjects="subjects" @success="loadQueries" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { fetchDataQueries } from "../api/dataQueries";
|
||||
import { fetchSubjects } from "../api/subjects";
|
||||
import { useStudyStore } from "../store/study";
|
||||
import { useAuthStore } from "../store/auth";
|
||||
import DataQueryForm from "../components/DataQueryForm.vue";
|
||||
|
||||
const study = useStudyStore();
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
|
||||
const queries = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = 10;
|
||||
const subjects = ref<any[]>([]);
|
||||
const subjectMap = computed(() =>
|
||||
subjects.value.reduce<Record<string, string>>((acc, cur) => {
|
||||
acc[cur.id] = cur.subject_no || cur.id;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const statuses = ["OPEN", "IN_PROGRESS", "ANSWERED", "CLOSED"];
|
||||
|
||||
const filters = ref({
|
||||
status: "",
|
||||
overdue: false,
|
||||
assigned_to: "",
|
||||
});
|
||||
|
||||
const showForm = ref(false);
|
||||
|
||||
const canEdit = computed(() => {
|
||||
const role = auth.user?.role;
|
||||
return role === "ADMIN" || role === "PM" || role === "CRA";
|
||||
});
|
||||
|
||||
const loadQueries = async () => {
|
||||
if (!study.currentStudy) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: Record<string, any> = { skip: (page.value - 1) * pageSize, limit: pageSize };
|
||||
if (filters.value.status) params.status = filters.value.status;
|
||||
if (filters.value.assigned_to) params.assigned_to = filters.value.assigned_to;
|
||||
if (filters.value.overdue) params.overdue = true;
|
||||
const { data } = await fetchDataQueries(study.currentStudy.id, params);
|
||||
const markOverdue = (list: any[]) =>
|
||||
list.map((item) => ({
|
||||
...item,
|
||||
is_overdue:
|
||||
item.is_overdue ?? (item.due_date && item.status !== "CLOSED" && new Date() > new Date(item.due_date)),
|
||||
}));
|
||||
if (Array.isArray(data)) {
|
||||
queries.value = markOverdue(data);
|
||||
total.value = data.length;
|
||||
} else {
|
||||
const list = data.items || [];
|
||||
queries.value = markOverdue(list);
|
||||
total.value = data.total || queries.value.length;
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || "数据问题加载失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onPageChange = (p: number) => {
|
||||
page.value = p;
|
||||
loadQueries();
|
||||
};
|
||||
|
||||
const goDetail = (row: any) => {
|
||||
router.push(`/study/data-queries/${row.id}`);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
if (!auth.user && auth.token) {
|
||||
await auth.fetchMe().catch(() => {});
|
||||
}
|
||||
if (study.currentStudy) {
|
||||
const { data } = await fetchSubjects(study.currentStudy.id, { skip: 0, limit: 500 });
|
||||
subjects.value = data.items || data || [];
|
||||
}
|
||||
loadQueries();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
padding: 16px;
|
||||
}
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.pagination {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
.mb-12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="page" v-if="query">
|
||||
<el-card class="mb-12">
|
||||
<div class="header">
|
||||
<div>
|
||||
<h3>数据问题详情</h3>
|
||||
<p>{{ query.title }}</p>
|
||||
</div>
|
||||
<el-button v-if="canEdit" type="primary" size="small" @click="nextStatus">流转状态</el-button>
|
||||
</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="受试者">
|
||||
{{ subjectMap[query.subject_id] || query.subject_id || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类别">{{ query.category }}</el-descriptions-item>
|
||||
<el-descriptions-item label="优先级">{{ query.priority }}</el-descriptions-item>
|
||||
<el-descriptions-item label="截止日期">{{ query.due_date }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="query.is_overdue ? 'danger' : 'info'">{{ query.status }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="描述">{{ query.description }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<el-tabs>
|
||||
<el-tab-pane label="评论">
|
||||
<CommentList :study-id="studyId" entity-type="data-queries" :entity-id="query.id" :can-comment="true" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="附件">
|
||||
<AttachmentList :study-id="studyId" entity-type="data-queries" :entity-id="query.id" :can-upload="canEdit" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div v-else class="page">
|
||||
<el-skeleton rows="4" animated />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { fetchDataQueries, updateDataQuery } from "../api/dataQueries";
|
||||
import { fetchSubjects } from "../api/subjects";
|
||||
import { useStudyStore } from "../store/study";
|
||||
import { useAuthStore } from "../store/auth";
|
||||
import CommentList from "../components/CommentList.vue";
|
||||
import AttachmentList from "../components/AttachmentList.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const study = useStudyStore();
|
||||
const auth = useAuthStore();
|
||||
|
||||
const query = ref<any | null>(null);
|
||||
const studyId = computed(() => study.currentStudy?.id || "");
|
||||
const subjects = ref<any[]>([]);
|
||||
const subjectMap = computed(() =>
|
||||
subjects.value.reduce<Record<string, string>>((acc, cur) => {
|
||||
acc[cur.id] = cur.subject_no || cur.id;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const canEdit = computed(() => {
|
||||
const role = auth.user?.role;
|
||||
return role === "ADMIN" || role === "PM" || role === "CRA";
|
||||
});
|
||||
|
||||
const enrichOverdue = (item: any) => {
|
||||
if (!item) return item;
|
||||
return {
|
||||
...item,
|
||||
is_overdue:
|
||||
item.is_overdue ?? (item.due_date && item.status !== "CLOSED" && new Date() > new Date(item.due_date)),
|
||||
};
|
||||
};
|
||||
|
||||
const load = async () => {
|
||||
if (!study.currentStudy) return;
|
||||
try {
|
||||
const { data } = await fetchDataQueries(study.currentStudy.id, { skip: 0, limit: 500 });
|
||||
const list = data.items || data || [];
|
||||
query.value = enrichOverdue(list.find((i: any) => i.id === route.params.queryId));
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || "数据问题加载失败");
|
||||
}
|
||||
};
|
||||
|
||||
const nextStatus = async () => {
|
||||
if (!query.value) return;
|
||||
const flow = ["OPEN", "IN_PROGRESS", "ANSWERED", "CLOSED"];
|
||||
const idx = flow.indexOf(query.value.status);
|
||||
const next = flow[Math.min(idx + 1, flow.length - 1)];
|
||||
if (next === query.value.status) {
|
||||
ElMessage.info("已是最终状态");
|
||||
return;
|
||||
}
|
||||
await ElMessageBox.confirm(`确认将状态流转为 ${next}?`, "提示");
|
||||
try {
|
||||
const resp = await updateDataQuery(studyId.value, query.value.id, { status: next });
|
||||
query.value = enrichOverdue(resp.data);
|
||||
ElMessage.success("状态已更新");
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || "更新失败");
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
if (!auth.user && auth.token) {
|
||||
await auth.fetchMe().catch(() => {});
|
||||
}
|
||||
if (study.currentStudy) {
|
||||
const { data } = await fetchSubjects(study.currentStudy.id, { skip: 0, limit: 500 });
|
||||
subjects.value = data.items || data || [];
|
||||
}
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
padding: 16px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mb-12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -8,13 +8,26 @@
|
||||
</el-select>
|
||||
<el-input v-model="filters.site_id" placeholder="中心ID" style="width: 180px" @change="load" />
|
||||
<div class="spacer" />
|
||||
<el-button v-if="canEdit" type="primary" @click="openCreate">新增进度</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<VerificationTable :verifications="verifications" :can-edit="canEdit" :loading="loading" @edit="openEdit" />
|
||||
|
||||
<el-dialog title="更新进度" v-model="showEdit" width="520px">
|
||||
<el-dialog :title="editForm.id ? '更新进度' : '新增进度'" v-model="showEdit" width="520px">
|
||||
<el-form :model="editForm" label-width="120px">
|
||||
<el-form-item label="受试者ID">
|
||||
<el-input v-model="editForm.subject_id" :disabled="!!editForm.id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="中心ID">
|
||||
<el-input v-model="editForm.site_id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-select v-model="editForm.level" placeholder="请选择">
|
||||
<el-option label="SDV" value="SDV" />
|
||||
<el-option label="SDR" value="SDR" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="完成度">
|
||||
<el-input-number v-model="editForm.percent" :min="0" :max="100" />
|
||||
</el-form-item>
|
||||
@@ -82,8 +95,22 @@ const openEdit = (row: any) => {
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
if (!canEdit.value) return;
|
||||
editForm.value = { subject_id: "", site_id: "", level: filters.value.level || "SDV", percent: 0, notes: "" };
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const submitEdit = async () => {
|
||||
if (!study.currentStudy) return;
|
||||
if (!editForm.value.subject_id) {
|
||||
ElMessage.warning("请输入受试者ID");
|
||||
return;
|
||||
}
|
||||
if (!editForm.value.level) {
|
||||
ElMessage.warning("请选择类型");
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
try {
|
||||
await upsertVerification(study.currentStudy.id, {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user