From 4d1957b36cded753f1b88d1fbb664ca7cb8d2183 Mon Sep 17 00:00:00 2001 From: xyc <3422692813@qq.com> Date: Thu, 9 Oct 2025 09:54:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/utils.js | 598 ++++++++++++++++++++++++++ src/views/repair/statistics/index.vue | 287 ++++++++++++ 2 files changed, 885 insertions(+) create mode 100644 src/utils/utils.js create mode 100644 src/views/repair/statistics/index.vue diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 0000000..537a108 --- /dev/null +++ b/src/utils/utils.js @@ -0,0 +1,598 @@ +import request from '@/utils/request'; +import { + setStorageWithExpiry, + getStorageWithExpiry +} from '@/utils/auth' + +export function getWXStatusHeight() { + // #ifdef MP-WEIXIN + // 获取距上 + const barTop = wx.getSystemInfoSync().statusBarHeight + // 获取胶囊按钮位置信息 + const menuButtonInfo = wx.getMenuButtonBoundingClientRect() + // 获取导航栏高度 + const barHeight = menuButtonInfo.height + (menuButtonInfo.top - barTop) * 2 + let barWidth = menuButtonInfo.width + console.log('menuButtonInfo', menuButtonInfo) + let barLeftPosition = 375 - menuButtonInfo.right + menuButtonInfo.width + let menuButtonLeft = menuButtonInfo.left + let menuButtonRight = menuButtonInfo.right + return { + barHeight, + barTop, + barWidth, + barLeftPosition, + menuButtonLeft, + menuButtonRight + } + // #endif +} + +/** + * 根据订单的状态获取订单的文字展示状态(这个状态范围少,是按李总意思整理出来的状态) + * @param ticketsStatus 订单状态 + * @param isHandover 是否交车 + */ +export function getOrderStatusText(ticketsStatus, isHandover) { + let str = "已进厂"; + if ("04" == ticketsStatus) { + //待派工 + str = "待维修" + } else if ("05" == ticketsStatus) { + //维修中 + str = "维修中" + } else if ("01" == ticketsStatus) { + //待取车结算 + if ("1" == isHandover) { + //已交车 + str = "已交车未结算" + } else { + //未交车 + str = "未交车未结算" + } + } else if ("06" == ticketsStatus) { + //挂单/记账 + if ("1" == isHandover) { + //已交车 + str = "已交车已结算" + } else { + //未交车 + str = "已结算未交车" + } + } else if ("07" == ticketsStatus) { + //待通知客户取车 + str = "已竣工" + } else if ("02" == ticketsStatus) { + //已结账 + if ("1" == isHandover) { + //已交车 + str = "已交车已结算" + } else { + //未交车 + str = "已结算未交车" + } + } else if ("03" == ticketsStatus) { + //已作废 + str = "已作废" + } else if ("08" == ticketsStatus) { + //已作废 + str = "已完成" + } + return str; +} + +/** + * 根据订单的状态获取订单的文字展示状态(这个状态多,就是系统初始状态) + * @param ticketsStatus 订单状态 + * @param isHandover 是否交车 + */ +export function getOrderStatusTextAll(ticketsStatus, isHandover) { + let str = "已进厂"; + if ("04" == ticketsStatus) { + //待派工 + str = "待派工" + } else if ("05" == ticketsStatus) { + //维修中 + str = "维修中" + } else if ("01" == ticketsStatus) { + //待取车结算 + if ("1" == isHandover) { + //已交车 + str = "已交车未结算" + } else { + //未交车 + str = "未交车未结算" + } + } else if ("06" == ticketsStatus) { + //挂单/记账 + str = "已挂单/记账待交车" + if ("1" == isHandover) { + //已交车 + str = "已交车已结算" + } else { + //未交车 + str = "已结算未交车" + } + } else if ("07" == ticketsStatus) { + //待通知客户取车 + str = "待通知客户取车" + } else if ("02" == ticketsStatus) { + //已结账 + if ("1" == isHandover) { + //已交车 + str = "已交车已结算" + } else { + //未交车 + str = "已结算未交车" + } + } else if ("03" == ticketsStatus) { + //已作废 + str = "已作废" + } else if ("08" == ticketsStatus) { + //已作废 + str = "已完成" + } + return str; +} + + +/** + * 查询字典可选值 + * @param dictCode + */ +export function getDictByCode(dictCode) { + let dictArray = getStorageWithExpiry(dictCode); + if (null == dictArray || undefined == dictArray) { + request({ + url: '/admin-api/system/dict-data/type', + method: 'get', + params: { + type: dictCode + } + }).then((res) => { + console.log(res) + if (res.code == 200) { + setStorageWithExpiry(dictCode, res.data, 3600) + return res.data + } + }) + } else { + return dictArray + } +} +/** + * 工单记录操作日志 + * @param id 工单主表id + * @param ticketsWorkStatus 工单主表状态 + * @param itemId 工单子表id + * @param itemStatus 工单子表状态 + * @param recordType 操作类型 对应后端枚举:RecordTypeEnum + * @param remark 备注 + * @param image 图片相对路径,多个英文逗号隔开 + * @param finishType 完成类型 01:完成并移交下一班组、02:完成并移交总检、03:完成工单 + * @param nextName 下一班组名称 + */ +export function saveTicketsRecords(id, ticketsWorkStatus, itemId, itemStatus, recordType, remark, image, finishType, + nextName) { + return new Promise((resolve, reject) => { + let dataObj = { + id: id, + ticketsWorkStatus: ticketsWorkStatus, + item: { + id: itemId, + itemStatus: itemStatus + }, + recordType: recordType, + remark: remark, + finishType: finishType, + nextName: nextName, + image: image + } + request({ + url: '/admin-api/repair/tickets/updateStatus', + method: 'POST', + data: dataObj + }).then((res) => { + console.log(res) + if (res.code == 200) { + uni.showToast({ + title: '操作成功', + icon: 'none' + }) + resolve(1); + } else { + uni.showToast({ + title: '操作失败,请联系管理员', + icon: 'none' + }) + reject(0); + } + }) + }); +} + +/** + * 翻译字典 + * @param dictCode + */ +export async function getDictTextByCodeAndValue(dictCode, value) { + let dictArray = getStorageWithExpiry(dictCode); + if (null == dictArray || undefined == dictArray) { + let res = await request({ + url: '/admin-api/system/dict-data/type', + method: 'get', + params: { + type: dictCode + } + }) + if (res.code == 200) { + setStorageWithExpiry(dictCode, res.data, 3600) + dictArray = res.data + let dictObj = dictArray.find(item => item.value == value) + if (dictObj) { + return dictObj.label + } else { + return "未知数据" + } + } + } else { + let dictObj = dictArray.find(item => item.value == value) + if (dictObj) { + return dictObj.label + } else { + return "未知数据" + } + } +} + +export function formatTimestamp(timestamp) { + // 将时间戳转换为Date对象 + const date = new Date(timestamp); + // 获取年月日时分秒 + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + const seconds = date.getSeconds().toString().padStart(2, '0'); + // 组合成日期时间字符串 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; +} + +/** + * 将时间戳转换为指定格式的日期字符串 + * @param {number} timestamp - 时间戳(毫秒) + * @param {string} [format='YYYY-MM-DD'] - 日期格式,默认为 'YYYY-MM-DD' + * @returns {string} - 格式化的日期字符串 + */ +export function formatTimestampCustom(timestamp, format = 'YYYY-MM-DD') { + const date = new Date(timestamp); + + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + const hour = date.getHours().toString().padStart(2, '0'); + const minute = date.getMinutes().toString().padStart(2, '0'); + const second = date.getSeconds().toString().padStart(2, '0'); + + const replaceMap = { + 'YYYY': year, + 'MM': month, + 'DD': day, + 'HH': hour, + 'mm': minute, + 'ss': second + }; + + return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => replaceMap[match]); +} + +/** + * 格式化金额 + * @param {string|number} amount - 金额 + * @returns {string} - 格式化后的金额 + */ +export function formatCurrency(amount) { + if (amount === undefined || amount === null || amount === '') { + return '0.00'; + } + // 处理可能的字符串类型金额 + const num = typeof amount === 'string' ? parseFloat(amount) : amount; + // 检查是否是有效数字 + if (isNaN(num)) { + return '0.00'; + } + // 格式化金额,保留两位小数 + return num.toLocaleString('zh-CN', { + minimumFractionDigits: 2, + numberOfDigits: 2 + }); +} + +/** + * 组装订单对象 + * @param order + */ +export function builderOrder(order) { + return { + id: order.id, + orderNo: order.ticketNo, + flag: 1, + ticketsStatus: order.ticketsStatus, + ticketsWorkStatus: order.ticketsWorkStatus, + flagStr: getOrderStatusTextAll(order.ticketsStatus, order.isHandover), + carNum: order.carNo, + nowRepairId: order.nowRepairId, + carModel: order.carBrandName, + userName: order.userName, + userPhone: order.userMobile, + counselorName: order.adviserName, + canOperate: order.canOperate, + ...order, + } +} + +export function formatDate(timestamp) { + // 将时间戳转换为Date对象 + const date = new Date(timestamp); + // 获取年月日时分秒 + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + // 组合成日期时间字符串 + return `${year}-${month}-${day}`; +} + +//转换为double +// utils.js +export function convertToDouble(value, decimalPlaces = 1) { + if (value !== undefined && value !== null) { + const parsedValue = parseFloat(value); + if (!isNaN(parsedValue)) { + return parsedValue.toFixed(decimalPlaces); + } else { + console.error('转换失败,值不是有效的数字'); + return '0.0'; // 可以设置一个默认值 + } + } else { + console.error('值不存在'); + return '0.0'; // 可以设置一个默认值 + } +} + + +/** + * 生成一个16位的纯数字的唯一ID + * 生成策略 head + 当前时间戳 + 随机数 + * @param head 前缀 + */ +export function createUniqueCodeByHead(head = '') { + const min = 100; // 最小值 + const max = 999; // 最大值 + return head.toString() + Date.now().toString() + Math.floor(Math.random() * (max - min + 1)) + min; +} + +/** + * 构造树 + * @param items 原对象数组 + * @param idKey 主标识 + * @param parentKey 父标识 + * @param mapping 映射Map + */ +export function buildTree(items, idKey, parentKey, mapping = null) { + const result = []; // 存储最终的树形结构 + const itemMap = {}; // 用于快速查找对象 + + // 首先将所有项放入map中,便于后续快速查找 + for (const item of items) { + itemMap[item[idKey]] = { + ...item, + children: [] + }; + } + + // 遍历每个项,构建树形结构 + for (const item of items) { + const id = item[idKey]; + const parentId = item[parentKey]; + + const node = itemMap[id]; + if (parentId !== null && itemMap[parentId]) { + // 如果有父ID,并且父节点存在,则将当前节点加入到其父节点的children数组中 + itemMap[parentId].children.push(node); + } else { + // 如果没有父ID,或者找不到对应的父节点,则认为这是根节点 + result.push(node); + } + } + + if (mapping) { + return mapTree(result, mapping) + } + + return result; +} + +// 映射函数 +function mapTree(tree, mapping) { + if (!tree || !Array.isArray(tree)) { + return tree; + } + + const mappedTree = tree.map(item => { + const mappedItem = {}; + for (const key in item) { + if (key === 'children') { + // 递归处理 children 数组 + if (mapping.children) { + mappedItem[mapping.children] = mapTree(item[key], mapping); + } else { + mappedItem[key] = mapTree(item[key], mapping); + } + } else if (key in mapping) { + // 根据映射规则转换属性 + const targetKey = mapping[key]; + mappedItem[targetKey] = item[key]; + } + } + return mappedItem; + }); + + return mappedTree; +} + +export function formatDateChinese(timestamp) { + // 将时间戳转换为Date对象 + const date = new Date(timestamp); + // 获取年月日时分秒 + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + // 组合成日期时间字符串 + return `${year}年${month}月${day}日`; +} + +export function formatDateTimeToMinute(timestamp) { + // 将时间戳转换为 Date 对象 + const date = new Date(timestamp); + // 获取年月日时分 + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + // 组合成日期时间字符串(格式:yyyy-MM-dd hh:mm) + return `${year}-${month}-${day} ${hours}:${minutes}`; +} + +export function getDateRange(type) { + const now = new Date() + let start, end + + if (type === 'week') { + // 获取本周的开始(周一)和结束(周日) + const day = now.getDay() || 7 // Sunday 为 0,设为 7 + start = new Date(now) + start.setDate(now.getDate() - day + 1) + + end = new Date(start) + end.setDate(start.getDate() + 6) + } else if (type === 'month') { + // 获取本月的开始和结束 + start = new Date(now.getFullYear(), now.getMonth(), 1) + end = new Date(now.getFullYear(), now.getMonth() + 1, 0) // 本月最后一天 + } else if (type === 'day') { + // 获取当天的开始和结束(00:00:00 到 23:59:59) + start = new Date(now) + start.setHours(0, 0, 0, 0) + + end = new Date(now) + end.setHours(23, 59, 59, 999) + } else { + return [] + } + + return [ + formatDateCus(start), + formatDateCus(end) + ] +} + +export function formatDateCus(date) { + const y = date.getFullYear() + const m = (date.getMonth() + 1).toString().padStart(2, '0') + const d = date.getDate().toString().padStart(2, '0') + return `${y}-${m}-${d}` +} + +/** + * 将扁平数组转换为树形结构,并支持按 sort 字段排序 + * @param {Array} data 源数据 + * @param {String} id 节点ID字段名,默认为 'id' + * @param {String} parentId 父节点ID字段名,默认为 'parentId' + * @param {String} children 子节点字段名,默认为 'children' + * @param {Number|String} rootId 根节点ID值,默认为最小的parentId或0 + * @param {String} sortKey 排序字段名,默认为 'sort' + * @returns {Array} 树形结构数据 + */ +export function handleTree(data, id, parentId, children, rootId, sortKey) { + // 参数默认值处理 + id = id || 'id'; + parentId = parentId || 'parentId'; + children = children || 'children'; + sortKey = sortKey || 'sort'; + + // 自动计算根节点ID(取最小的parentId,若无则用0) + rootId = rootId || Math.min(...data.map(item => item[parentId])) || 0; + + // 深拷贝源数据以避免污染原数组 + const cloneData = JSON.parse(JSON.stringify(data)); + + // 先对所有数据进行排序(确保父节点在前) + cloneData.sort((a, b) => { + const aSort = a[sortKey] ?? 0; // 使用空值合并运算符处理undefined + const bSort = b[sortKey] ?? 0; + return aSort - bSort; // 升序排序 + }); + + // 构建哈希表加速查找 + const nodeMap = {}; + cloneData.forEach(item => { + nodeMap[item[id]] = item; + item[children] = []; // 初始化children数组 + }); + + // 构建树形结构 + const tree = []; + cloneData.forEach(item => { + if (item[parentId] === rootId) { + // 根节点直接加入结果 + tree.push(item); + } else { + // 非根节点找到父节点并插入 + const parent = nodeMap[item[parentId]]; + parent?.[children]?.push(item); + } + }); + + // 递归排序所有子节点 + const sortChildren = (nodes) => { + nodes.forEach(node => { + if (node[children]?.length) { + node[children].sort((a, b) => { + const aSort = a[sortKey] ?? 0; + const bSort = b[sortKey] ?? 0; + return aSort - bSort; + }); + sortChildren(node[children]); + } + }); + }; + sortChildren(tree); + + return tree.length ? tree : data; // 空树时返回原数据 +} + +/** + * 将树形结构数据转成 uniapp multiSelector picker 可用的二维数组 + * @param {Array} tree 树形结构数据 + * @param {String} labelKey 节点显示字段,默认为 'name' + * @param {String} childrenKey 子节点字段,默认为 'children' + * @returns {Array} picker二维数组,例如 [[一级], [二级], [三级]...] + */ +export function toPickerData(tree, labelKey = 'name', childrenKey = 'children') { + const result = [] + + function buildColumns(nodes, level = 0) { + if (!nodes || !nodes.length) return + // 当前层级的所有 name + result[level] = nodes.map(n => n[labelKey]) + // 默认取第一个子节点继续递归 + if (nodes[0][childrenKey] && nodes[0][childrenKey].length) { + buildColumns(nodes[0][childrenKey], level + 1) + } + } + + buildColumns(tree, 0) + return result +} \ No newline at end of file diff --git a/src/views/repair/statistics/index.vue b/src/views/repair/statistics/index.vue new file mode 100644 index 0000000..f32d987 --- /dev/null +++ b/src/views/repair/statistics/index.vue @@ -0,0 +1,287 @@ + + + + +