feat(odf-uniapp): Add mileage correction editing and export enhancements
Some checks are pending
continuous-integration/drone/push Build is running

- Add inline editing UI for mileage correction field in fault detail page
- Implement save/cancel buttons for mileage correction updates
- Add updateMileageCorrection API endpoint and service method
- Update App.vue global styles with page background color
- Enhance pages.json with backgroundColorTop and backgroundColorBottom
- Add MileageCorrectionDto for request validation
- Implement batch fault times retrieval for export functionality
- Add fault frequency time concatenation in export data
- Improve export data structure with complete fault history information
This commit is contained in:
zpc 2026-03-31 15:51:07 +08:00
parent 1bd5bc74ee
commit 837ff4d4cc
10 changed files with 293 additions and 8 deletions

View File

@ -14,4 +14,7 @@
<style>
/*每个页面公共css */
page {
background-color: #F5F5F5;
}
</style>

View File

@ -140,6 +140,8 @@
"navigationBarTextStyle": "white",
"navigationBarTitleText": "绥时录",
"navigationBarBackgroundColor": "#1A73EC",
"backgroundColor": "#F5F5F5"
"backgroundColor": "#F5F5F5",
"backgroundColorTop": "#F5F5F5",
"backgroundColorBottom": "#F5F5F5"
}
}

View File

@ -73,9 +73,29 @@
<text class="info-label">表显故障里程</text>
<text class="info-value">{{ displayMileage }}</text>
</view>
<view class="info-row" v-if="detail.mileageCorrection">
<view class="info-row">
<text class="info-label">表显里程矫正</text>
<text class="info-value">{{ detail.mileageCorrection }}</text>
<view class="info-value correction-row" v-if="editingCorrection">
<input
class="correction-input"
v-model="correctionInput"
type="text"
placeholder="请输入"
placeholder-class="input-placeholder"
/>
<view class="correction-btn save-btn" @click="saveCorrection">
<text class="correction-btn-text">保存</text>
</view>
<view class="correction-btn cancel-btn" @click="cancelEditCorrection">
<text class="correction-btn-text cancel-text">取消</text>
</view>
</view>
<view class="info-value correction-row" v-else>
<text>{{ detail.mileageCorrection || '未填写' }}</text>
<view class="correction-btn edit-btn" @click="startEditCorrection">
<text class="correction-btn-text">修改</text>
</view>
</view>
</view>
<view class="info-row">
<text class="info-label">所属光缆</text>
@ -104,7 +124,7 @@
<script setup>
import { ref, reactive, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getFaultDetail, incrementFaultCount } from '@/services/trunk'
import { getFaultDetail, incrementFaultCount, updateMileageCorrection } from '@/services/trunk'
import { BASE_URL } from '@/services/api'
import { openNavigation } from '@/utils/navigation'
@ -128,6 +148,9 @@ const detail = reactive({
faultTimes: []
})
const incrementing = ref(false)
const editingCorrection = ref(false)
const correctionInput = ref('')
const savingCorrection = ref(false)
const allFaultTimes = computed(() => {
const times = []
@ -228,6 +251,34 @@ async function handleIncrement() {
}
}
function startEditCorrection() {
correctionInput.value = detail.mileageCorrection || ''
editingCorrection.value = true
}
function cancelEditCorrection() {
editingCorrection.value = false
}
async function saveCorrection() {
if (savingCorrection.value) return
savingCorrection.value = true
try {
const res = await updateMileageCorrection(faultId.value, correctionInput.value)
if (res.code === 200) {
detail.mileageCorrection = correctionInput.value
editingCorrection.value = false
uni.showToast({ title: '保存成功', icon: 'success' })
} else {
uni.showToast({ title: res.msg || '保存失败', icon: 'none' })
}
} catch (err) {
uni.showToast({ title: '网络异常', icon: 'none' })
} finally {
savingCorrection.value = false
}
}
function handleNavigate() {
openNavigation(detail.latitude, detail.longitude, detail.location || '故障地点')
}
@ -407,6 +458,55 @@ onLoad((options) => {
font-size: 24rpx;
}
.correction-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.correction-input {
flex: 1;
height: 56rpx;
padding: 0 16rpx;
background: #F5F5F5;
border-radius: 8rpx;
border: 1rpx solid #E8E8E8;
font-size: 26rpx;
color: #333;
}
.input-placeholder {
color: #999;
}
.correction-btn {
border-radius: 8rpx;
padding: 6rpx 20rpx;
flex-shrink: 0;
}
.edit-btn {
background: #1A73EC;
}
.save-btn {
background: #1A73EC;
}
.cancel-btn {
background: #fff;
border: 1rpx solid #ccc;
}
.correction-btn-text {
color: #fff;
font-size: 24rpx;
}
.cancel-text {
color: #666;
}
.bottom-bar {
position: fixed;
bottom: 0;

View File

@ -45,5 +45,8 @@ export function addFault(formData) {
export const incrementFaultCount = (id) =>
post(`/business/OdfCableFaults/incrementFaultCount/${id}`)
export const updateMileageCorrection = (id, mileageCorrection) =>
post(`/business/OdfCableFaults/updateMileageCorrection/${id}`, { mileageCorrection })
export const searchCablesAndFaults = (deptId, keyword) =>
get('/business/OdfCables/search', { deptId, keyword })

View File

@ -74,6 +74,18 @@ namespace ZR.Admin.WebApi.Controllers.Business
return SUCCESS(response);
}
/// <summary>
/// 更新表显里程矫正
/// </summary>
[HttpPost("updateMileageCorrection/{id}")]
[ActionPermissionFilter(Permission = "odfcablefaults:list")]
[Log(Title = "干线故障", BusinessType = BusinessType.UPDATE)]
public IActionResult UpdateMileageCorrection(int id, [FromBody] MileageCorrectionDto dto)
{
var response = _OdfCableFaultsService.UpdateMileageCorrection(id, dto.MileageCorrection);
return ToResponse(response);
}
/// <summary>
/// 删除故障并级联删除图片
/// </summary>
@ -102,6 +114,10 @@ namespace ZR.Admin.WebApi.Controllers.Business
return ToResponse(ResultCode.FAIL, "没有要导出的数据");
}
// 收集所有故障ID批量查询频次时间记录
var faultIds = list.Result.Select(item => (int)((dynamic)item).Id).ToList();
var allFaultTimes = _OdfCableFaultsService.GetFaultTimesByFaultIds(faultIds);
// 将英文属性名转换为中文列头
var exportList = new List<Dictionary<string, object>>();
foreach (var item in list.Result)
@ -109,14 +125,29 @@ namespace ZR.Admin.WebApi.Controllers.Business
var obj = (object)item;
var props = obj.GetType().GetProperties();
var dict = props.ToDictionary(p => p.Name, p => p.GetValue(obj));
var faultId = (int)dict.GetValueOrDefault("Id");
var faultCount = dict.ContainsKey("FaultCount") ? dict["FaultCount"] : 1;
// 拼接所有故障时间(首次 + 增加的频次时间)
var times = new List<string>();
var firstTime = dict.GetValueOrDefault("FaultTime");
if (firstTime != null) times.Add(firstTime.ToString());
if (allFaultTimes.ContainsKey(faultId))
{
times.AddRange(allFaultTimes[faultId].Select(t => t.ToString()));
}
times.Sort();
var row = new Dictionary<string, object>
{
["编号"] = dict.GetValueOrDefault("Id"),
["光缆编号"] = dict.GetValueOrDefault("CableId"),
["故障时间"] = dict.GetValueOrDefault("FaultTime"),
["故障时间"] = string.Join("\n", times),
["故障发生频次"] = faultCount,
["人员"] = dict.GetValueOrDefault("Personnel"),
["故障原因"] = dict.GetValueOrDefault("FaultReason"),
["表显故障里程"] = dict.GetValueOrDefault("Mileage"),
["表显里程矫正"] = dict.GetValueOrDefault("MileageCorrection"),
["地点"] = dict.GetValueOrDefault("Location"),
["纬度"] = dict.GetValueOrDefault("Latitude"),
["经度"] = dict.GetValueOrDefault("Longitude"),

View File

@ -76,6 +76,9 @@ namespace ZR.Model.Business.Dto
[ExcelColumnName("表显故障里程")]
public string Mileage { get; set; }
[ExcelColumnName("表显里程矫正")]
public string MileageCorrection { get; set; }
[ExcelColumnName("地点")]
public string Location { get; set; }
@ -93,5 +96,16 @@ namespace ZR.Model.Business.Dto
[ExcelColumnName("所属光缆")]
public string CableName { get; set; }
[ExcelColumnName("故障发生频次")]
public int? FaultCount { get; set; }
}
/// <summary>
/// 更新表显里程矫正
/// </summary>
public class MileageCorrectionDto
{
public string MileageCorrection { get; set; }
}
}

View File

@ -43,5 +43,15 @@ namespace ZR.Service.Business.IBusinessService
/// 增加故障频次
/// </summary>
object IncrementFaultCount(int id);
/// <summary>
/// 批量查询故障频次时间记录
/// </summary>
Dictionary<int, List<DateTime>> GetFaultTimesByFaultIds(List<int> faultIds);
/// <summary>
/// 更新表显里程矫正
/// </summary>
int UpdateMileageCorrection(int id, string mileageCorrection);
}
}

View File

@ -42,6 +42,7 @@ namespace ZR.Service.Business
f.Longitude,
f.Remark,
f.CreatedAt,
f.FaultCount,
CableName = c.CableName
})
.MergeTable()
@ -269,6 +270,24 @@ namespace ZR.Service.Business
};
}
/// <summary>
/// 批量查询故障频次时间记录按故障ID分组
/// </summary>
public Dictionary<int, List<DateTime>> GetFaultTimesByFaultIds(List<int> faultIds)
{
if (faultIds == null || faultIds.Count == 0)
return new Dictionary<int, List<DateTime>>();
var records = Context.Queryable<OdfCableFaultTimes>()
.Where(t => faultIds.Contains(t.FaultId))
.OrderBy(t => t.FaultTime)
.ToList();
return records
.GroupBy(t => t.FaultId)
.ToDictionary(g => g.Key, g => g.Select(t => t.FaultTime).ToList());
}
/// <summary>
/// 查询表达式
/// </summary>
@ -327,10 +346,12 @@ namespace ZR.Service.Business
Personnel = item.Personnel,
FaultReason = item.FaultReason,
Mileage = item.Mileage,
MileageCorrection = item.MileageCorrection,
Location = item.Location,
Latitude = item.Latitude ?? 0,
Longitude = item.Longitude ?? 0,
Remark = item.Remark,
FaultCount = item.FaultCount ?? 1,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
@ -348,5 +369,21 @@ namespace ZR.Service.Business
string errorMsg = errorMsgs.Count > 0 ? string.Join("", errorMsgs.Take(10)) : "";
return (successCount, errorCount, errorMsg);
}
/// <summary>
/// 更新表显里程矫正
/// </summary>
public int UpdateMileageCorrection(int id, string mileageCorrection)
{
var fault = GetFirst(f => f.Id == id);
if (fault == null)
{
throw new CustomException("故障记录不存在");
}
fault.MileageCorrection = mileageCorrection;
fault.UpdatedAt = DateTime.Now;
return Update(fault, true);
}
}
}

View File

@ -39,3 +39,14 @@ export function delOdfCableFaults(pid) {
export async function exportOdfCableFaults(query) {
await downFile('business/OdfCableFaults/export', { ...query })
}
/**
* 更新表显里程矫正
*/
export function updateMileageCorrection(id, mileageCorrection) {
return request({
url: 'business/OdfCableFaults/updateMileageCorrection/' + id,
method: 'POST',
data: { mileageCorrection }
})
}

View File

@ -83,7 +83,9 @@
<el-table-column prop="personnel" label="人员" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('personnel')" />
<el-table-column prop="faultReason" label="故障原因" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('faultReason')" />
<el-table-column prop="mileage" label="表显故障里程" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('mileage')" />
<el-table-column prop="mileageCorrection" label="表显里程矫正" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('mileageCorrection')" />
<el-table-column prop="cableName" label="所属光缆" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('cableName')" />
<el-table-column prop="faultCount" label="故障频次" align="center" v-if="columns.showColumn('faultCount')" />
<el-table-column label="地点" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('location')">
<template #default="scope">
<span v-if="scope.row.latitude || scope.row.longitude">{{ scope.row.longitude }}, {{ scope.row.latitude }}</span>
@ -109,9 +111,29 @@
<el-dialog v-model="detailVisible" title="故障详情" width="700px" destroy-on-close>
<el-descriptions :column="2" border v-if="detailData">
<el-descriptions-item label="Id">{{ detailData.id }}</el-descriptions-item>
<el-descriptions-item label="故障时间">{{ detailData.faultTime }}</el-descriptions-item>
<el-descriptions-item label="故障发生频次">{{ detailData.faultCount || 1 }}</el-descriptions-item>
<el-descriptions-item label="故障时间" :span="2">
<div v-if="allDetailFaultTimes.length > 0">
<div v-for="(t, i) in allDetailFaultTimes" :key="i">{{ t }}</div>
</div>
<span v-else>{{ detailData.faultTime }}</span>
</el-descriptions-item>
<el-descriptions-item label="人员">{{ detailData.personnel }}</el-descriptions-item>
<el-descriptions-item label="表显故障里程">{{ detailData.mileage }}</el-descriptions-item>
<el-descriptions-item label="表显故障里程">{{ detailDisplayMileage }}</el-descriptions-item>
<el-descriptions-item label="表显里程矫正">
<div style="display: flex; align-items: center; gap: 8px;">
<el-input
v-if="editingDetailCorrection"
v-model="detailCorrectionInput"
size="small"
style="width: 150px;"
placeholder="请输入" />
<span v-else>{{ detailData.mileageCorrection || '未填写' }}</span>
<el-button v-if="editingDetailCorrection" type="primary" size="small" @click="saveDetailCorrection" :loading="savingDetailCorrection">保存</el-button>
<el-button v-if="editingDetailCorrection" size="small" @click="editingDetailCorrection = false">取消</el-button>
<el-button v-if="!editingDetailCorrection" type="primary" size="small" link @click="startEditDetailCorrection">修改</el-button>
</div>
</el-descriptions-item>
<el-descriptions-item label="所属光缆">{{ detailData.cableName }}</el-descriptions-item>
<el-descriptions-item label="地点">{{ detailData.location }}</el-descriptions-item>
<el-descriptions-item label="纬度">{{ detailData.latitude }}</el-descriptions-item>
@ -142,7 +164,7 @@
</template>
<script setup name="odfcablefaults">
import { listOdfCableFaults, getOdfCableFaults, delOdfCableFaults } from '@/api/business/odfcablefaults.js'
import { listOdfCableFaults, getOdfCableFaults, delOdfCableFaults, updateMileageCorrection } from '@/api/business/odfcablefaults.js'
import { listOdfCables } from '@/api/business/odfcables.js'
import importData from '@/components/ImportData'
@ -156,6 +178,9 @@ const detailLoading = ref(false)
const cableOptions = ref([])
const cableSearchLoading = ref(false)
const faultTimeRange = ref(null)
const editingDetailCorrection = ref(false)
const detailCorrectionInput = ref('')
const savingDetailCorrection = ref(false)
const queryParams = reactive({
pageNum: 1,
@ -174,7 +199,9 @@ const columns = ref([
{ visible: true, align: 'center', type: '', prop: 'personnel', label: '人员', showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'faultReason', label: '故障原因', showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'mileage', label: '表显故障里程', showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'mileageCorrection', label: '表显里程矫正', showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'cableName', label: '所属光缆', showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'faultCount', label: '故障频次' },
{ visible: true, align: 'center', type: '', prop: 'location', label: '地点', showOverflowTooltip: true },
{ visible: false, align: 'center', type: '', prop: 'latitude', label: '纬度' },
{ visible: false, align: 'center', type: '', prop: 'longitude', label: '经度' },
@ -192,6 +219,33 @@ const state = reactive({
const { single, multiple } = toRefs(state)
// +
const allDetailFaultTimes = computed(() => {
if (!detailData.value) return []
const times = []
if (detailData.value.faultTime) {
times.push(detailData.value.faultTime)
}
if (detailData.value.faultTimes && detailData.value.faultTimes.length > 0) {
detailData.value.faultTimes.forEach(t => {
times.push(t.faultTime || t.FaultTime || '')
})
}
times.sort()
return times
})
// = +
const detailDisplayMileage = computed(() => {
if (!detailData.value) return ''
const m = parseFloat(detailData.value.mileage)
const c = parseFloat(detailData.value.mileageCorrection)
if (!isNaN(m) && !isNaN(c)) {
return String(Math.round((m + c) * 10000) / 10000)
}
return detailData.value.mileage || ''
})
//
function remoteCableSearch(query) {
if (query) {
@ -232,6 +286,26 @@ function getList() {
})
}
function startEditDetailCorrection() {
detailCorrectionInput.value = detailData.value?.mileageCorrection || ''
editingDetailCorrection.value = true
}
function saveDetailCorrection() {
savingDetailCorrection.value = true
updateMileageCorrection(detailData.value.id, detailCorrectionInput.value).then((res) => {
if (res.code == 200) {
detailData.value.mileageCorrection = detailCorrectionInput.value
editingDetailCorrection.value = false
proxy.$modal.msgSuccess('保存成功')
getList()
}
savingDetailCorrection.value = false
}).catch(() => {
savingDetailCorrection.value = false
})
}
//
function handleQuery() {
queryParams.pageNum = 1