feat: 添加签到信息页面路由和按钮,提升团队管理功能
This commit is contained in:
@@ -119,6 +119,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import("@/views/team/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/team_check_in",
|
||||
component: () => import("@/views/team/check-in.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/asset_center",
|
||||
component: () => import("@/views/asset_center/index.vue"),
|
||||
|
||||
482
src/views/team/check-in.vue
Normal file
482
src/views/team/check-in.vue
Normal file
@@ -0,0 +1,482 @@
|
||||
<script lang='ts' setup>
|
||||
import dayjs from "dayjs";
|
||||
import { calendarOutline, peopleOutline } from "ionicons/icons";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const [depth1Query] = useResetRef({
|
||||
depth: 1,
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [depth2Query] = useResetRef({
|
||||
depth: 2,
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [depth3Query] = useResetRef({
|
||||
depth: 3,
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// 今日签到统计
|
||||
const { data: checkInStats } = safeClient(client.api.team["check-ins"].count.get());
|
||||
const { data: checkInStats1 } = safeClient(client.api.team["check-ins"].count.get({ query: { depth: 1 } }));
|
||||
const { data: checkInStats2 } = safeClient(client.api.team["check-ins"].count.get({ query: { depth: 2 } }));
|
||||
const { data: checkInStats3 } = safeClient(client.api.team["check-ins"].count.get({ query: { depth: 3 } }));
|
||||
const { data: depth1, execute: executeDepth1 } = safeClient(() => client.api.team["check-ins"].get({ query: { ...depth1Query.value } }));
|
||||
const { data: depth2, execute: executeDepth2 } = safeClient(() => client.api.team["check-ins"].get({ query: { ...depth2Query.value } }));
|
||||
const { data: depth3, execute: executeDepth3 } = safeClient(() => client.api.team["check-ins"].get({ query: { ...depth3Query.value } }));
|
||||
|
||||
// 当前选中的级别
|
||||
const selectedLevel = ref<1 | 2 | 3>(1);
|
||||
|
||||
// 获取当前级别对应的数据源和执行函数
|
||||
const currentData = computed(() => {
|
||||
switch (selectedLevel.value) {
|
||||
case 1:
|
||||
return depth1;
|
||||
case 2:
|
||||
return depth2;
|
||||
case 3:
|
||||
return depth3;
|
||||
default:
|
||||
return depth1;
|
||||
}
|
||||
});
|
||||
|
||||
const currentExecute = computed(() => {
|
||||
switch (selectedLevel.value) {
|
||||
case 1:
|
||||
return executeDepth1;
|
||||
case 2:
|
||||
return executeDepth2;
|
||||
case 3:
|
||||
return executeDepth3;
|
||||
default:
|
||||
return executeDepth1;
|
||||
}
|
||||
});
|
||||
|
||||
const currentQuery = computed(() => {
|
||||
switch (selectedLevel.value) {
|
||||
case 1:
|
||||
return depth1Query;
|
||||
case 2:
|
||||
return depth2Query;
|
||||
case 3:
|
||||
return depth3Query;
|
||||
default:
|
||||
return depth1Query;
|
||||
}
|
||||
});
|
||||
|
||||
// 当前级别的签到人员列表
|
||||
const filteredRecords = computed(() => {
|
||||
return currentData.value.value?.data || [];
|
||||
});
|
||||
|
||||
// 全部签到人数
|
||||
const totalRecords = computed(() => {
|
||||
return currentData.value.value?.pagination.total || 0;
|
||||
});
|
||||
|
||||
// 总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalRecords.value / currentQuery.value.value.pageSize);
|
||||
});
|
||||
|
||||
// 当前页码
|
||||
const currentPage = computed(() => {
|
||||
return currentQuery.value.value.pageIndex;
|
||||
});
|
||||
|
||||
function handleLevelChange(event: CustomEvent) {
|
||||
selectedLevel.value = event.detail.value;
|
||||
// 切换级别时重置到第一页
|
||||
currentQuery.value.value.pageIndex = 1;
|
||||
currentExecute.value();
|
||||
}
|
||||
|
||||
async function goToPage(page: number) {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
currentQuery.value.value.pageIndex = page;
|
||||
await currentExecute.value();
|
||||
}
|
||||
}
|
||||
|
||||
function prevPage() {
|
||||
goToPage(currentPage.value - 1);
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
goToPage(currentPage.value + 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar class="ion-toolbar">
|
||||
<ion-buttons slot="start">
|
||||
<back-button />
|
||||
</ion-buttons>
|
||||
<ion-title>签到信息</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<!-- 今日签到统计 -->
|
||||
<div class="stats-section">
|
||||
<div class="section-title">
|
||||
<ion-icon :icon="calendarOutline" class="title-icon" />
|
||||
<span>今日签到</span>
|
||||
</div>
|
||||
<div class="check-in-card">
|
||||
<div class="check-in-total">
|
||||
<div class="total-value">
|
||||
{{ checkInStats?.count || 0 }}
|
||||
</div>
|
||||
<div class="total-label">
|
||||
今日签到人数
|
||||
</div>
|
||||
</div>
|
||||
<div class="check-in-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">一级签到</span>
|
||||
<span class="info-value">{{ checkInStats1?.count || 0 }}人</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">二级签到</span>
|
||||
<span class="info-value">{{ checkInStats2?.count || 0 }}人</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">三级签到</span>
|
||||
<span class="info-value">{{ checkInStats3?.count || 0 }}人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 签到列表 -->
|
||||
<div class="check-in-list-section">
|
||||
<div class="section-title">
|
||||
<span>签到记录</span>
|
||||
</div>
|
||||
|
||||
<!-- 级别切换 -->
|
||||
<ion-segment :value="selectedLevel" class="level-segment" @ion-change="handleLevelChange">
|
||||
<ion-segment-button :value="1">
|
||||
<ion-label>一级直推</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button :value="2">
|
||||
<ion-label>二级直推</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button :value="3">
|
||||
<ion-label>三级直推</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<!-- 签到人员列表 -->
|
||||
<div class="record-list">
|
||||
<div
|
||||
v-for="record, index in filteredRecords"
|
||||
:key="index"
|
||||
class="record-card"
|
||||
>
|
||||
<div class="record-header">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="member-avatar">
|
||||
<img src="@/assets/images/avatar.jpg" alt="头像">
|
||||
</div>
|
||||
<div>
|
||||
<div class="member-name">
|
||||
{{ record.username }}
|
||||
</div>
|
||||
<div class="member-phone">
|
||||
{{ record.displayUsername }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="check-in-badge">
|
||||
已签到
|
||||
</div>
|
||||
</div>
|
||||
<div class="record-info">
|
||||
<div class="info-row">
|
||||
<span class="label">签到时间</span>
|
||||
<span class="value">{{ dayjs(record.checkInAt).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredRecords.length === 0" class="empty-list">
|
||||
<ion-icon :icon="peopleOutline" class="empty-icon" />
|
||||
<div class="empty-text">
|
||||
暂无签到记录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页控制 -->
|
||||
<div v-if="totalRecords > 0 && totalPages > 1" class="pagination-section">
|
||||
<div class="pagination-info">
|
||||
共 {{ totalRecords }} 条,第 {{ currentPage }}/{{ totalPages }} 页
|
||||
</div>
|
||||
<div class="pagination-buttons">
|
||||
<ion-button
|
||||
fill="outline"
|
||||
size="small"
|
||||
:disabled="currentPage === 1"
|
||||
@click="prevPage"
|
||||
>
|
||||
上一页
|
||||
</ion-button>
|
||||
<ion-button
|
||||
fill="outline"
|
||||
size="small"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="nextPage"
|
||||
>
|
||||
下一页
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped>
|
||||
.member-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.member-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 20px;
|
||||
color: #c41e3a;
|
||||
}
|
||||
|
||||
.check-in-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #fff7f0 0%, #ffe8e8 100%);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.check-in-total {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding-right: 20px;
|
||||
border-right: 1px solid #333;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: #c41e3a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.check-in-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.check-in-list-section {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.level-segment {
|
||||
margin-bottom: 16px;
|
||||
--background: #f5f5f5;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.level-segment ion-segment-button {
|
||||
--indicator-color: #c41e3a;
|
||||
--color: #666;
|
||||
--color-checked: white;
|
||||
min-height: 36px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
background: #fafafa;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.record-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.member-phone {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.check-in-badge {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #389e0d 100%);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.record-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
color: #ddd;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.pagination-section {
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pagination-buttons ion-button {
|
||||
--border-color: #c41e3a;
|
||||
--color: #c41e3a;
|
||||
--border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.pagination-buttons ion-button:not([disabled]):hover {
|
||||
--background: rgba(196, 30, 58, 0.05);
|
||||
}
|
||||
|
||||
.pagination-buttons ion-button[disabled] {
|
||||
--border-color: #ddd;
|
||||
--color: #999;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
@@ -118,6 +118,11 @@ function nextPage() {
|
||||
<back-button />
|
||||
</ion-buttons>
|
||||
<ion-title>我的团队</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button color="light" @click="$router.push('/team_check_in')">
|
||||
签到信息
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
@@ -363,6 +368,7 @@ function nextPage() {
|
||||
.total-value {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: #c41e3a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user