ql_apimanager_frontend/src/components/DataTable.vue
2025-07-30 20:01:47 +08:00

391 lines
13 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>
<div class="card">
<div class="card-body">
<h5 class="card-title text-center card-header">{{ title }}</h5>
<div id="zero-conf_wrapper" class="dataTables_wrapper dt-bootstrap4">
<div class="row align-items-center">
<div class="col-sm-12 col-md-6 d-flex align-items-center">
<div class="dataTables_length" id="zero-conf_length">
<label class="d-flex align-items-center" style="white-space: nowrap">
展示
<select
name="zero-conf_length"
aria-controls="zero-conf"
class="custom-select custom-select-sm form-control form-control-sm mx-2"
v-model="pageSize"
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
条数据
</label>
</div>
<div class="ms-3">
<label class="d-flex align-items-center" style="white-space: nowrap">
搜索:
<input
type="search"
class="form-control form-control-sm ms-2"
placeholder="请输入关键字"
aria-controls="zero-conf"
/>
<button class="btn btn-sm btn-secondary ms-2" @click="handleSearch">
搜索
</button>
</label>
</div>
</div>
<div class="col-sm-12 col-md-6 text-center">
<button class="btn btn-sm btn-primary ms-2" @click="handleAddDate">
添加
</button>
<button class="btn btn-sm btn-danger ms-2" @click="handleBatchDelete">
批量删除
</button>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<table
id="zero-conf"
class="display dataTable"
style="width: 100%"
role="grid"
aria-describedby="zero-conf_info"
>
<thead>
<tr role="row">
<th
class="sorting_asc"
tabindex="0"
aria-controls="zero-conf"
rowspan="1"
colspan="1"
aria-sort="ascending"
aria-label="Name: activate to sort column descending"
style="width: 85.5469px"
v-for="(item, index) in headers"
:key="index"
>
{{ item.text }}
</th>
</tr>
</thead>
<tbody>
<tr
role="row"
v-for="(item, rowIndex) in rows"
:key="rowIndex"
:class="rowIndex % 2 ? 'even' : 'odd'"
>
<td v-for="(header, colIndex) in headers" :key="colIndex">
<div v-if="header.type == null">
<span>{{ item[header.value] }}</span>
</div>
<div v-if="header.type == 'bool'">
<span
:class="[
'badge',
item[header.value] ? 'bg-danger' : 'bg-success',
]"
>{{ item[header.value] ? "禁用" : "正常" }}</span
>
</div>
<div v-if="header.type == 'arrObj'">
<span
class="badge bg-info"
v-for="(objItem, objIndex) in item[header.value]"
:key="objIndex"
>
{{ objItem[header.child] }}
</span>
</div>
<div v-if="header.type == 'money'">
<span>{{ item[header.value].toFixed(2) }}</span>
</div>
<div v-if="header.type == 'datetime'">
<span>{{ formatDateTime(item[header.value]) }}</span>
</div>
<div v-if="header.type == 'method'">
<span
:class="[
'badge',
'bg-warning',
]"
>{{ callMethod[item[header.value]] }}</span
>
</div>
<div v-if="header.type == 'find'">
<span class="badge bg-info">
{{ header.findValue.find(x => x.id == item[header.value]).name }}
</span>
</div>
</td>
<td>
<button
class="btn btn-outline-secondary m-b-xs"
@click="modify(item.id)"
>
修改
</button>
<button
class="btn btn-outline-danger m-b-xs"
@click="deleteData(item.id)"
>
删除
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<th
rowspan="1"
colspan="1"
v-for="(header, index) in headers"
:key="index"
>
{{ header.value }}
</th>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-5">
<div
class="dataTables_info"
id="zero-conf_info"
role="status"
aria-live="polite"
>
正在展示 {{ currentPageIndex * pageSize - (pageSize - 1) }} 到
{{ currentPageIndex * pageSize }} 条数据 总计 {{ dataCount }} 条
</div>
</div>
<div class="col-sm-12 col-md-7">
<div
class="dataTables_paginate paging_simple_numbers"
id="zero-conf_paginate"
>
<ul class="pagination">
<li
:class="[
'paginate_button',
'page-item',
'previous',
currentPageIndex == 1 ? 'disabled' : '',
]"
id="zero-conf_previous"
>
<a
href="#"
aria-controls="zero-conf"
data-dt-idx="0"
tabindex="0"
class="page-link"
@click.prevent="previousPage"
>上一页</a
>
</li>
<li
:class="[
'paginate_button',
'page-item',
item.index == currentPageIndex ? 'active' : '',
]"
v-for="(item, index) in pageBtn"
:key="index"
>
<a
href="#"
aria-controls="zero-conf"
:data-dt-idx="item.index"
tabindex="0"
class="page-link"
@click.prevent="currentPageIndex = item.index"
>{{ item.text }}</a
>
</li>
<li
:class="[
'paginate_button',
'page-item',
'next',
currentPageIndex == pageCount ? 'disabled' : '',
]"
id="zero-conf_next"
>
<a
href="#"
aria-controls="zero-conf"
data-dt-idx="7"
tabindex="0"
class="page-link"
@click.prevent="nextPage"
>下一页</a
>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "DataTable",
props: {
title: {
type: String,
default: "标题",
},
headers: {
// 表头数组 [{ text: '名字', value: 'name'}, ...]
type: Array,
required: true,
},
rows: {
// 数据数组 [{ name: '佐藤爱理', position: '会计', office: '东京', age: 33, startDate: '2008/11/28', salary: '162,700 元' }, ...]
type: Array,
required: true,
},
dataCount: {
type: Number,
required: true,
},
},
data() {
return {
//当前页索引
currentPageIndex: 1,
//单页大小
pageSize: 10,
//总页数
pageCount: 0,
//分页按钮数据,[{text:'1',index:1},.....]
pageBtn: [],
//调用方法
callMethod:['GET','POST']
};
},
mounted() {},
methods: {
//下一页
nextPage() {
if (this.currentPageIndex < this.pageCount) this.currentPageIndex++;
},
//上一页
previousPage() {
if (this.currentPageIndex > 1) {
this.currentPageIndex--;
}
},
changePage(pageIndex) {
this.currentPageIndex == pageIndex;
},
//更新总页数
updatePageCount(newPageSize) {
this.pageCount = Math.floor(this.dataCount / newPageSize);
this.pageCount += this.dataCount % newPageSize == 0 ? 0 : 1;
},
//更新分页按钮数据
updatePageBtn(newPageIndex) {
this.pageBtn = [];
//初始化首页
this.pageBtn.push({ text: "1", index: 1 });
//总页数小于8时显示全部页码
if (this.pageCount <= 8) {
for (let i = 2; i < this.pageCount; i++) {
this.pageBtn.push({ text: `${i}`, index: i });
}
} else {
//当前页左侧显示2格页码当显示最小页码距离首页中间间隔大于1时隐藏间隔页面
if (newPageIndex - 2 > 3) {
this.pageBtn.push({ text: "...", index: newPageIndex });
}
//渲染当前页码左右各两格页码
const numleft = newPageIndex - 2 < 2 ? newPageIndex - 2 : 2;
const numright =
newPageIndex + 2 < this.pageCount ? 2 : this.pageCount - newPageIndex - 1;
for (let i = newPageIndex - numleft; i <= newPageIndex + numright; i++) {
this.pageBtn.push({ text: `${i}`, index: i });
}
//当前页右侧显示2格页码当显示最大页码距离尾页中间间隔大于1时隐藏间隔页面
if (newPageIndex + 2 < this.pageCount - 2) {
this.pageBtn.push({ text: "...", index: newPageIndex });
}
}
//渲染尾页
this.pageBtn.push({ text: `${this.pageCount}`, index: this.pageCount });
},
formatDateTime(str) {
if (!str) return "-";
const date = new Date(str);
if (isNaN(date)) return "-"; // 防止 Safari 报 Invalid Date
const pad = (n) => n.toString().padStart(2, "0");
const Y = date.getFullYear();
const M = pad(date.getMonth() + 1);
const D = pad(date.getDate());
const h = pad(date.getHours());
const m = pad(date.getMinutes());
const s = pad(date.getSeconds());
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
},
modify(id) {
this.$emit("dataModify", id);
},
deleteData(id) {
this.$emit("dataDelete", id);
},
handleSearch() {},
handleAddDate() {
this.$emit("dateAdd", {});
},
handleBatchDelete() {},
},
watch: {
//监听分页大小变化
pageSize: {
handler(newVal) {
this.updatePageCount(newVal);
//单页显示数据量改变时重置当前索引,防止数据以及控件异常
this.currentPageIndex = 1;
this.$emit("pageChanged", { pageIndex: this.currentPageIndex, pageSize: newVal });
this.updatePageBtn(this.currentPageIndex);
},
immediate: true,
},
//监听页索引,用于切换数据
currentPageIndex: {
handler(newVal) {
this.$emit("pageChanged", { pageIndex: newVal, pageSize: this.pageSize });
this.updatePageBtn(newVal);
},
immediate: true,
},
},
};
</script>
<style scoped>
/* 可自定义样式 */
.card-title {
font-size: 1.8rem;
font-weight: bold;
color: #333;
}
</style>