lanan-repair-app/pages-detail/detail/detail.vue

551 lines
13 KiB
Vue
Raw Permalink Normal View History

2025-10-14 20:13:29 +08:00
<template>
<view class="detail-container">
<view class="top">
2025-10-21 10:07:57 +08:00
<VNavigationBar title="详情页" background-color="rgba(0,0,0,0)" title-color="#1f2d3d"></VNavigationBar>
<!-- <view class="navbar">
2025-10-14 20:13:29 +08:00
<uni-icons type="left" color="#1f2d3d" size="22" @click="goBack" />
<text class="navbar-title">详情页</text>
2025-10-21 10:07:57 +08:00
</view> -->
2025-10-14 20:13:29 +08:00
<view class="header">
<!-- 选项卡 -->
<view class="tabs">
<view class="tab" :class="{'active':currentTab == index}" v-for="(item,index) in tapList"
:key="index" @click="switchTab(index)">
<view class="">{{item.label || "无"}}</view>
<view class="tap_img" style="display: block;" v-show="currentTab == index">
<image src="/static/images/icon_tap.png" mode="widthFix" class="tap_icon"></image>
</view>
</view>
</view>
<view class="">
<uni-datetime-picker v-model="queryParams.dateRange" type="daterange" @change="loadData()"
rangeSeparator="至" class="">
<view class="cont_time">
<view class="cont_size">{{queryParams.dateRange[0]}}</view>
<view class="bule_size"></view>
<view class="cont_size">{{queryParams.dateRange[1]}}</view>
<view class=""> <u-icon name="arrow-down-fill" color="#0357FF" size="12"></u-icon></view>
</view>
</uni-datetime-picker>
</view>
</view>
</view>
<view class="content">
<view class="section-list">
<view class="section-card" v-for="(sKey, idx) in statsData" :key="idx">
<view class="section-header" @click="goList(sKey.selectType)">
<view class="section-header-left">
<view class="section-tag">{{ sKey.name }}</view>
<view class="section-total">{{ sKey.total }}台次</view>
</view>
<view class="section-header-right">
<image src="../images/small.png" mode="widthFix" class="tap_icon" />
</view>
</view>
<view class="section-body">
<view class="kv" v-for="(item, i) in sKey.children" :key="i"
@click="goList(sKey.selectType,item.repairType)">
<view class="kv-number">{{ item.count }}</view>
<view class="kv-label">{{ dictData[item.repairType] }}</view>
</view>
</view>
</view>
</view>
<view class="finance" v-if="checkPermi(['repair:tick:profit'])">
2025-10-20 11:41:40 +08:00
<view class="fin-card receivable" @click="goListByPayStatus('receivable')">
2025-10-14 20:13:29 +08:00
<view class="fin-top">
<text class="fin-title">应收款</text>
<uni-icons :type="financeVisibility.receivable ? 'eye' : 'eye-slash'" color="#7aa6ff" size="20"
2025-10-20 11:41:40 +08:00
@click.native.stop="toggleFinance('receivable')" />
2025-10-14 20:13:29 +08:00
</view>
<!-- 已收款金额 -->
<text
class="fin-amount">{{ financeVisibility.receivable ? formatCurrency(otherInfo.receivableAmount || 0) : '*****' }}</text>
<view class="fin-bottom">
<view class="fin-count">{{otherInfo.receivableCount}}台次</view>
<image src="../images/money_one.png" mode="widthFix" class="tap_icon"></image>
</view>
</view>
2025-10-20 11:41:40 +08:00
<view class="fin-card collected" @click="goListByPayStatus('receivedAmount')">
2025-10-14 20:13:29 +08:00
<view class="fin-top">
<text class="fin-title">已收款</text>
<uni-icons :type="financeVisibility.collected ? 'eye' : 'eye-slash'" color="#a896ff" size="20"
2025-10-20 11:41:40 +08:00
@click.native.stop="toggleFinance('collected')" />
2025-10-14 20:13:29 +08:00
</view>
<text
class="fin-amount">{{ financeVisibility.collected ? formatCurrency(otherInfo.receivedAmount) : '*****' }}</text>
<view class="fin-bottom">
<view class="fin-count">{{otherInfo.receivedCount}}台次</view>
<image src="../images/money_two.png" mode="widthFix" class="tap_icon"></image>
</view>
</view>
2025-10-20 11:41:40 +08:00
<view class="fin-card pending" @click="goListByPayStatus('pendingAmount')">
2025-10-14 20:13:29 +08:00
<view class="fin-top">
<text class="fin-title">待收款</text>
<uni-icons :type="financeVisibility.pending ? 'eye' : 'eye-slash'" color="#ffcf7a" size="20"
2025-10-20 11:41:40 +08:00
@click.native.stop="toggleFinance('pending')" />
2025-10-14 20:13:29 +08:00
</view>
<text
class="fin-amount">{{ financeVisibility.pending ? formatCurrency(otherInfo.pendingAmount) : '*****' }}</text>
<view class="fin-bottom">
<view class="fin-count">{{otherInfo.pendingCount}}台次</view>
<image src="../images/money_three.png" mode="widthFix" class="tap_icon"></image>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import request from '@/utils/request';
2025-10-21 10:07:57 +08:00
import VNavigationBar from '@/components/VNavigationBar.vue'
2025-10-14 20:13:29 +08:00
import {
formatCurrency,
getDictByCode,
getDateRange
} from '@/utils/utils';
import {
checkPermi,
checkRole
} from "@/utils/permission"; // 权限判断函数
export default {
2025-10-21 10:07:57 +08:00
components: {
VNavigationBar,
},
2025-10-14 20:13:29 +08:00
name: 'DetailPage',
data() {
return {
currentTab: 0,
queryParams: {
dateRange: [],
},
tapList: [{
label: "本日",
value: "day",
},
{
label: "本月",
value: "month",
},
{
label: "全部",
value: "all",
},
],
statsData: {},
financeVisibility: {
receivable: false,
collected: false,
pending: false
},
dictData: undefined,
otherInfo: {}
};
},
async mounted() {
await this.setCurrentMonthRange()
this.getDict()
this.loadFinanceVisibility() // 从缓存加载显示设置
this.loadData();
},
methods: {
checkPermi,
checkRole,
async switchTab(tab) {
if (this.currentTab !== tab) {
this.currentTab = tab;
await this.slectRange(tab)
}
},
slectRange(index) {
this.selected = index;
console.log(index, this.selected);
const {
value
} = this.tapList[index];
console.log(value);
this.queryParams.dateRange = getDateRange(value);
},
showDatePicker() {
console.log('show date picker');
},
// 从本地缓存加载显示设置
loadFinanceVisibility() {
try {
const savedVisibility = uni.getStorageSync('financeVisibility');
if (savedVisibility) {
this.financeVisibility = savedVisibility;
}
} catch (e) {
console.error('Failed to load finance visibility from storage:', e);
}
},
// 切换财务信息显示/隐藏并保存到缓存
toggleFinance(key) {
this.financeVisibility[key] = !this.financeVisibility[key];
// 保存到本地缓存
try {
uni.setStorageSync('financeVisibility', this.financeVisibility);
} catch (e) {
console.error('Failed to save finance visibility to storage:', e);
}
},
async getDict() {
const list = await getDictByCode('repair_type')
this.dictData = list?.reduce((map, item) => {
map[item.value] = item.label
return map
}, {}) ?? {} // 如果 list 为空或 reduce 返回 undefined则使用空对象
console.log('dict', this.dictData)
},
/**
* 列表
*/
goList(selectType, repairType) {
if (repairType) {
uni.navigateTo({
url: `/pages-business/statistics/statistics?selectType=${selectType}&repairType=${repairType}`
})
} else {
uni.navigateTo({
url: `/pages-business/statistics/statistics?selectType=${selectType}`
})
}
},
goListByPayStatus(payStatus) {
uni.navigateTo({
url: `/pages-business/statistics/statistics?payStatus=${payStatus}`
})
},
loadData() {
request({
url: '/admin-api/repair/tickets/getBossNumStatistics',
method: 'get',
params: this.queryParams
}).then(res => {
if (res.code === 200 && res.data) {
this.statsData = res.data.stats;
this.otherInfo = res.data
}
}).catch(error => {
console.error('Failed to load stats data:', error);
});
},
formatCurrency(amount) {
return formatCurrency(amount);
},
// toggleFinance(key) {
// this.financeVisibility[key] = !this.financeVisibility[key];
// },
goBack() {
uni.navigateBack();
},
setCurrentMonthRange() {
// 直接使用 Date 对象
const now = new Date();
// 创建月初的 Date 对象
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
this.queryParams.dateRange = [
this.formatDate(now), // 月初
this.formatDate(now), // 今天
];
},
formatDate(d) {
// 添加类型检查,确保 d 是 Date 对象
if (!(d instanceof Date)) {
console.error("formatDate() 期望接收 Date 对象,但收到:", d);
d = new Date(d); // 尝试转换为 Date 对象
if (isNaN(d.getTime())) {
// 检查是否有效日期
d = new Date(); // 如果转换失败,使用当前日期
}
}
return `${d.getFullYear()}-${this.pad(d.getMonth() + 1)}-${this.pad(
d.getDate()
)}`;
},
// 补零1 → 01
pad(n) {
return n.toString().padStart(2, "0");
},
},
watch: {
currentTab() {
this.loadData();
}
}
};
</script>
<style scoped>
.detail-container {
/* background: #f2f6ff; */
min-height: 100vh;
padding-bottom: 24rpx;
height: 100%;
flex-direction: column;
overflow: hidden;
display: flex;
}
.navbar {
height: 88rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
}
.top {
background-image: url('/static/images/table_header.png');
padding-top: var(--status-bar-height); //给组件加个上边距
2025-10-21 10:07:57 +08:00
/* z-index: 1; */
2025-10-14 20:13:29 +08:00
}
.navbar-title {
flex: 1;
text-align: center;
color: #1f2d3d;
font-weight: 600;
font-size: 32rpx;
margin-right: 44rpx;
}
.header {
margin: 0 24rpx 24rpx;
/* background: linear-gradient(180deg, #e8f1ff 0%, #f5f8ff 100%); */
border-radius: 16rpx;
padding: 24rpx;
display: flex;
justify-content: space-between;
/* box-shadow: 0 8rpx 24rpx rgba(88, 131, 255, 0.12); */
}
.tabs {
display: flex;
gap: 24rpx;
margin-bottom: 20rpx;
}
.tab {
font-size: 28rpx;
color: #7f8fa4;
position: relative;
padding-bottom: 8rpx;
}
.tab.active {
color: #2a6cff;
font-weight: 600;
}
.tab.active:after {
/* content: '';
position: absolute;
left: 0;
bottom: 0;
width: 48rpx;
height: 6rpx;
background: #2a6cff;
border-radius: 6rpx; */
}
.date-range {
background: #ffffff;
border-radius: 12rpx;
padding: 18rpx 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
color: #5f6b7a;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
}
.date-text {
font-size: 26rpx;
}
.section-list {
padding: 0 24rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.section-card {
background: #ffffff;
border-radius: 16rpx;
box-shadow: 0 8rpx 28rpx rgba(31, 45, 61, 0.06);
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 8rpx;
background: linear-gradient(180deg, #EDF8FF 0%, #FBFEFF 100%);
}
.section-header-left {
display: flex;
}
.section-header-right {
image {
height: 30rpx;
width: 30rpx;
}
}
.section-tag {
background-image: url('../images/list_title.png');
color: #2a6cff;
font-size: 26rpx;
padding: 10rpx 18rpx;
font-weight: 600;
}
.section-total {
padding: 10rpx 18rpx;
font-size: 28rpx;
color: #144B7E;
font-weight: 500;
}
.section-body {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
padding: 8rpx;
}
.kv {
border-radius: 12rpx;
padding: 18rpx 0;
text-align: center;
}
.kv-number {
color: #1f2d3d;
font-weight: 700;
font-size: 30rpx;
margin-bottom: 4rpx;
}
.kv-label {
color: #7f8fa4;
font-size: 22rpx;
}
.finance {
padding: 16rpx 24rpx 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.fin-card {
border-radius: 16rpx;
padding: 18rpx;
color: #1f2d3d;
display: flex;
flex-direction: column;
gap: 10rpx;
box-shadow: 0 10rpx 28rpx rgba(0, 0, 0, 0.06);
}
.fin-card.receivable {
background: linear-gradient(180deg, #eaf3ff, #f3f8ff);
}
.fin-card.collected {
background: linear-gradient(180deg, #f1ecff, #f7f4ff);
}
.fin-card.pending {
background: linear-gradient(180deg, #fff3df, #fff8ea);
}
.tap_img {
height: 3px;
text-align: center;
margin: 0 auto;
image {
width: 34rpx;
height: 12rpx;
}
}
.fin-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.fin-title {
color: #7f8fa4;
font-size: 22rpx;
}
.fin-amount {
font-size: 32rpx;
font-weight: 700;
letter-spacing: 1rpx;
}
.fin-bottom {
display: flex;
align-items: center;
justify-content: space-between;
color: #7f8fa4;
font-size: 22rpx;
image {
height: 30rpx;
width: 30rpx;
}
}
.content {
overflow-y: scroll;
}
.cont_time {
background: #F1F4F7;
border-radius: 36rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 30rpx;
width: 386rpx;
height: 56rpx;
background: #FFFFFF;
border-radius: 36rpx;
}
.cont_size {
font-weight: 400;
font-size: 24rpx;
color: #686C7A;
}
.tap_icon {
width: 40rpx;
height: 40rpx;
}
</style>