You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

748 lines
18 KiB
Vue

<template>
<view class="page-container tn-gradient-bg__cool-5" @click="hideUserMenu">
<cheader :show-time="false"></cheader>
<div class="index-body">
<view class="duty-rail">
<text class="duty-clock">{{ clockText }}</text>
<text class="duty-date">{{ dateText }}</text>
<view class="duty-block">
<text class="duty-kicker">当班人员</text>
<text class="duty-value">{{ userInfo.realName || "未登录" }}</text>
</view>
<view class="duty-todos">
<text class="duty-kicker">待办事项</text>
<scroll-view class="duty-todo-list" scroll-y>
<text v-if="todos.length === 0" class="duty-todo-empty">暂无待办</text>
<view
v-for="item in todos"
:key="item.id"
class="duty-todo-item"
:class="{ 'duty-todo-done': item.done }"
>
<text class="duty-todo-text">{{ item.text }}</text>
</view>
</scroll-view>
</view>
<view class="duty-tip" @click="goToDutyTodos">
<text class="duty-tip-text">编辑待办事项</text>
<uni-icons type="right" size="16" color="#fff"></uni-icons>
</view>
</view>
<div class="main-pane">
<div class="body-title">
<view class="title-left">
<span class="title-mian">导税工作台</span>
<text class="app-version">v{{ appVersion }}</text>
</view>
<view
class="user-mini"
:class="{ 'user-mini-open': showUserMenu }"
@click.stop="toggleUserMenu"
>
<image
v-if="userInfo.image"
:src="userInfo.image"
class="user-mini-avatar"
></image>
<view v-else class="user-mini-avatar user-mini-fallback">
<uni-icons type="person-filled" size="16" color="#1e3a8a"></uni-icons>
</view>
<text class="user-mini-name">{{ userInfo.realName || "未登录" }}</text>
<view
class="user-mini-caret"
:class="{ 'user-mini-caret-open': showUserMenu }"
>
<uni-icons type="bottom" size="12" color="#fff"></uni-icons>
</view>
<view v-if="showUserMenu" class="user-menu" @click.stop>
<view class="user-menu-item" @click.stop="goToAppLog">
<uni-icons type="list" size="16" color="#1e3a8a"></uni-icons>
<text>查看日志</text>
</view>
<view class="user-menu-sep"></view>
<view
class="user-menu-item user-menu-item-danger"
@click.stop="loginOutAction"
>
<uni-icons type="locked" size="16" color="#dc2626"></uni-icons>
<text>退出登录</text>
</view>
</view>
</view>
</div>
<div class="btn-content">
<div class="btn-row1">
<div class="home-tile row1-item1 col-center" @click="goToQueue()">
<uni-icons type="compose" size="60" color="#fff"></uni-icons>
<p class="btn-p">预检取号</p>
</div>
<div class="home-tile row1-item2 col-center" @click="goToTicket()">
<uni-icons type="list" size="60" color="#fff"></uni-icons>
<p class="btn-p">票号管理</p>
</div>
<div class="home-tile row1-item3 col-center" @click="goToManager()">
<uni-icons type="staff-filled" size="60" color="#fff"></uni-icons>
<p class="btn-p">网格员</p>
</div>
</div>
<div class="btn-row2">
<div class="row2-item1">
<div class="home-tile row2-item1-top row-center" @click="goToPushMessage()">
<uni-icons type="info" size="60" color="#fff"></uni-icons>
<p class="btn-p">信息推送</p>
</div>
<div class="home-tile row2-item1-bottom row-center">
<uni-icons type="compose" size="60" color="#fff"></uni-icons>
<p class="btn-p">预留功能</p>
</div>
</div>
<div class="row2-item2">
<div
class="home-tile row2-item2-left col-center"
@click="goToSignPriority()"
>
<uni-icons
type="home-filled"
size="60"
color="#fff"
></uni-icons>
<p class="btn-p">回签优先</p>
</div>
<div class="home-tile row2-item2-center col-center">
<uni-icons
type="auth-filled"
size="60"
color="#fff"
></uni-icons>
<p class="btn-p">预留功能</p>
</div>
<div class="home-tile row2-item2-right col-center">
<uni-icons
type="settings-filled"
size="60"
color="#fff"
></uni-icons>
<p class="btn-p">预留功能</p>
</div>
</div>
</div>
</div>
</div>
</div>
</view>
</template>
<script setup>
import { getAuthProfile } from "@/api/index.js";
import cheader from "@/components/header.vue";
import { appLog, flushLogs } from "@/utils/appLog.js";
import { loadDutyTodos } from "@/utils/dutyTodos.js";
import { waitSessionReady } from "@/utils/request.js";
import { getAppVersion } from "@/utils/appVersion.js";
import {
onBackPress,
onHide,
onLoad,
onShow,
onUnload,
} from "@dcloudio/uni-app";
import { computed, ref } from "vue";
const WEEK_DAYS = [
"星期日",
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
];
const appVersion = getAppVersion();
const userInfo = ref({
realName: "",
id: "",
image: "",
});
const showUserMenu = ref(false);
const toggleUserMenu = () => {
showUserMenu.value = !showUserMenu.value;
};
const hideUserMenu = () => {
showUserMenu.value = false;
};
const now = ref(new Date());
const todos = ref([]);
let clockTimer = null;
const pad2 = (value) => String(value).padStart(2, "0");
const clockText = computed(() => {
const date = now.value;
return `${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
});
const dateText = computed(() => {
const date = now.value;
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${WEEK_DAYS[date.getDay()]}`;
});
onBackPress(() => {
return true;
});
onLoad(() => {
try {
const storageData = uni.getStorageSync("userInfo");
if (storageData) {
const parsedData = JSON.parse(storageData);
userInfo.value = {
realName: parsedData.realName || "",
id: parsedData.id || "",
image: parsedData.image || "/static/header_no.png",
};
console.log("用户信息:", userInfo.value);
}
} catch (error) {
console.error("获取用户信息失败:", error);
}
});
const loadHallNameFromProfile = async () => {
try {
const profile = await getAuthProfile();
const swjgMC = profile?.swjgMC || profile?.swjgMc || "";
console.log("[auth/profile@index] response:", profile);
console.log("[auth/profile@index] parsed swjgMC:", swjgMC);
if (swjgMC) {
uni.setStorageSync("swjgMC", swjgMC);
uni.$emit("hall-name-updated");
console.log("[auth/profile@index] swjgMC saved");
} else {
console.log("[auth/profile@index] swjgMC is empty, skip save");
}
} catch (error) {
console.log("获取机构名称失败", error);
}
};
const startClockTimer = () => {
stopClockTimer();
now.value = new Date();
clockTimer = setInterval(() => {
now.value = new Date();
}, 1000);
};
const stopClockTimer = () => {
if (clockTimer) {
clearInterval(clockTimer);
clockTimer = null;
}
};
onShow(async () => {
await waitSessionReady();
loadHallNameFromProfile();
todos.value = loadDutyTodos();
startClockTimer();
});
onHide(() => {
stopClockTimer();
});
onUnload(() => {
stopClockTimer();
});
const goToGuidance = () => {
uni.navigateTo({
url: "/pages/mod/guidance?isFromTicket=false",
});
};
const goToDailyEntry = () => {
uni.navigateTo({
url: "/pages/table/dailyEntry",
});
};
const goToAppointment = () => {
uni.navigateTo({
url: "/pages/table/appointment",
});
};
const goToTicket = () => {
uni.navigateTo({
url: "/pages/table/ticket",
});
};
const goToMultiInOne = () => {
uni.navigateTo({
url: "/pages/mod/multiInOne",
});
};
const goToPushMessage = () => {
uni.navigateTo({
url: "/pages/mod/pushMessage",
});
};
const goToQueue = () => {
uni.navigateTo({
url: "/pages/queue/index",
});
};
const goToManager = () => {
uni.navigateTo({
url: "/pages/mod/manager",
});
};
const goToHallManagment = () => {
uni.navigateTo({
url: "/pages/mod/hallManagment",
});
};
const goToSignPriority = () => {
uni.navigateTo({
url: "/pages/mod/sign-priority",
});
};
const goToDutyTodos = () => {
uni.navigateTo({
url: "/pages/mod/duty-todos",
});
};
const goToAppLog = () => {
showUserMenu.value = false;
uni.navigateTo({
url: "/pages/mod/appLog",
});
};
const loginOutAction = () => {
uni.showModal({
title: "登出",
content: "是否退出登录,并返回到登录界面?",
success: (res) => {
if (res.confirm) {
appLog('info', '[index] 用户退出登录')
flushLogs()
stopClockTimer();
uni.removeStorageSync("token");
uni.removeStorageSync("refresh_token");
uni.removeStorageSync("userInfo");
uni.reLaunch({
url: "/pages/login/login",
});
}
},
});
};
</script>
<style lang="scss">
.page-container {
height: var(--pad-h);
overflow: hidden;
display: flex;
flex-direction: column;
}
.index-body {
flex: 1;
min-height: 0;
width: 100%;
padding: 12px 5vw 16px;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: stretch;
overflow: hidden;
--module-gap: 10px;
.duty-rail {
width: 20vw;
flex: 0 0 20vw;
min-height: 0;
margin-right: 16px;
padding: 20px 16px 14px;
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 16px;
background: rgba(15, 23, 42, 0.22);
border: 1px solid rgba(255, 255, 255, 0.22);
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
backdrop-filter: blur(14px);
}
.duty-clock {
font-size: 36px;
font-weight: 700;
color: #fff;
letter-spacing: 1px;
line-height: 1;
font-variant-numeric: tabular-nums;
font-family: "DIN Alternate", "Roboto Mono", "Courier New", monospace;
}
.duty-date {
margin-top: 8px;
font-size: 13px;
color: rgba(255, 255, 255, 0.78);
letter-spacing: 0.5px;
}
.duty-block {
margin-top: 16px;
display: flex;
flex-direction: column;
}
.duty-kicker {
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 2px;
}
.duty-value {
margin-top: 6px;
font-size: 15px;
font-weight: 600;
color: #fff;
line-height: 1.4;
word-break: break-all;
}
.duty-todos {
flex: 1;
min-height: 0;
margin-top: 16px;
display: flex;
flex-direction: column;
}
.duty-todo-list {
flex: 1;
min-height: 0;
margin-top: 8px;
}
.duty-todo-empty {
font-size: 13px;
color: rgba(255, 255, 255, 0.45);
}
.duty-todo-item {
margin-bottom: 8px;
padding-left: 8px;
border-left: 2px solid rgba(255, 255, 255, 0.35);
}
.duty-todo-done {
opacity: 0.55;
}
.duty-todo-text {
font-size: 13px;
line-height: 1.4;
color: #fff;
word-break: break-all;
}
.duty-todo-done .duty-todo-text {
text-decoration: line-through;
}
.duty-tip {
margin-top: 12px;
padding: 10px 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.16);
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-shrink: 0;
}
.duty-tip-text {
font-size: 14px;
font-weight: 700;
color: #fff;
}
.main-pane {
flex: 1;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
overflow: visible;
}
.body-title {
min-height: 0;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
position: relative;
z-index: 30;
overflow: visible;
.title-left {
display: flex;
align-items: baseline;
gap: 12px;
}
.title-mian {
color: #fff;
font-size: 32px;
font-weight: 700;
letter-spacing: 1px;
text-shadow: 0 6px 18px rgba(15, 23, 42, 0.25);
}
.app-version {
font-size: 14px;
color: rgba(255, 255, 255, 0.78);
font-weight: 500;
}
.user-mini {
position: relative;
display: flex;
align-items: center;
background-color: rgba(255, 255, 255, 0.18);
border: 1px solid rgba(255, 255, 255, 0.32);
border-radius: 22px;
padding: 4px 10px 4px 4px;
backdrop-filter: blur(8px);
flex-shrink: 0;
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.12);
.user-mini-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.85);
margin-right: 8px;
object-fit: cover;
background: #e8eef7;
flex-shrink: 0;
}
.user-mini-fallback {
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-color: #fff;
}
.user-mini-name {
font-size: 14px;
color: #fff;
font-weight: 600;
max-width: 120px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1.2;
}
.user-mini-caret {
margin-left: 6px;
display: flex;
align-items: center;
opacity: 0.9;
}
.user-mini-caret-open {
transform: rotate(180deg);
}
.user-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
background-color: #fff;
border-radius: 12px;
border: 1px solid #d7e2ee;
box-shadow: 0 14px 32px rgba(15, 23, 42, 0.18);
overflow: hidden;
min-width: 156px;
padding: 6px;
z-index: 40;
.user-menu-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
color: #1e3a8a;
text-align: left;
}
.user-menu-item-danger {
color: #dc2626;
}
.user-menu-sep {
height: 1px;
margin: 4px 8px;
background: #e2e8f0;
}
.user-menu-item:active {
background-color: #eef3f9;
}
}
}
.user-mini-open {
background-color: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
}
}
.btn-content {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: var(--module-gap);
overflow: hidden;
.home-tile {
min-height: 0;
border-radius: $card-border-radius;
overflow: hidden;
box-shadow: 0 12px 24px rgba(15, 23, 42, 0.18);
}
.btn-row1 {
flex: 1;
min-height: 0;
width: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--module-gap);
.row1-item1 {
background-color: #2563eb;
background-image: linear-gradient(to bottom right, #93c5fd 0%, #2563eb 48%, #1e3a8a 100%);
}
.row1-item2 {
background-color: #1d4ed8;
background-image: linear-gradient(to bottom right, #7dd3fc 0%, #3b82f6 48%, #1e40af 100%);
}
.row1-item3 {
background-color: #0284c7;
background-image: linear-gradient(to bottom right, #67e8f9 0%, #0ea5e9 48%, #0369a1 100%);
}
}
.btn-row2 {
flex: 1.6;
min-height: 0;
width: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--module-gap);
.row2-item1 {
min-height: 0;
display: flex;
flex-direction: column;
gap: var(--module-gap);
.row2-item1-top {
flex: 1;
min-height: 0;
background-color: #4f46e5;
background-image: linear-gradient(to bottom right, #a5b4fc 0%, #6366f1 48%, #3730a3 100%);
}
.row2-item1-bottom {
flex: 1;
min-height: 0;
background-color: #64748b;
background-image: linear-gradient(to bottom right, #cbd5e1 0%, #94a3b8 48%, #475569 100%);
}
}
.row2-item2 {
grid-column: span 2;
min-height: 0;
display: flex;
flex-direction: row;
gap: var(--module-gap);
.row2-item2-left {
flex: 1;
background-color: #0e7490;
background-image: linear-gradient(to bottom right, #67e8f9 0%, #06b6d4 48%, #155e75 100%);
}
.row2-item2-center {
flex: 1;
background-color: #64748b;
background-image: linear-gradient(to bottom right, #cbd5e1 0%, #94a3b8 48%, #475569 100%);
}
.row2-item2-right {
flex: 1;
background-color: #64748b;
background-image: linear-gradient(to bottom right, #e2e8f0 0%, #94a3b8 48%, #334155 100%);
}
}
}
}
}
.col-center {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.row-center {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.btn-p {
font-size: 22px;
font-weight: 800;
color: #fff;
margin: 8px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
</style>