Files
ctms/frontend/src/views/DataQueryDetail.vue
T
2025-12-17 16:16:47 +08:00

137 lines
4.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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"
:show-uploader="true"
/>
</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/attachments/AttachmentList.vue";
import { usePermission } from "../utils/permission";
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 { can } = usePermission();
const canEdit = computed(() => can("dataquery.edit"));
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>