Files
ctms/frontend/src/components/FaqList.vue
T

261 lines
6.5 KiB
Vue

<template>
<div class="faq-list-card">
<el-table
:data="items"
v-loading="loading"
style="width: 100%"
class="faq-table"
:row-class-name="rowClassName"
@row-click="onRowClick"
table-layout="fixed"
>
<el-table-column prop="question" :label="TEXT.common.fields.question" show-overflow-tooltip>
<template #default="scope">
<div class="question-cell">
<span class="question-icon">{{ questionInitial(scope.row.question) }}</span>
<el-link type="primary" :underline="false" class="question-link">
{{ scope.row.question }}
</el-link>
</div>
</template>
</el-table-column>
<el-table-column prop="category_id" :label="TEXT.common.labels.category" show-overflow-tooltip>
<template #default="scope">
<span class="category-pill">{{ categoryLabel(scope.row.category_id) }}</span>
</template>
</el-table-column>
<el-table-column prop="updated_at" :label="TEXT.common.labels.updatedAt" show-overflow-tooltip>
<template #default="scope">
<span class="time-text">
<span class="time-date">{{ splitDateTime(scope.row.updated_at).date }}</span>
<span class="time-clock">{{ splitDateTime(scope.row.updated_at).time }}</span>
</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="TEXT.common.fields.status">
<template #default="scope">
<el-tag :type="statusTagType(scope.row.status)" size="small" effect="light" round>{{ statusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column v-if="canDelete" :label="TEXT.common.labels.actions">
<template #default="scope">
<div class="cell-actions">
<el-button v-if="canDelete" link type="danger" size="small" class="danger" @click.stop="remove(scope.row)">
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from "element-plus";
import { computed } from "vue";
import { useRouter } from "vue-router";
import { deleteFaqItem } from "../api/faqs";
import type { FaqItem, FaqCategory } from "../api/faqs";
import { displayDateTime, displayEnum } from "../utils/display";
import { TEXT } from "../locales";
const props = defineProps<{
items: FaqItem[];
categories: FaqCategory[];
loading: boolean;
canUpdate: boolean;
canDelete: boolean;
}>();
const emit = defineEmits(["refresh"]);
const router = useRouter();
const categoryMap = computed(() =>
props.categories.reduce<Record<string, string>>((acc, cur) => {
acc[cur.id] = cur.name;
return acc;
}, {})
);
const categoryLabel = (categoryId: string) => {
return categoryMap.value[categoryId] || categoryId || "--";
};
const questionInitial = (question?: string) => {
return String(question || "?").trim().slice(0, 1) || "?";
};
const splitDateTime = (value?: string | number | Date | null) => {
const displayValue = displayDateTime(value);
const [date, time] = displayValue.split(" ");
return { date, time: time || "" };
};
const rowClassName = () => "faq-row";
const view = (row: FaqItem) => {
router.push(`/knowledge/medical-consult/${row.id}`);
};
const onRowClick = (row: FaqItem) => {
view(row);
};
const statusLabel = (status?: string) => {
return displayEnum(TEXT.enums.faqStatus, status || "");
};
const statusTagType = (status?: string) => {
if (status === "RESOLVED") return "success";
if (status === "PROCESSING") return "warning";
return "info";
};
const remove = async (row: FaqItem) => {
if (!props.canDelete) {
ElMessage.warning("权限不足");
return;
}
const ok = await ElMessageBox.confirm(
TEXT.modules.knowledgeMedicalConsult.confirmDeleteItem.replace("{name}", row.question),
TEXT.common.labels.tips
).catch(() => null);
if (!ok) return;
try {
await deleteFaqItem(row.id, row.study_id);
ElMessage.success(TEXT.common.messages.deleteSuccess);
emit("refresh");
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.deleteFailed);
}
};
</script>
<style scoped>
.faq-list-card {
min-height: 240px;
}
.faq-table {
background: transparent;
}
.faq-table :deep(.el-table__inner-wrapper::before) {
display: none;
}
.faq-table :deep(.el-table__header-wrapper th.el-table__cell) {
border-bottom: 0;
background: transparent;
color: #8a98aa;
font-size: 14px;
font-weight: 800;
}
.faq-table :deep(.el-table__body-wrapper) {
background: transparent;
}
.faq-table :deep(.el-table__body) {
border-collapse: separate;
border-spacing: 0 10px;
}
.faq-table :deep(.faq-row) {
cursor: pointer;
}
.faq-table :deep(.faq-row td.el-table__cell) {
padding: 12px 0;
border-bottom: 1px solid #e8eef6;
background: #ffffff;
transition: background 0.18s ease;
}
.faq-table :deep(.faq-row:hover td.el-table__cell) {
background: #f8f9fb;
}
.faq-table :deep(col:nth-child(1)) { width: 43%; }
.faq-table :deep(col:nth-child(2)) { width: 14%; }
.faq-table :deep(col:nth-child(3)) { width: 14%; }
.faq-table :deep(col:nth-child(4)) { width: 14%; }
.faq-table :deep(col:nth-child(5)) { width: 15%; }
/* 紧凑操作按钮 */
.faq-row .cell-actions { display: flex; gap: 4px; white-space: nowrap; }
.faq-row .cell-actions .el-button { font-size: 12px; padding: 0; height: auto; }
.faq-row .cell-actions .el-button + .el-button { margin-left: 0; }
.danger { color: #f56c6c !important; }
.question-cell {
display: flex;
align-items: center;
gap: 14px;
min-width: 0;
}
.question-icon {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 10px;
background: #f3f7fb;
color: #5e6f7b;
font-size: 15px;
font-weight: 800;
}
.question-link {
min-width: 0;
color: #26323b;
font-size: 15px;
font-weight: 800;
}
.category-pill {
display: inline-flex;
align-items: center;
max-width: 100%;
height: 26px;
padding: 0 10px;
overflow: hidden;
border-radius: 7px;
background: #eaf4ff;
color: #2c73d2;
font-size: 13px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.time-text {
display: inline-flex;
flex-direction: column;
gap: 2px;
color: #8794a6;
font-size: 12px;
font-weight: 700;
font-variant-numeric: tabular-nums;
line-height: 1.25;
white-space: nowrap;
}
.time-clock {
color: #9aa6b6;
font-size: 11px;
}
.danger {
color: #f56c6c;
}
.question-link {
cursor: pointer;
}
</style>