Files
tkPage/src/components/LiveRecordDialog.vue
2025-12-18 20:09:21 +08:00

240 lines
7.7 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.

<!-- LiveRecordDialog.vue -->
<template>
<el-dialog v-model="visible" title="直播记录" width="86vw" top="6vh" :close-on-click-modal="false" destroy-on-close>
<!-- 顶部工具栏 -->
<div class="toolbar">
<div class="left">
<el-input v-model="kw" placeholder="搜索hostsId / userId / tenantId / id" clearable
style="width: 320px" />
<el-select v-model="sortKey" style="width: 170px" placeholder="排序字段">
<el-option label="开始时间" value="startTimeFormatted" />
<el-option label="结束时间" value="endTimeFormatted" />
<el-option label="点赞数" value="likeCount" />
<el-option label="粉丝团" value="fansClubCount" />
</el-select>
<el-select v-model="sortOrder" style="width: 140px" placeholder="排序方式">
<el-option label="降序" value="desc" />
<el-option label="升序" value="asc" />
</el-select>
<el-checkbox v-model="onlyAbnormal">只看异常</el-checkbox>
<el-button @click="reset">重置</el-button>
</div>
<div class="right">
<el-tag type="info">总条数{{ filteredRows.length }}</el-tag>
<el-tag type="success">点赞合计{{ totalLikes }}</el-tag>
<el-tag type="warning">like=0{{ zeroLikeCount }}</el-tag>
</div>
</div>
<el-table :data="pagedRows" border height="62vh" style="width: 100%"
:default-sort="{ prop: 'startTimeFormatted', order: 'descending' }">
<el-table-column prop="id" label="ID" width="90" />
<el-table-column prop="userId" label="userId" width="90" />
<el-table-column prop="hostsId" label="hostsId" width="130" />
<el-table-column prop="tenantId" label="tenantId" width="100" />
<el-table-column prop="fansClubCount" label="粉丝团" width="90" />
<el-table-column prop="lightedVsTotalGifts" label="点亮/礼物" width="110" />
<el-table-column prop="startTimeFormatted" label="开始时间" width="170" />
<el-table-column prop="endTimeFormatted" label="结束时间" width="170" />
<el-table-column prop="durationFormatted" label="时长" width="120" />
<el-table-column prop="likeCount" label="点赞" width="110" sortable>
<template #default="{ row }">
<el-tag v-if="row.likeCount === 0" type="danger">0</el-tag>
<span v-else>{{ row.likeCount }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="入库时间" width="170" />
<el-table-column label="操作" fixed="right" width="160">
<template #default="{ row }">
<el-button size="small" @click="copyRow(row)">复制</el-button>
<el-button size="small" type="primary" @click="emitSelect(row)">选中</el-button>
</template>
</el-table-column>
</el-table>
<div class="footer">
<el-pagination v-model:current-page="page" v-model:page-size="pageSize" :page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper" :total="filteredRows.length" />
</div>
<template #footer>
<el-button @click="visible = false">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
type Row = {
id: number;
userId: number;
hostsId: string;
tenantId: number;
fansClubCount: number;
lightedVsTotalGifts: string;
startTimeFormatted: string;
endTimeFormatted: string;
likeCount: number;
durationFormatted: string;
createTime: string;
};
const props = defineProps<{
modelValue: boolean;
rows: Row[];
}>();
const emit = defineEmits<{
(e: "update:modelValue", v: boolean): void;
(e: "select", row: Row): void;
}>();
const visible = computed({
get: () => props.modelValue,
set: (v) => emit("update:modelValue", v),
});
const kw = ref("");
const sortKey = ref<keyof Row>("startTimeFormatted");
const sortOrder = ref<"asc" | "desc">("desc");
const onlyAbnormal = ref(false);
const page = ref(1);
const pageSize = ref(20);
watch([kw, sortKey, sortOrder, onlyAbnormal], () => {
page.value = 1;
});
function parseTime(s?: string) {
// "2025-12-18 13:12:11" -> Date
if (!s) return 0;
return new Date(s.replace(" ", "T")).getTime() || 0;
}
function durationSeconds(durationFormatted?: string) {
// 例:"2小时49分钟55秒" / "18分钟31秒" / "25秒"
if (!durationFormatted) return 0;
const h = Number((durationFormatted.match(/(\d+)\s*小时/) || [])[1] || 0);
const m = Number((durationFormatted.match(/(\d+)\s*分钟/) || [])[1] || 0);
const s = Number((durationFormatted.match(/(\d+)\s*秒/) || [])[1] || 0);
return h * 3600 + m * 60 + s;
}
const filteredRows = computed(() => {
const k = kw.value.trim().toLowerCase();
let arr = (props.rows || []).filter((r) => {
if (!k) return true;
const hay = `${r.id} ${r.userId} ${r.hostsId} ${r.tenantId}`.toLowerCase();
return hay.includes(k);
});
if (onlyAbnormal.value) {
arr = arr.filter((r) => r.likeCount === 0 || durationSeconds(r.durationFormatted) < 60);
}
// 排序
const key = sortKey.value;
const order = sortOrder.value;
arr = [...arr].sort((a, b) => {
const av = a[key] as any;
const bv = b[key] as any;
// 时间字段特殊处理
if (key === "startTimeFormatted" || key === "endTimeFormatted" || key === "createTime") {
const aa = parseTime(String(av));
const bb = parseTime(String(bv));
return order === "desc" ? bb - aa : aa - bb;
}
// 数字
if (typeof av === "number" && typeof bv === "number") {
return order === "desc" ? bv - av : av - bv;
}
// 字符串
return order === "desc"
? String(bv).localeCompare(String(av))
: String(av).localeCompare(String(bv));
});
return arr;
});
const pagedRows = computed(() => {
const start = (page.value - 1) * pageSize.value;
return filteredRows.value.slice(start, start + pageSize.value);
});
const totalLikes = computed(() =>
filteredRows.value.reduce((sum, r) => sum + (Number(r.likeCount) || 0), 0)
);
const zeroLikeCount = computed(() => filteredRows.value.filter((r) => r.likeCount === 0).length);
function reset() {
kw.value = "";
sortKey.value = "startTimeFormatted";
sortOrder.value = "desc";
onlyAbnormal.value = false;
page.value = 1;
pageSize.value = 20;
}
async function copyRow(row: Row) {
try {
await navigator.clipboard.writeText(JSON.stringify(row, null, 2));
ElMessage.success("已复制该行 JSON");
} catch {
ElMessage.warning("复制失败:浏览器不支持或无权限");
}
}
function emitSelect(row: Row) {
emit("select", row);
ElMessage.success(`已选中ID ${row.id}`);
}
</script>
<style scoped>
.toolbar {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
flex-wrap: wrap;
}
.toolbar .left {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.toolbar .right {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.footer {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
</style>