| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>物料交货计划 - SCOR供应链模型</title>
- <link rel="stylesheet" href="styles.css">
- <!-- 引入Vue 3 -->
- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
- <!-- 引入SheetJS用于Excel解析 -->
- <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
- </head>
- <body>
- <div id="app">
- <div class="container">
- <h1>物料交货计划管理系统</h1>
-
- <!-- 操作区域 -->
- <div class="upload-section">
- <label for="file-upload" class="upload-btn">
- 上传Excel数据
- <input
- type="file"
- id="file-upload"
- accept=".xlsx, .xls"
- style="display: none;"
- @change="handleFileUpload">
- </label>
- <span id="file-name" class="file-name">{{ fileName || '未选择文件' }}</span>
- <button
- id="refresh-btn"
- class="analyze-btn"
- @click="refreshFromERP"
- >
- 从ERP刷新数据
- </button>
- <button
- id="save-btn"
- class="analyze-btn"
- :disabled="!hasUnsavedChanges"
- @click="saveChanges"
- >
- 保存修改
- </button>
- </div>
-
- <!-- 过滤和搜索区域 -->
- <div class="filter-section">
- <div class="filter-controls">
- <select v-model="filterFactoryCode" class="filter-input">
- <option value="">所有工厂</option>
- <option v-for="factory in factories" :key="factory" :value="factory">{{ factory }}</option>
- </select>
- <select v-model="filterSupplier" class="filter-input">
- <option value="">所有供应商</option>
- <option v-for="supplier in suppliers" :key="supplier" :value="supplier">{{ supplier }}</option>
- </select>
- <input
- type="text"
- v-model="searchKeyword"
- class="filter-input"
- placeholder="搜索物料编码/描述/订单号/图号/供应商"
- >
- </div>
- </div>
-
- <!-- 交货计划列表 -->
- <div class="delivery-plan-section">
- <h2>物料交货计划列表</h2>
-
- <div v-if="isLoading" class="loading-indicator">
- <p>数据加载中...</p>
- </div>
-
- <div v-else-if="deliveryPlanData.length === 0" class="placeholder">
- <p>暂无数据,请上传Excel文件或从ERP刷新数据</p>
- </div>
-
- <div v-else class="delivery-plan-table-container">
- <table class="data-table">
- <thead>
- <tr>
- <th>工厂代码</th>
- <th>物料编码</th>
- <th>物料描述</th>
- <th>图号</th>
- <th>版本</th>
- <th>采购订单号</th>
- <th>订单行</th>
- <th>供应商代码</th>
- <th>采购员</th>
- <th>供应类型</th>
- <th>订单数量</th>
- <th>入库数量</th>
- <th>在途数量</th>
- <th>待交数量</th>
- <th>交货数量</th>
- <th>交货日期</th>
- <th>供应商回复数量</th>
- <th>供应商回复交期</th>
- <th>备注</th>
- <th>操作</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="(row, index) in filteredData" :key="index" :class="{ 'modified-row': isRowModified(row) }">
- <td>{{ row.工厂代码 || '-' }}</td>
- <td>{{ row.物料编码 || '-' }}</td>
- <td>{{ row.物料描述 || '-' }}</td>
- <td>{{ row.图号 || '-' }}</td>
- <td>{{ row.版本 || '-' }}</td>
- <td>{{ row.采购订单号 || '-' }}</td>
- <td>{{ row.订单行 || '-' }}</td>
- <td>{{ row.供应商代码 || '-' }}</td>
- <td>{{ row.采购员 || '-' }}</td>
- <td>{{ row.供应类型 || '-' }}</td>
- <td>{{ formatNumber(row.订单数量) }}</td>
- <td>{{ formatNumber(row.入库数量) }}</td>
- <td>{{ formatNumber(row.在途数量) }}</td>
- <td>{{ formatNumber(row.待交数量) }}</td>
- <td>{{ formatNumber(row.交货数量) }}</td>
- <td>{{ formatDate(row.交货日期) }}</td>
- <td>
- <input
- type="number"
- :value="row.供应商回复数量 || ''"
- @input="updateSupplierReplyQuantity(row, $event)"
- class="editable-input supplier-reply-quantity"
- min="0"
- step="1"
- >
- </td>
- <td>
- <input
- type="date"
- v-model="row.供应商回复交期"
- class="editable-input"
- @change="markAsModified(row)"
- >
- </td>
- <td>
- <input
- type="text"
- v-model="row.备注"
- class="editable-input"
- @change="markAsModified(row)"
- >
- </td>
- <td>
- <button class="action-btn save-btn" @click="saveRow(row)">保存</button>
- <button class="action-btn cancel-btn" @click="cancelEdit(row)">取消</button>
- </td>
- </tr>
- </tbody>
- </table>
- <div class="table-footer">
- <p>共 {{ filteredData.length }} 条记录 / 总计 {{ deliveryPlanData.length }} 条</p>
- </div>
- </div>
- </div>
-
- <!-- 统计信息区域 -->
- <div class="stats-section" v-if="deliveryPlanData.length > 0">
- <h2>交货计划统计</h2>
- <div class="stats-cards">
- <div class="stat-card">
- <div class="stat-value">{{ formatNumber(totalOrderQuantity) }}</div>
- <div class="stat-label">总订单数量</div>
- </div>
- <div class="stat-card">
- <div class="stat-value">{{ formatNumber(totalDeliveredQuantity) }}</div>
- <div class="stat-label">已交货数量</div>
- </div>
- <div class="stat-card">
- <div class="stat-value">{{ formatNumber(totalPendingQuantity) }}</div>
- <div class="stat-label">待交货数量</div>
- </div>
- <div class="stat-card">
- <div class="stat-value">{{ formatNumber(totalInTransitQuantity) }}</div>
- <div class="stat-label">在途数量</div>
- </div>
- <div class="stat-card">
- <div class="stat-value">{{ onTimeRate }}%</div>
- <div class="stat-label">按时交货率</div>
- <div class="stat-formula">公式: 按时交货记录数/总记录数×100%</div>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <script>
- const { createApp, ref, reactive, computed } = Vue;
-
- createApp({
- setup() {
- // 响应式数据
- const fileName = ref('');
- const selectedFile = ref(null);
- const deliveryPlanData = ref([]);
- const originalData = ref([]);
- const isLoading = ref(false);
- const hasUnsavedChanges = ref(false);
- const modifiedRows = ref(new Set());
-
- // 过滤和搜索
- const filterFactoryCode = ref('');
- const filterSupplier = ref('');
- const searchKeyword = ref('');
-
- // 计算属性 - 工厂列表
- const factories = computed(() => {
- const uniqueFactories = new Set(deliveryPlanData.value.map(row => row.工厂代码).filter(Boolean));
- return Array.from(uniqueFactories).sort();
- });
-
- // 计算属性 - 供应商列表
- const suppliers = computed(() => {
- const uniqueSuppliers = new Set(deliveryPlanData.value.map(row => row.供应商代码).filter(Boolean));
- return Array.from(uniqueSuppliers).sort();
- });
-
- // 计算属性 - 过滤后的数据
- const filteredData = computed(() => {
- return deliveryPlanData.value.filter(row => {
- // 工厂代码过滤
- if (filterFactoryCode.value && row.工厂代码 !== filterFactoryCode.value) {
- return false;
- }
- // 供应商过滤
- if (filterSupplier.value && row.供应商代码 !== filterSupplier.value) {
- return false;
- }
- // 搜索关键词
- if (searchKeyword.value) {
- const keyword = searchKeyword.value.toLowerCase();
- return (
- (row.物料编码 && row.物料编码.toLowerCase().includes(keyword)) ||
- (row.物料描述 && row.物料描述.toLowerCase().includes(keyword)) ||
- (row.采购订单号 && row.采购订单号.toLowerCase().includes(keyword)) ||
- (row.图号 && row.图号.toLowerCase().includes(keyword)) ||
- (row.供应商代码 && row.供应商代码.toLowerCase().includes(keyword))
- );
- }
- return true;
- });
- });
-
- // 计算属性 - 统计数据
- const totalOrderQuantity = computed(() => {
- return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.订单数量) || 0), 0);
- });
-
- const totalDeliveredQuantity = computed(() => {
- return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.入库数量) || 0), 0);
- });
-
- const totalPendingQuantity = computed(() => {
- return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.待交数量) || 0), 0);
- });
-
- // 计算属性 - 在途数量总计
- const totalInTransitQuantity = computed(() => {
- return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.在途数量) || 0), 0);
- });
-
- const onTimeRate = computed(() => {
- const totalLines = deliveryPlanData.value.length;
- if (totalLines === 0) return 0;
-
- const onTimeLines = deliveryPlanData.value.filter(row => {
- if (!row.供应商回复交期 || !row.交货日期) return false;
- return new Date(row.供应商回复交期) <= new Date(row.交货日期);
- }).length;
-
- return ((onTimeLines / totalLines) * 100).toFixed(2);
- });
-
- // 处理文件上传
- const handleFileUpload = (event) => {
- const file = event.target.files[0];
- if (file) {
- fileName.value = file.name;
- selectedFile.value = file;
- parseExcelFile(file);
- }
- };
-
- // 解析Excel文件
- const parseExcelFile = (file) => {
- isLoading.value = true;
- const reader = new FileReader();
-
- // 添加文件读取进度反馈
- reader.onprogress = function(e) {
- if (e.lengthComputable) {
- const percentComplete = Math.round((e.loaded / e.total) * 100);
- console.log(`文件加载进度: ${percentComplete}%`);
- }
- };
-
- // 处理文件读取错误
- reader.onerror = function() {
- console.error('文件读取失败');
- alert('无法读取文件,请确保文件格式正确且没有被占用。');
- isLoading.value = false;
- };
-
- reader.onload = function(e) {
- try {
- const data = new Uint8Array(e.target.result);
-
- // 增强兼容性配置
- const workbook = XLSX.read(data, {
- type: 'array',
- cellDates: true, // 自动识别日期
- cellText: false, // 保持数值类型
- cellNF: false, // 禁用数字格式
- raw: true // 获取原始值
- });
-
- // 获取第一个工作表
- if (workbook.SheetNames.length === 0) {
- throw new Error('Excel文件中没有找到工作表');
- }
-
- const firstSheetName = workbook.SheetNames[0];
- const worksheet = workbook.Sheets[firstSheetName];
-
- // 转换为JSON格式,增加头部行处理选项
- const jsonData = XLSX.utils.sheet_to_json(worksheet, {
- header: 1, // 先获取原始行数据,处理可能的表头问题
- raw: false // 转换为适当的JavaScript类型
- });
-
- // 处理表头和数据
- if (jsonData.length === 0) {
- throw new Error('Excel文件中没有数据');
- }
-
- // 获取表头行
- const headerRow = jsonData[0];
- // 准备转换为对象数组
- const processedData = [];
-
- // 从第二行开始处理数据
- for (let i = 1; i < jsonData.length; i++) {
- const row = jsonData[i];
- const rowObj = {};
-
- // 遍历表头,将数据映射到对应的字段
- headerRow.forEach((header, index) => {
- if (header && header.trim() !== '') {
- // 转换列索引为字母(如0->A, 1->B, ..., 12->M)
- const colIndex = String.fromCharCode(65 + index);
- // 同时保存原始列名和字母列名,增强兼容性
- rowObj[header] = row[index];
- rowObj[colIndex] = row[index];
- }
- });
-
- if (Object.keys(rowObj).length > 0) {
- processedData.push(rowObj);
- }
- }
-
- // 处理数据,确保所有必需字段存在
- deliveryPlanData.value = processedData.map(row => {
- // 创建行的深拷贝以支持修改检测
- const newRow = JSON.parse(JSON.stringify(row));
-
- // 重命名和转换字段,增加更多别名支持
- const aliases = {
- '物料号': '物料编码',
- '物料编码': '物料编码',
- 'Material': '物料编码',
- 'Material Code': '物料编码',
- '订单行号': '订单行',
- '行号': '订单行',
- 'Line': '订单行',
- '已交数量': '入库数量',
- '已交货数量': '入库数量',
- 'Delivered Qty': '入库数量',
- '供应商回复数量': '供应商回复数量',
- 'Reply Qty': '供应商回复数量',
- '交货日期': '交货日期',
- 'Delivery Date': '交货日期',
- '供应商回复交期': '供应商回复交期',
- 'Reply Date': '供应商回复交期'
- };
-
- // 应用别名映射
- Object.keys(aliases).forEach(alias => {
- if (row[alias] !== undefined && newRow[aliases[alias]] === undefined) {
- newRow[aliases[alias]] = row[alias];
- }
- });
-
- // 确保在途数量从Excel的M列获取,增加更多可能的列名
- if (row['M'] !== undefined) newRow.在途数量 = row['M'];
- if (row['在途数量'] !== undefined) newRow.在途数量 = row['在途数量'];
- if (row['In Transit'] !== undefined) newRow.在途数量 = row['In Transit'];
-
- // 计算待交数量 = 订单数量 - 入库数量 - 在途数量
- const orderQty = parseFloat(newRow.订单数量 || 0);
- const deliveredQty = parseFloat(newRow.入库数量 || 0);
- const inTransitQty = parseFloat(newRow.在途数量 || 0);
- newRow.待交数量 = orderQty - deliveredQty - inTransitQty;
-
- // 确保数字字段是数字类型
- ['订单数量', '入库数量', '在途数量', '待交数量', '交货数量', '供应商回复数量'].forEach(field => {
- if (newRow[field] !== undefined && newRow[field] !== null && newRow[field] !== '') {
- newRow[field] = parseFloat(newRow[field]);
- }
- });
-
- // 处理日期格式,确保与Excel数据一致
- if (newRow.交货日期) {
- newRow.交货日期 = formatDateForStorage(newRow.交货日期);
- }
- if (newRow.供应商回复交期) {
- newRow.供应商回复交期 = formatDateForStorage(newRow.供应商回复交期);
- }
-
- return newRow;
- });
-
- // 保存原始数据用于比较和取消操作
- originalData.value = JSON.parse(JSON.stringify(deliveryPlanData.value));
-
- // 重置状态
- hasUnsavedChanges.value = false;
- modifiedRows.value.clear();
-
- // 显示成功消息
- if (deliveryPlanData.value.length > 0) {
- console.log(`成功解析Excel文件,共${deliveryPlanData.value.length}条数据`);
- } else {
- console.warn('Excel文件已解析,但没有找到有效数据行');
- alert('Excel文件已解析,但没有找到有效数据行,请检查文件格式。');
- }
- } catch (error) {
- console.error('Excel解析错误:', error);
- alert(`解析Excel文件时发生错误: ${error.message}\n\n请确保文件格式正确,并包含必需的数据字段。`);
- deliveryPlanData.value = [];
- } finally {
- isLoading.value = false;
- }
- };
-
- try {
- reader.readAsArrayBuffer(file);
- } catch (error) {
- console.error('文件读取启动失败:', error);
- alert('无法启动文件读取,请稍后重试。');
- isLoading.value = false;
- }
- };
-
- // 从ERP刷新数据(模拟)
- const refreshFromERP = () => {
- isLoading.value = true;
-
- // 模拟API请求延迟
- setTimeout(() => {
- // 这里应该是实际的API调用,现在使用模拟数据
- const mockERPData = generateMockERPData();
-
- deliveryPlanData.value = mockERPData;
- originalData.value = JSON.parse(JSON.stringify(mockERPData));
-
- hasUnsavedChanges.value = false;
- modifiedRows.value.clear();
- fileName.value = '';
- selectedFile.value = null;
-
- isLoading.value = false;
- }, 1000);
- };
-
- // 生成模拟ERP数据
- const generateMockERPData = () => {
- const factories = ['F001', 'F002', 'F003'];
- const materials = [
- { id: 'M001', desc: '电子元件A', drawing: 'DWG001', version: 'V1.0' },
- { id: 'M002', desc: '机械零件B', drawing: 'DWG002', version: 'V2.1' },
- { id: 'M003', desc: '包装材料C', drawing: 'DWG003', version: 'V1.5' },
- { id: 'M004', desc: '原材料D', drawing: 'DWG004', version: 'V3.0' },
- { id: 'M005', desc: '配件E', drawing: 'DWG005', version: 'V2.2' }
- ];
- const buyers = ['张三', '李四', '王五', '赵六'];
- const supplyTypes = ['常规采购', '紧急采购', '寄售', 'VMI'];
-
- const mockData = [];
-
- for (let i = 1; i <= 20; i++) {
- const factory = factories[Math.floor(Math.random() * factories.length)];
- const material = materials[Math.floor(Math.random() * materials.length)];
- const orderQuantity = Math.floor(Math.random() * 1000) + 100;
- const deliveredQuantity = Math.floor(Math.random() * orderQuantity);
- const inTransitQuantity = Math.floor(Math.random() * (orderQuantity - deliveredQuantity));
- const pendingQuantity = orderQuantity - deliveredQuantity - inTransitQuantity; // 待交数量=订单数量-入库数量-在途数量
- const deliveryQuantity = Math.floor(Math.random() * pendingQuantity) + 1;
- const buyer = buyers[Math.floor(Math.random() * buyers.length)];
- const supplyType = supplyTypes[Math.floor(Math.random() * supplyTypes.length)];
-
- // 生成交货日期(未来30天内)
- const deliveryDate = new Date();
- deliveryDate.setDate(deliveryDate.getDate() + Math.floor(Math.random() * 30) + 1);
-
- mockData.push({
- 工厂代码: factory,
- 物料编码: material.id,
- 物料描述: material.desc,
- 图号: material.drawing,
- 版本: material.version,
- 采购订单号: `PO${factory}-${String(i).padStart(5, '0')}`,
- 订单行: Math.floor(Math.random() * 10) + 1,
- 供应商代码: `S${String(Math.floor(Math.random() * 100) + 1).padStart(3, '0')}`,
- 采购员: buyer,
- 供应类型: supplyType,
- 订单数量: orderQuantity,
- 入库数量: deliveredQuantity,
- 在途数量: inTransitQuantity,
- 待交数量: pendingQuantity,
- 交货数量: deliveryQuantity,
- 交货日期: deliveryDate.toISOString().split('T')[0],
- 供应商回复数量: null,
- 供应商回复交期: null,
- 备注: '',
- id: `ROW-${i}`
- });
- }
-
- return mockData;
- };
-
- // 更新供应商回复数量
- const updateSupplierReplyQuantity = (row, event) => {
- const value = event.target.value;
- // 允许空字符串或数字
- if (value === '' || (value !== '' && !isNaN(parseFloat(value)))) {
- row.供应商回复数量 = value === '' ? null : parseFloat(value);
- markAsModified(row);
- } else {
- // 如果输入的不是有效数字,清空输入
- event.target.value = '';
- }
- };
- // 标记行为已修改
- const markAsModified = (row) => {
- modifiedRows.value.add(row.id || row.采购订单号 + '-' + row.订单行号);
- hasUnsavedChanges.value = true;
- };
-
- // 检查行是否已修改
- const isRowModified = (row) => {
- return modifiedRows.value.has(row.id || row.采购订单号 + '-' + row.订单行号);
- };
-
- // 保存单行修改
- const saveRow = (row) => {
- const rowKey = row.id || row.采购订单号 + '-' + row.订单行号;
-
- // 这里应该是实际的API调用,现在仅更新状态
- console.log('保存行修改:', row);
-
- // 在实际应用中,这里会调用API保存数据
- // 保存成功后更新原始数据
- const originalRowIndex = originalData.value.findIndex(
- r => (r.id || r.采购订单号 + '-' + r.订单行号) === rowKey
- );
-
- if (originalRowIndex !== -1) {
- originalData.value[originalRowIndex] = JSON.parse(JSON.stringify(row));
- }
-
- modifiedRows.value.delete(rowKey);
- hasUnsavedChanges.value = modifiedRows.value.size > 0;
- };
-
- // 取消编辑
- const cancelEdit = (row) => {
- const rowKey = row.id || row.采购订单号 + '-' + row.订单行号;
-
- // 查找原始数据
- const originalRow = originalData.value.find(
- r => (r.id || r.采购订单号 + '-' + r.订单行号) === rowKey
- );
-
- if (originalRow) {
- // 恢复原始数据
- Object.keys(originalRow).forEach(key => {
- row[key] = originalRow[key];
- });
- }
-
- modifiedRows.value.delete(rowKey);
- hasUnsavedChanges.value = modifiedRows.value.size > 0;
- };
-
- // 保存所有修改
- const saveChanges = () => {
- // 获取所有修改过的行
- const modifiedData = deliveryPlanData.value.filter(row =>
- isRowModified(row)
- );
-
- if (modifiedData.length === 0) {
- alert('没有需要保存的修改');
- return;
- }
-
- // 这里应该是实际的API调用,现在仅显示信息
- console.log('保存所有修改:', modifiedData);
-
- // 保存成功后更新原始数据
- modifiedData.forEach(row => {
- const rowKey = row.id || row.采购订单号 + '-' + row.订单行号;
- const originalRowIndex = originalData.value.findIndex(
- r => (r.id || r.采购订单号 + '-' + r.订单行号) === rowKey
- );
-
- if (originalRowIndex !== -1) {
- originalData.value[originalRowIndex] = JSON.parse(JSON.stringify(row));
- }
-
- modifiedRows.value.delete(rowKey);
- });
-
- hasUnsavedChanges.value = false;
- alert('修改已成功保存');
- };
-
- // 格式化数字
- const formatNumber = (num) => {
- if (num === null || num === undefined || isNaN(num)) return '-';
- return parseFloat(num).toLocaleString('zh-CN');
- };
-
- // 格式化日期显示
- const formatDate = (dateString) => {
- if (!dateString) return '-';
-
- try {
- let date;
-
- // 检查是否是Excel日期数值(从1900年1月1日开始的天数)
- if (!isNaN(dateString) && typeof dateString === 'number') {
- // Excel从1900年1月1日开始,但JavaScript从1970年1月1日开始
- // 计算从1900-01-01到1970-01-01的毫秒数,加上Excel天数的毫秒数
- const excelEpoch = new Date(1900, 0, 1).getTime();
- const msPerDay = 24 * 60 * 60 * 1000;
- // 注意:Excel错误地认为1900年是闰年,所以需要减去1天
- const correctedDays = dateString < 60 ? dateString - 1 : dateString - 2;
- date = new Date(excelEpoch + correctedDays * msPerDay);
- } else if (typeof dateString === 'string') {
- // 处理YYYY-MM-DD格式
- if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
- return dateString;
- }
- // 处理其他字符串格式
- date = new Date(dateString);
- } else {
- // 尝试直接使用
- date = new Date(dateString);
- }
-
- // 检查日期是否有效
- if (isNaN(date.getTime())) return dateString; // 如果无法解析,返回原始值
-
- // 格式化日期为YYYY-MM-DD格式
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
-
- return `${year}-${month}-${day}`;
- } catch (e) {
- console.error('日期格式化错误:', e);
- return dateString; // 如果出错,返回原始值
- }
- };
-
- // 格式化日期为存储格式(确保与Excel数据一致)
- const formatDateForStorage = (dateString) => {
- if (!dateString) return null;
-
- try {
- // 尝试直接解析日期字符串
- let date;
-
- // 如果已经是YYYY-MM-DD格式,直接返回
- if (typeof dateString === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
- return dateString;
- }
-
- // 检查是否是Excel日期数值(从1900年1月1日开始的天数)
- if (!isNaN(dateString) && typeof dateString === 'number') {
- // Excel从1900年1月1日开始,但JavaScript从1970年1月1日开始
- // 计算从1900-01-01到1970-01-01的毫秒数,加上Excel天数的毫秒数
- const excelEpoch = new Date(1900, 0, 1).getTime();
- const msPerDay = 24 * 60 * 60 * 1000;
- // 注意:Excel错误地认为1900年是闰年,所以需要减去1天
- const correctedDays = dateString < 60 ? dateString - 1 : dateString - 2;
- date = new Date(excelEpoch + correctedDays * msPerDay);
- } else if (typeof dateString === 'string') {
- // 处理ISO格式日期
- if (dateString.includes('T')) {
- date = new Date(dateString);
- } else {
- // 处理其他字符串格式
- date = new Date(dateString);
- }
- } else {
- // 尝试直接使用
- date = new Date(dateString);
- }
-
- // 检查日期是否有效
- if (isNaN(date.getTime())) return dateString; // 如果无法解析,返回原始值
-
- // 格式化日期为YYYY-MM-DD格式
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
-
- return `${year}-${month}-${day}`;
- } catch (e) {
- console.error('日期存储格式化错误:', e);
- return dateString; // 如果出错,返回原始值
- }
- };
-
- return {
- fileName,
- selectedFile,
- deliveryPlanData,
- isLoading,
- hasUnsavedChanges,
- filterFactoryCode,
- filterSupplier,
- searchKeyword,
- factories,
- suppliers,
- filteredData,
- totalOrderQuantity,
- totalDeliveredQuantity,
- totalPendingQuantity,
- totalInTransitQuantity,
- onTimeRate,
- handleFileUpload,
- refreshFromERP,
- saveChanges,
- markAsModified,
- isRowModified,
- saveRow,
- cancelEdit,
- formatNumber,
- formatDate,
- updateSupplierReplyQuantity,
- formatDateForStorage
- };
- }
- }).mount('#app');
- </script>
- </body>
- </html>
|