lanan-app/pages/rescue/businessManage1.vue

1262 lines
35 KiB
Vue
Raw Normal View History

2025-10-23 13:04:29 +08:00
<template>
<view class="container">
<!-- 顶部导航栏 -->
<view class="navbar">
<view class="navbar-left" @click="goBack">
<text class="icon-back"></text>
</view>
<view class="navbar-title">业务管理</view>
<view class="navbar-right"></view>
</view>
<!-- Tab切换 -->
<view v-if="!showVehicleDetail" class="tab-bar">
<view
v-for="(tab, index) in tabs"
:key="index"
:class="['tab-item', currentTab === index ? 'active' : '']"
@click="currentTab = index"
>
<text>{{ tab }}</text>
<view v-if="currentTab === index" class="tab-line"></view>
</view>
</view>
<!-- 流程人员管理 -->
<view v-if="currentTab === 0" class="personnel-page">
<view class="node-tabs">
<view
v-for="(node, index) in nodeList"
:key="index"
:class="['node-tab', selectedNode === index ? 'active' : '']"
@click="selectedNode = index"
>
{{ node.name }}
</view>
</view>
<scroll-view scroll-x scroll-y class="table-scroll" show-scrollbar>
<view class="data-table" :style="{minWidth: getTableWidth()}">
<view class="table-row header-row">
<view
v-for="(col, index) in currentColumns"
:key="index"
class="table-cell header-cell"
:style="{width: col.width, minWidth: col.width}"
>
{{ col.label }}
</view>
</view>
<view
v-for="(item, index) in currentTableData"
:key="index"
class="table-row"
>
<view
v-for="(col, colIndex) in currentColumns"
:key="colIndex"
:class="[
'table-cell',
col.prop === 'channels' ? 'wrap-cell' : '' /* 业务渠道列 */
]"
:style="{width: col.width}"
>
<!-- {{ item[col.prop] }} -->
{{ formatValue(col.prop, item[col.prop]) }}
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 业务统计列表 -->
<view v-if="currentTab === 1 && !showVehicleDetail" class="statistics-page">
<view class="filter-header mt20">
<text class="filter-header-title">筛选条件</text>
<view class="header-btns">
<text class="search-btn" @click="fetch">查询</text>
<text class="clear-btn" @click="resetFilters">清空</text>
</view>
</view>
<!-- 筛选条件 -->
<!-- ===== 美化后的筛选行 ===== -->
<!-- 1性质 / 司机 -->
<view class="filter-row">
<view class="filter-item">
<text class="filter-label">性质</text>
<picker
:value="getIndex(optionRescueCarOwn, filters.rescueCarOwn)"
:range="optionRescueCarOwn"
range-key="label"
@change="onSelect('rescueCarOwn', optionRescueCarOwn[$event.detail.value].value)"
class="chip"
>
<view>{{ getLabel(optionRescueCarOwn, filters.rescueCarOwn) }}</view>
</picker>
</view>
<view class="filter-item">
<text class="filter-label">司机</text>
<view class="chip">
<input
v-model.trim="filters.driverName"
class="chip-input"
placeholder="请输入司机姓名"
@input="onInputChange('driverName')"
/>
</view>
</view>
</view>
<!-- 3调度 / 司机 -->
<view class="filter-row">
<!-- <view class="filter-item">
<text class="filter-label">调度</text>
<view class="chip">
<input
v-model.trim="filters.secondDispatchName"
class="chip-input"
placeholder="请输入调度姓名"
@input="onInputChange('secondDispatchName')"
/>
</view>
</view> -->
<view class="filter-item">
<text class="filter-label">车辆</text>
<view class="chip">
<input
v-model.trim="filters.driverCarNum"
class="chip-input"
placeholder="请输入车牌号"
@input="onInputChange('driverCarNum')"
/>
</view>
</view>
</view>
<!-- 车辆列表表格 -->
<scroll-view scroll-x scroll-y class="table-scroll">
<view class="data-table" style="min-width: 1000rpx;">
<view class="table-row header-row">
<view
v-for="(col, index) in vehicleColumns"
:key="index"
class="table-cell header-cell"
:style="{width: col.width}"
>
{{ col.label }}
</view>
</view>
<view
v-for="(item, index) in vehicleList"
:key="index"
class="table-row clickable"
@click="showVehicleInfo(item)"
>
<view
v-for="(col, colIndex) in vehicleColumns"
:key="colIndex"
class="table-cell"
:style="{width: col.width}"
>
{{ item[col.prop] }}
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 车辆详情页 -->
<view v-if="currentTab === 1 && showVehicleDetail" class="detail-page">
<common-time-select
v-model="ranges"
class="time-picker"
@subsection-change="slectRangeInspectionCount">
</common-time-select>
<!-- 业务统计页面的筛选条件添加到红框区域下方 -->
<view class="filter-row">
<view class="filter-item">
<text class="filter-label">渠道</text>
<picker
:value="getIndex(optionChannel, filters.channel)"
:range="optionChannel"
range-key="label"
@change="onSelect('channel', optionChannel[$event.detail.value].value)"
class="chip"
>
<view>{{ getLabel(optionChannel, filters.channel) }}</view>
</picker>
</view>
<view class="filter-item">
<text class="filter-label">调度</text>
<view class="chip">
<input
v-model.trim="filters.secondDispatchName"
class="chip-input"
placeholder="请输入调度姓名"
@input="onInputChange('secondDispatchName')"
/>
</view>
</view>
</view>
<!-- 详情页标题 -->
<view class="detail-title-bar">
<text class="detail-title">{{ selectedVehicle.driverCarNum }} {{ detailMonth }}</text>
</view>
<!-- 车辆基本信息 -->
<view class="info-section">
<text class="section-title">车辆基本信息</text>
<view class="info-table">
<view class="info-table-row">
<view class="info-table-item">
<text class="info-label">车牌:</text>
<text class="info-value">{{ selectedVehicle.driverCarNum }}</text>
</view>
<view class="info-table-item">
<text class="info-label">性质:</text>
<text class="info-value">{{ selectedVehicle.ownTypeLabel }}</text>
</view>
<view class="info-table-item">
<text class="info-label">型号:</text>
<text class="info-value">{{ selectedVehicle.rescueCarBrand }}</text>
</view>
</view>
<view class="info-table-row">
<view class="info-table-item">
<text class="info-label">车龄:</text>
<text class="info-value">{{ selectedVehicle.carAge }}</text>
</view>
<view class="info-table-item">
<text class="info-label">所属司机:</text>
<text class="info-value">{{ selectedVehicle.driverName }}</text>
</view>
<view class="info-table-item">
<text class="info-label">调度:</text>
<text class="info-value">{{ selectedVehicle.secondDispatchName }}</text>
</view>
</view>
<view class="info-table-row">
<view class="info-table-item full-width">
<text class="info-label">累计里程:</text>
<text class="info-value">{{ kpiTotal.mileage }}公里</text>
</view>
<view class="info-table-item full-width">
<text class="info-label">救援单数:</text>
<text class="info-value">{{ kpiTotal.orderCount }}</text>
</view>
</view>
<view class="info-table-row">
<view class="info-table-item full-width">
<text class="info-label">总金额:</text>
<text class="info-value">{{ kpiTotal.amount }}</text>
</view>
</view>
</view>
</view>
<!-- 业绩详细表格 -->
<view class="detail-section">
<scroll-view scroll-x scroll-y class="table-scroll">
<view class="data-table" style="min-width: 1000rpx;">
<view class="table-row header-row">
<view
v-for="(col, index) in detailColumns"
:key="index"
class="table-cell header-cell"
:style="{width: col.width}"
>
{{ col.label }}
</view>
</view>
<view
v-for="(item, index) in vehicleDetailData"
:key="index"
class="table-row"
>
<view
v-for="(col, colIndex) in detailColumns"
:key="colIndex"
class="table-cell"
:style="{width: col.width}"
>
{{ item[col.prop] }}
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import request from '../../utils/request';
import { getDictDataByType } from '../../utils/utils.js'
import CommonTimeSelect from "@/components/commonTimeSelect.vue";
export default {
data() {
return {
// ===== 字典映射 =====
sexMap: { 0: '男', 1: '女' },
authStatusMap: { 0: '待认证', 1: '审核中', 2: '审核通过', 3: '认证失败' },
driverTypeMap: { '01': '自有', '02': '挂靠', 1: '挂靠', 0: '自有' },
driverStatusMap:{ 1: '空闲', 2: '暂停', 3: '忙碌', 4: '离线' },
roleId: undefined,
secondDispatcherRoleId: undefined,
tenantId: 180,
// roleCode: 'ddzx',
channelList:[],
tabs: ['流程人员管理', '业务统计'],
currentTab: 0,
selectedNode: 0,
showVehicleDetail: false,
selectedVehicle: {},
detailMonth: '7月1号-7月31号',
nodeList: [
{ name: '调度中心', type: 'dispatch' },
{ name: '二级调度', type: 'secondary' },
{ name: '司机', type: 'driver' }
],
dispatchData: [],
secondaryData: [],
driverData: [],
/* ① 统一写成对象数组label 负责显示value 负责提交 */
optionRescueCarOwn : [
// { label: '全部', value: null },
// { label: '自有', value: '0' },
// { label: '挂靠', value: '1' }
],
optionChannel : [
// {label:'全部', value:null},
// {label:'城际运输', value:'CJ'},
// {label:'市内配送', value:'SN'},
// {label:'长途运输', value:'CT'}
],
optionOrderStatus : [
// {label:'全部', value:null},
// {label:'已付款', value:'1'},
// {label:'未付款', value:'0'},
// {label:'部分付款', value:'2'}
],
/* ② 当前已选值null 表示“全部”) */
filters:{
rescueCarOwn : null,
driverName : null,
driverCarNum : null,
secondDispatchName : null,
channel : null,
orderStatus: null,
rescueTime: null,
beginDate : null,
endDate : null
},
vehicleDetailAll: [], // ← 新增,保存接口完整结果
vehicleList: [],
vehicleDetailData: [],
ranges: ['2023-9-28', '2023-10-7'],
vehicleColumns: [
{ label: '车牌号', prop: 'driverCarNum', width: '220rpx' },
{ label: '性质', prop: 'ownTypeLabel', width: '150rpx' },
{ label: '型号', prop: 'rescueCarBrand', width: '200rpx' },
{ label: '所属司机', prop: 'driverName', width: '180rpx' }
],
detailColumns: [
{ label: '车辆', prop: 'driverCarNum', width: '200rpx' },
// { label: '性质', prop: 'ownTypeLabel', width: '200rpx' },
// { label: '救援司机', prop: 'driverName', width: '200rpx' },
{ label: '渠道', prop: 'channel', width: '200rpx' },
{ label: '调度', prop: 'secondDispatchName', width: '200rpx' },
{ label: '收款状态', prop: 'orderStatusLabel', width: '150rpx' },
{ label: '应收金额', prop: 'setMoney', width: '160rpx' },
{ label: '日期', prop: 'rescueTime', width: '200rpx' },
{ label: '里程(km)', prop: 'mileage', width: '150rpx' }
]
};
},
onLoad() {
this.gettime()
},
onShow() {
// const roleArr = uni.getStorageSync('role');
// this.roleId = roleArr[0].id;
// 获取租户id
const tenantId = uni.getStorageSync('TENANT_ID');
this.tenantId = tenantId;
this.getDdzxRoleId()
this.selectRoleIdByRoleCode();
this.getDriverData()
this.getChannelList()
this.getRescueCarList()
this.getRescueCarAgeAndMileage()
// this.getRescueCarKPI()
this.loadDictOptions();
// this.loadFilterOptions()
},
computed: {
currentColumns() {
if (this.selectedNode === 0 || this.selectedNode === 1) {
return [
{ label: '姓名', prop: 'name', width: '180rpx' },
{ label: '联系电话', prop: 'tel', width: '260rpx' },
{ label: '性别', prop: 'sex', width: '120rpx' },
// { label: '业务渠道', prop: 'channel', width: '220rpx' }
{ label: '业务渠道', prop: 'channels', width: '320rpx' }
];
} else {
return [
{ label: '姓名', prop: 'nickName', width: '220rpx' },
{ label: '性别', prop: 'sex', width: '100rpx' },
{ label: '手机号', prop: 'phonenumber', width: '200rpx' },
{ label: '年龄', prop: 'age', width: '100rpx' },
{ label: '认证状态', prop: 'authStatus', width: '180rpx' },
{ label: '司机性质', prop: 'driverType', width: '180rpx' },
{ label: '所属二级调度', prop: 'secondDispatcherName', width: '300rpx' },
{ label: '接单状态', prop: 'driverStatus', width: '150rpx' }
];
}
},
currentTableData() {
if (this.selectedNode === 0) return this.dispatchData;
if (this.selectedNode === 1) return this.secondaryData;
return this.driverData;
},
/** 根据 vehicleDetailData 实时计算汇总 */
kpiTotal () {
// 累计里程
const mileage = this.vehicleDetailData
.reduce((sum, r) => sum + (Number(r.mileage) || 0), 0);
// 总金额setMoney 可能是字符串,要转数值;空串当 0 处理)
const amount = this.vehicleDetailData
.reduce((sum, r) => sum + (parseFloat(r.setMoney) || 0), 0);
return {
mileage : mileage.toFixed(1), // 保留 1 位小数,可按需调整
orderCount : this.vehicleDetailData.length,
amount : amount.toFixed(2) // 金额保留 2 位小数
};
}
},
components: {
CommonTimeSelect
},
watch: {
secondDispatcherRoleId(val) {
if (val) this.getSecondDispatchData();
},
roleId(val){
if (val) this.getDispatchData();
}
},
methods: {
gettime() {
// 获取当前时间
var now = new Date();
// 获取年份
var year = now.getFullYear();
// 获取月份
var month = now.getMonth() + 1; // 月份从 0 开始,需要加 1
if (month < 10) {
var month = "0" + month
}
// 获取日期
var date = now.getDate();
// 格式化时间
var currentTime = year + '-' + month + '-' + date
this.ranges[0] = currentTime
this.ranges[1] = currentTime
},
async slectRangeInspectionCount(e) {
this.ranges = e
console.log('ranges',this.ranges);
// let data = {
// driverCarNum: this.selectedVehicle.driverCarNum,
// startTime: this.ranges[0],
// endTime: this.ranges[1]
// }
const [start,end] = this.ranges.map(d => new Date(d).getTime());
// 只改这一行:过滤后直接赋值即可,后续汇总由 computed 自动完成
this.vehicleDetailData = this.vehicleDetailAll.filter(row => {
const t = new Date(row.rescueTime).getTime()
return t >= start && t <= end
})
// 过滤 + 重新计算区间
// const filtered = this.vehicleDetailAll.filter(row => {
// const t = new Date(row.rescueTime).getTime();
// return t >= start && t <= end;
// });
// this.vehicleDetailData = filtered;
// 根据时间范围查询
// let res = await request({
// url: '/app/rescueInfo/getRescueCarKPI',
// method: 'get',
// params: data
// })
},
/* 监听输入框实时筛选 - 简单防抖 */
onInputChange(key){
clearTimeout(this._timer);
this._timer = setTimeout(()=>{
// 如果筛选条件是 'secondDispatchName' 或 'channel',获取业绩数据
if (key === 'secondDispatchName' || key === 'channel') {
if (this.selectedVehicle.driverCarNum) {
this.getRescueCarKPI(this.selectedVehicle.driverCarNum); // 使用当前选中的车牌号请求业绩数据
}
} else {
this.getRescueCarList(); // 否则获取车辆列表
}
}, 300); // 300ms 防抖
},
/** 统一拉取字典并生成下拉数组 */
async loadDictOptions () {
// 2-1 车辆性质字典类型RESCUE_CAR_OWN
const ownDict = await getDictDataByType('RESCUE_CAR_OWN');
// 先塞“全部”,再把字典转成 {label,value}
this.optionRescueCarOwn = [
{ label: '全部', value: null },
...ownDict.map(({ label, value }) => ({ label, value }))
];
// 收款
const orderDict = await getDictDataByType('JY_ORDER_STATUS');
// 先塞“全部”,再把字典转成 {label,value}
this.optionOrderStatus = [
{ label: '全部', value: null },
...orderDict.map(({ label, value }) => ({ label, value }))
];
},
/* 点击“查询”——把当前 filters 整体带给后端即可 */
fetch () {
this.getRescueCarList() // 你原来就用这个拉表格,沿用即可
},
/* ---------- 通用工具 ---------- */
getLabel(list,v){ const o=list.find(i=>i.value===v); return o ? o.label : '全部' },
getIndex(list,v){ return Math.max(0, list.findIndex(i=>i.value===v)) },
/** 选择器统一监听 */
onSelect(key, val){
this.$set(this.filters, key, val) // 改值
// this.getRescueCarList() // 重新拉数据
// 触发获取业绩数据
if (key === 'channel' || key === 'secondDispatchName') {
this.getRescueCarKPI(this.selectedVehicle.driverCarNum); // 使用当前选中的车牌号进行请求
} else {
this.getRescueCarList(); // 如果是其他筛选项,重新获取车辆列表
}
},
/* 统一处理日期 */
onDate(val){
this.filters.rescueTime = val
this.getRescueCarList()
},
/** 重置本页所有本地筛选项 */
resetFilters () {
Object.keys(this.filters).forEach(k => this.filters[k] = null)
this.pageNum = 1
this.getRescueCarList()
},
// 获取救援车辆车龄和里程
async getRescueCarAgeAndMileage() {
const res = await request({
url: '/app/rescueInfo/getRescueCarAgeAndMileage',
method: 'GET'
});
// this.vehicleList = res.data;
// ⬅️ ❷ 把车龄/里程补进已存在的 vehicleList
res.data.forEach(info => {
const row = this.vehicleList.find(r => r.driverCarNum === info.rescueCarNum)
if (row) {
row.carAge = info.carAge
row.mileage = info.mileage
}
})
},
// 通过车牌号获取救援车辆业绩表
async getRescueCarKPI(driverCarNum) {
const res = await request({
url: '/app/rescueInfo/getRescueCarKPI',
method: 'GET',
params: {
// driverCarNum: this.selectedVehicle.driverCarNum,
driverCarNum: driverCarNum, // 传递车牌号
channel: this.filters.channel, // 渠道筛选
secondDispatchName: this.filters.secondDispatchName // 调度筛选
}
});
/* ---------- ② 字典映射 ---------- */
// 取一次就行,可放到 data() 缓存
const dictArr = await getDictDataByType('JY_ORDER_STATUS');
const statusMap = dictArr.reduce((m, { value, label }) => {
m[String(value)] = label; // { "0":"未付款", "1":"已付款", ... }
return m;
}, {});
/* ---------- ③ 整理列表 ---------- */
const list = (res.data || []).map(row => ({
...row,
// ① 应收金额:分 → 元,保留 2 位小数
setMoney : row.setMoney != null
? (row.setMoney / 100).toFixed(2) // 2000 → "20.00"
: '',
// 1) 日期裁掉时分秒
rescueTime : row.rescueTime
? row.rescueTime.split(' ')[0] // '2025-10-14'
: '',
// 2) 状态转中文,没有匹配就回退原值
orderStatusLabel : statusMap[String(row.orderStatus)] || row.orderStatus
}));
this.vehicleDetailAll = list; // 缓存“全集”
this.vehicleDetailData = list; // 默认先显示全部
/* ---------- ④ 计算顶部时间区间 ---------- */
if (this.vehicleDetailData.length) {
const ts = this.vehicleDetailData.map(r => new Date(r.rescueTime).getTime());
const fmt = t => {
const d = new Date(t);
return `${d.getMonth() + 1}${d.getDate()}`;
};
this.detailMonth = `${fmt(Math.min(...ts))}-${fmt(Math.max(...ts))}`;
} else {
this.detailMonth = '';
}
},
// 获取救援车辆列表
async getRescueCarList() {
/* 1把 filters 里有值的字段塞进 params */
const params = {}
Object.keys(this.filters).forEach(k => {
const v = this.filters[k]
if (v !== null && v !== '') { // 只传有意义的条件
params[k] = v
}
})
const res = await request({
url: '/app/rescueInfo/rescueCarList',
method: 'GET',
params
});
this.vehicleDetailData = res.data;
const dictArr = await getDictDataByType('rescue_car_own');
// 把字典转成 Map{ "1": "自有", "2": "挂靠" }
const dictMap = dictArr.reduce((acc, { value, label }) => {
acc[String(value)] = label;
return acc;
}, {});
// 生成最终车辆列表
this.vehicleList = res.data.map(item => ({
...item,
// rescueCarOwn 可能是数字,把它转成字符串再映射
ownTypeLabel: dictMap[String(item.rescueCarOwn)] || item.rescueCarOwn
}));
},
/** 统一格式化函数:根据列名返回友好文本 */
formatValue(prop, val) {
if (prop === 'channels') { // ★ 新增
return Array.isArray(val) && val.length
? val.map(c => c.name).join('')
: '-'
}
// 先把数字转字符串,方便匹配 '01' 这种情况
const v = val != null ? String(val) : '';
switch (prop) {
case 'sex': return (v in this.sexMap) ? this.sexMap[v] : v;
case 'authStatus': return (v in this.authStatusMap) ? this.authStatusMap[v] : v;
case 'driverType': return (v in this.driverTypeMap) ? this.driverTypeMap[v] : v;
case 'driverStatus': return (v in this.driverStatusMap) ? this.driverStatusMap[v] : v;
default: return (val != null ? val : '');
}
},
// 通过租户id和角色码查询角色id
// 获取二级调度中心的id
async selectRoleIdByRoleCode() {
const res = await request({
url: '/company/staff/selectRoleIdByRoleCode',
method: 'GET',
params: {
// tenantId: 180,
tenantId: this.tenantId,
code: 'second_dispatcher'
}
});
this.secondDispatcherRoleId = res.data;
},
// 获取调度中心的角色id
async getDdzxRoleId() {
const res = await request({
url: '/company/staff/selectRoleIdByRoleCode',
method: 'GET',
params: {
tenantId: this.tenantId,
code: 'ddzx'
}
});
this.roleId = res.data;
},
// 获取所有渠道
async getChannelList() {
const res = await request({
url: '/rescue-channel-source/channelList',
method: 'GET'
});
this.channelList = res.data;
// b) 生成 picker 选项:先“全部”,再接口返回的每一项
this.optionChannel = [
{ label: '全部', value: null },
...res.data.map(({ id, name }) => ({ label: name, value: name }))
];
},
async getDispatchData () {
// ① 先取员工列表
const res = await request({
url: '/company/staff/newPage',
method: 'GET',
params: {
pageNo: 1,
pageSize: 100,
roleIds: this.roleId
// roleIds: 233
}
})
// this.dispatchData = res.data.records || []
// ② 先给列表每条数据垫一层空数组(可选,但写了可避免“闪一下 -”)
this.dispatchData = (res.data.records || []).map(row => ({
...row,
channels: [] // ← 这样 channels 从一开始就是响应式字段
}))
// 并行获取每位员工的渠道
await Promise.all(
this.dispatchData.map(async (item) => {
try {
const channelRes = await request({
url: `/rescue-second-channel-association/get-channels/${item.userId}`,
// url: `/rescue-second-channel-association/get-channels/4221`,
method: 'GET'
})
// 把渠道 id 映射成 {id,name}
item.channels = (channelRes.data || []).map(id => {
const channel = this.channelList.find(c => c.id === id)
return channel ? { id, name: channel.name } : { id, name: id }
})
console.log(item.channels) // 若需调试可打印这一行
} catch (e) {
console.error(`获取员工 ${item.name} 渠道失败:`, e)
item.channels = []
}
})
)
},
async getSecondDispatchData() {
const res = await request({
url: '/company/staff/newPage',
method: 'GET',
params: {
pageNo: 1,
pageSize: 100,
roleIds: this.secondDispatcherRoleId
}
});
// this.secondaryData = res.data.records;
// ② 先给列表每条数据垫一层空数组(可选,但写了可避免“闪一下 -”)
this.secondaryData = (res.data.records || []).map(row => ({
...row,
channels: [] // ← 这样 channels 从一开始就是响应式字段
}))
// 并行获取每位员工的渠道
await Promise.all(
this.secondaryData.map(async (item) => {
try {
const channelRes = await request({
url: `/rescue-second-channel-association/get-channels/${item.userId}`,
// url: `/rescue-second-channel-association/get-channels/4221`,
method: 'GET'
})
// 把渠道 id 映射成 {id,name}
item.channels = (channelRes.data || []).map(id => {
const channel = this.channelList.find(c => c.id === id)
return channel ? { id, name: channel.name } : { id, name: id }
})
console.log(item.channels) // 若需调试可打印这一行
} catch (e) {
console.error(`获取员工 ${item.name} 渠道失败:`, e)
item.channels = []
}
})
)
},
async getDriverData() {
const res = await request({
url: '/system/rescueInfo/driverAndCarList',
method: 'GET',
params: {
pageNo: 1,
pageSize: 100
}
});
this.driverData = res.data.records;
},
goBack() {
if (this.showVehicleDetail) {
this.showVehicleDetail = false;
} else {
uni.navigateBack();
}
},
async showVehicleInfo(vehicle) {
this.selectedVehicle = {
driverCarNum: vehicle.driverCarNum,
ownTypeLabel: vehicle.ownTypeLabel,
rescueCarBrand: vehicle.rescueCarBrand,
secondDispatchName: vehicle.secondDispatchName,
driverName: vehicle.driverName,
carAge: vehicle.carAge,
mileage: vehicle.mileage
};
this.showVehicleDetail = true;
console.log('详情',vehicle)
console.log('详情1',vehicle.ownTypeLabel)
await this.getRescueCarKPI(vehicle.driverCarNum);
},
getTableWidth() {
const totalWidth = this.currentColumns.reduce((sum, col) => {
return sum + parseInt(col.width);
}, 0);
return totalWidth + 'rpx';
}
}
};
</script>
<style scoped>
/* 加到 <style scoped> 里 */
.time-picker{
display:block;
margin-bottom:20rpx; /* 下方空 20rpx */
}
/* 行容器 ─ 两列平均,左右撑满 */
.filter-row{
display:flex;
flex-wrap:wrap;
justify-content:space-between;
}
/* 每个筛选项:两列结构 + 行距用 margin-bottom 控制 */
.filter-item{
flex:0 0 calc(50% - 18rpx); /* 50%-列距/2 → 两列正好 */
display:flex;
align-items:center;
margin-bottom:15rpx; /* ← 行距,想再宽就调大 */
box-sizing:border-box;
}
/* 整个筛选区与下方表格留白 */
.filter-section{
padding:24rpx 30rpx 40rpx; /* 上/左右/下,下边距 40rpx */
background:#fff;
}
/* label 固定宽度,避免“性质”换行 */
.filter-label{
width:72rpx; /* 两字足够,若仍折行再调小一点 */
font-size:28rpx;
text-align:right;
margin-right:12rpx;
white-space:nowrap;
color:#333;
}
/* Chip不固定像素宽宽度 = 剩余空间;高 & 圆角统一 */
.chip{
flex:1; /* 占满 item 剩余空间 → 两列等宽 */
min-width:0; /* 允许收缩 */
height:56rpx; /* 统一高度 */
padding:0 20rpx;
background:#fff;
border:1rpx solid #dcdfe6;
border-radius:28rpx;
display:flex;
align-items:center;
}
.chip-input{
width:100%;
border:none;
background:transparent;
font-size:26rpx;
color:#333;
}
/* picker 内容左对齐 */
.chip view{ width:100%; text-align:left; }
/* 下移 20rpx 的小工具类 */
.mt20{ margin-top:20rpx; }
/* 让筛选行和下面的 filter-section 视觉留白 */
.filter-section{ margin-top:12rpx; } /* 原样式在文件里,直接把这一行加进去即可 */
/* 把 Chip 再收一收,留一点空间 */
.filter-value{ margin-right:20rpx; }
/* 头部按钮组 */
.header-btns{
display:flex;
gap:24rpx;
align-items:center;
}
/* 按钮公用底板 */
.search-btn,
.clear-btn{
padding: 6rpx 26rpx;
font-size:26rpx;
border-radius:40rpx;
border:1rpx solid;
}
.search-btn{
color:#fff;
background:#4a7aff;
border-color:#4a7aff;
}
.clear-btn{
color:#4a7aff;
border-color:#4a7aff;
}
.search-btn:active,
.clear-btn:active{ opacity:.6; }
/* 每个下拉框的显示块:加圆角+阴影,看上去像 Chip */
.filter-value{
padding:10rpx 28rpx;
min-width:120rpx;
background:#f0f4ff;
border-radius:40rpx;
font-size:26rpx;
text-align:center;
color:#333;
box-shadow:0 0 6rpx rgba(0,0,0,.04) inset;
}
.filter-header{
display:flex;
justify-content:space-between;
align-items:center;
padding:0 30rpx 16rpx;
background:#fff;
margin-bottom:12rpx;
}
.filter-header-title{
font-size:28rpx;
color:#333;
font-weight:600;
}
.clear-btn{
font-size:26rpx;
color:#4a7aff;
}
.clear-btn:active{ opacity:.6; }
.container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
overflow-x:hidden;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(180deg, #3a8dff, #579dff);
color: #fff;
padding: 20px 16px;
height: auto; /* 高度由内边距撑开即可 */
box-sizing: border-box;
}
.navbar-left,
.navbar-right {
width: 100rpx;
}
.icon-back {
font-size: 48rpx;
color: #fff;
font-weight: bold;
}
.navbar-title {
flex: 1;
text-align: center;
font-size: 36rpx;
color: #fff;
font-weight: bold;
}
.tab-bar {
display: flex;
background-color: #fff;
height: 88rpx;
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 30rpx;
color: #666;
position: relative;
}
.tab-item.active {
color: #4a7aff;
font-weight: bold;
}
.tab-line {
position: absolute;
bottom: 0;
width: 60rpx;
height: 6rpx;
background-color: #4a7aff;
border-radius: 3rpx;
}
.personnel-page {
flex: 1;
display: flex;
flex-direction: column;
overflow-x:hidden;
}
.node-tabs {
display: flex;
background-color: #fff;
padding: 20rpx 0;
border-bottom: 1px solid #e5e5e5;
}
.node-tab {
flex: 1;
text-align: center;
padding: 16rpx 0;
font-size: 28rpx;
color: #666;
margin: 0 20rpx;
border-radius: 8rpx;
}
.node-tab.active {
background-color: #e6f0ff;
color: #4a7aff;
font-weight: bold;
}
.statistics-page {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.filter-section {
background-color: #fff;
padding: 20rpx 30rpx;
border-bottom: 1px solid #e5e5e5;
}
.filter-group {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.filter-label {
font-size: 28rpx;
color: #333;
margin-right: 12rpx;
}
.filter-value {
padding: 8rpx 20rpx;
background-color: #f5f5f5;
border-radius: 6rpx;
font-size: 26rpx;
color: #333;
margin-right: 30rpx;
min-width: 80rpx;
text-align: center;
}
.detail-page {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.detail-title-bar {
background-color: #fff;
padding: 24rpx 30rpx;
border-bottom: 1px solid #e5e5e5;
text-align: center;
}
.detail-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.info-section {
background-color: #fff;
padding: 20rpx 30rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 20rpx;
}
.info-table {
width: 100%;
}
.info-table-row {
display: flex;
width: 100%;
margin-bottom: 20rpx;
}
.info-table-row:last-child {
margin-bottom: 0;
}
.info-table-item {
flex: 1;
display: flex;
align-items: center;
}
.info-table-item.full-width {
flex: 1;
}
.info-label {
font-size: 28rpx;
color: #666;
white-space: nowrap;
}
.info-value {
font-size: 28rpx;
color: #333;
margin-left: 8rpx;
}
.detail-section {
min-height: 0;
background-color: #fff;
margin-top: 16rpx;
padding: 20rpx 0;
}
.table-scroll {
flex: 1;
width: 100%;
height: 100%;
/* height: calc(100vh - 200rpx - 88rpx - 100rpx); */
background-color: #fff;
}
.data-table {
display: inline-block;
}
.table-row {
display: flex;
border-bottom: 1px solid #e5e5e5;
}
.header-row {
background-color: #fafafa;
}
.table-cell {
white-space: nowrap;
padding: 24rpx 16rpx;
font-size: 26rpx;
color: #666;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
word-break: break-all;
white-space: nowrap;
border-right: 1px solid #e5e5e5;
box-sizing: border-box;
flex-shrink: 0;
}
.table-cell:last-child {
border-right: none;
}
.header-cell {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.clickable:active {
background-color: #f5f5f5;
}
.wrap-cell{
display : block; /* 关键:不要再用 flex */
white-space : normal !important;
word-break : break-all;
text-align : left; /* 视觉更舒服;可按需改 */
line-height : 36rpx; /* 行距自定 */
}
</style>