lanan-repair-app/pages-detail/detail/detail.vue
2025-10-14 20:13:29 +08:00

555 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>
<view class="detail-container">
<view class="top">
<view class="navbar">
<uni-icons type="left" color="#1f2d3d" size="22" @click="goBack" />
<text class="navbar-title">详情页</text>
</view>
<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'])">
<view class="fin-card receivable" @click.self="goListByPayStatus('receivable')">
<view class="fin-top">
<text class="fin-title">应收款</text>
<uni-icons :type="financeVisibility.receivable ? 'eye' : 'eye-slash'" color="#7aa6ff" size="20"
@click="toggleFinance('receivable')" />
</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>
<view class="fin-card collected" @click.self="goListByPayStatus('receivedAmount')">
<view class="fin-top">
<text class="fin-title">已收款</text>
<uni-icons :type="financeVisibility.collected ? 'eye' : 'eye-slash'" color="#a896ff" size="20"
@click="toggleFinance('collected')" />
</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>
<view class="fin-card pending" @click.self="goListByPayStatus('pendingAmount')">
<view class="fin-top">
<text class="fin-title">待收款</text>
<uni-icons :type="financeVisibility.pending ? 'eye' : 'eye-slash'" color="#ffcf7a" size="20"
@click="toggleFinance('pending')" />
</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';
import {
formatCurrency,
getDictByCode,
getDateRange
} from '@/utils/utils';
import {
checkPermi,
checkRole
} from "@/utils/permission"; // 权限判断函数
export default {
name: 'DetailPage',
data() {
return {
currentTab: 0,
queryParams: {
dateRange: [],
},
sectionOrder: ['orders', 'repairing', 'completed', 'settled', 'unsettled', 'delivered', 'inFactory'],
sectionTitle: {
orders: '订单(进厂)数',
repairing: '维修中',
completed: '已竣工',
settled: '竣工已结算',
unsettled: '竣工未结算',
delivered: '已交车',
inFactory: '在厂数'
},
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); //给组件加个上边距
}
.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>