| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>供应商交货计划分析 (Vue版)</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="analyze-btn"
- class="analyze-btn"
- :disabled="!fileName || isAnalyzing"
- @click="analyzeFile"
- >
- {{ isAnalyzing ? '分析中...' : '开始分析' }}
- </button>
- </div>
-
- <!-- 分析结果区域 -->
- <div class="analysis-section" v-if="fieldStats.length > 0">
- <h2>字段分析结果</h2>
- <div class="field-analysis">
- <div class="field-card" v-for="field in fieldStats" :key="field.fieldName">
- <h3>{{ field.fieldName }}</h3>
- <div class="field-details">
- <div class="field-detail">数据类型: {{ field.dataType }}</div>
- <div class="field-detail">总记录数: {{ field.totalCount }}</div>
- <div class="field-detail">非空值数: {{ field.nonEmptyCount }} ({{ field.nonEmptyPercentage.toFixed(2) }}%)</div>
- <div class="field-detail">空值数: {{ field.emptyCount }} ({{ field.emptyPercentage.toFixed(2) }}%)</div>
- <div class="field-detail">唯一值数量: {{ field.uniqueValues }}</div>
-
- <!-- 数值类型的统计信息 -->
- <div v-if="field.min !== undefined" class="field-detail">最小值: {{ field.min }}</div>
- <div v-if="field.max !== undefined" class="field-detail">最大值: {{ field.max }}</div>
- <div v-if="field.average !== undefined" class="field-detail">平均值: {{ field.average.toFixed(2) }}</div>
-
- <!-- 样本值 -->
- <div v-if="field.sampleValues && field.sampleValues.length > 0" class="field-detail">
- 样本值: {{ field.sampleValues.join(', ') }}
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 数据预览区域 -->
- <div class="preview-section" v-if="dataPreview.length > 0">
- <h2>数据预览</h2>
- <div class="data-preview">
- <table class="data-table">
- <thead>
- <tr>
- <th v-for="header in headers" :key="header">{{ header }}</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="(row, index) in dataPreview" :key="index">
- <td v-for="header in headers" :key="header">{{ row[header] || '' }}</td>
- </tr>
- </tbody>
- </table>
- <p v-if="totalRows > dataPreview.length" class="placeholder">
- 仅显示前{{ dataPreview.length }}行数据,共 {{ totalRows }} 行
- </p>
- </div>
- </div>
-
- <!-- 初始提示 -->
- <div v-if="!fileName && fieldStats.length === 0" class="placeholder">
- <p>请上传Excel文件以分析供应商交货计划数据</p>
- </div>
- </div>
- </div>
-
- <script>
- const { createApp, ref, reactive, computed } = Vue;
-
- createApp({
- setup() {
- // 响应式数据
- const fileName = ref('');
- const selectedFile = ref(null);
- const rawData = ref([]);
- const fieldStats = ref([]);
- const dataPreview = ref([]);
- const headers = ref([]);
- const totalRows = ref(0);
- const isAnalyzing = ref(false);
-
- // 特定日期字段列表
- const DATE_FIELDS = ['交货日期', '交期回复'];
-
- // 处理文件上传
- const handleFileUpload = (event) => {
- const file = event.target.files[0];
- if (file) {
- fileName.value = file.name;
- selectedFile.value = file;
- // 重置之前的分析结果
- fieldStats.value = [];
- dataPreview.value = [];
- rawData.value = [];
- headers.value = [];
- totalRows.value = 0;
- }
- };
-
- // 分析文件
- const analyzeFile = () => {
- if (selectedFile.value) {
- isAnalyzing.value = true;
- parseExcelFile(selectedFile.value);
- }
- };
-
- // 解析Excel文件
- const parseExcelFile = (file) => {
- const reader = new FileReader();
-
- reader.onload = function(e) {
- const data = new Uint8Array(e.target.result);
- const workbook = XLSX.read(data, { type: 'array' });
-
- // 获取第一个工作表
- const firstSheetName = workbook.SheetNames[0];
- const worksheet = workbook.Sheets[firstSheetName];
-
- // 转换为JSON格式
- rawData.value = XLSX.utils.sheet_to_json(worksheet);
- totalRows.value = rawData.value.length;
-
- // 分析字段
- analyzeFields();
-
- // 显示数据预览
- showDataPreview();
-
- // 分析完成,更新状态
- isAnalyzing.value = false;
- };
-
- reader.readAsArrayBuffer(file);
- };
-
- // 获取数据类型
- const getDataType = (value, fieldName = '') => {
- if (value === null || value === undefined) return 'unknown';
-
- // 对于特定的日期字段,强制识别为日期类型
- if (DATE_FIELDS.includes(fieldName)) {
- // 尝试各种日期格式解析
- const dateFormats = [
- value, // 原值
- value.replace(/\//g, '-'), // 将/替换为-(例如2023/05/20 -> 2023-05-20)
- value.replace(/\./g, '-') // 将.替换为-(例如2023.05.20 -> 2023-05-20)
- ];
-
- for (const format of dateFormats) {
- const date = new Date(format);
- if (!isNaN(date.getTime()) && date.toISOString() !== '0001-01-01T00:00:00.000Z' && date.getFullYear() > 1900) {
- return 'date';
- }
- }
- }
-
- // 尝试检查是否为日期
- if (typeof value === 'string') {
- const date = new Date(value);
- if (!isNaN(date.getTime()) && date.toISOString() !== '0001-01-01T00:00:00.000Z' && date.getFullYear() > 1900) {
- return 'date';
- }
- }
-
- // 检查是否为数字
- if (typeof value === 'number' || !isNaN(parseFloat(value)) && isFinite(value)) {
- return 'number';
- }
-
- // 检查是否为布尔值
- if (typeof value === 'boolean' || value === 'true' || value === 'false') {
- return 'boolean';
- }
-
- return 'string';
- };
-
- // 分析单个字段的统计信息
- const analyzeField = (fieldName) => {
- const values = rawData.value
- .map(row => row[fieldName])
- .filter(val => val !== undefined && val !== null && val !== '');
-
- const stats = {
- fieldName,
- totalCount: rawData.value.length,
- nonEmptyCount: values.length,
- emptyCount: rawData.value.length - values.length,
- dataType: values.length > 0 ? getDataType(values[0], fieldName) : 'unknown',
- uniqueValues: [...new Set(values)].length,
- sampleValues: values.slice(0, 5), // 显示前5个样本值
- nonEmptyPercentage: 0,
- emptyPercentage: 0
- };
-
- // 计算百分比
- stats.nonEmptyPercentage = (stats.nonEmptyCount / stats.totalCount) * 100;
- stats.emptyPercentage = (stats.emptyCount / stats.totalCount) * 100;
-
- // 如果是数值类型,计算额外的统计信息
- if (stats.dataType === 'number' || stats.dataType === 'date') {
- const numericValues = values.map(val => {
- if (stats.dataType === 'date') {
- // 处理日期类型
- return new Date(val).getTime();
- }
- return parseFloat(val);
- }).filter(val => !isNaN(val));
-
- if (numericValues.length > 0) {
- stats.min = Math.min(...numericValues);
- stats.max = Math.max(...numericValues);
- stats.average = numericValues.reduce((sum, val) => sum + val, 0) / numericValues.length;
-
- // 如果是日期类型,格式化最小和最大值
- if (stats.dataType === 'date') {
- stats.min = new Date(stats.min).toLocaleDateString();
- stats.max = new Date(stats.max).toLocaleDateString();
- }
- }
- }
-
- return stats;
- };
-
- // 分析所有字段
- const analyzeFields = () => {
- if (!rawData.value || rawData.value.length === 0) {
- fieldStats.value = [];
- return;
- }
-
- // 获取所有字段名
- headers.value = Object.keys(rawData.value[0]);
-
- // 分析每个字段
- fieldStats.value = headers.value.map(field => analyzeField(field));
- };
-
- // 显示数据预览
- const showDataPreview = () => {
- if (!rawData.value || rawData.value.length === 0) {
- dataPreview.value = [];
- return;
- }
-
- // 只显示前10行数据
- dataPreview.value = rawData.value.slice(0, 10);
- };
-
- return {
- fileName,
- selectedFile,
- fieldStats,
- dataPreview,
- headers,
- totalRows,
- isAnalyzing,
- handleFileUpload,
- analyzeFile
- };
- }
- }).mount('#app');
- </script>
- </body>
- </html>
|