Commit 2bdbe81c authored by 刘勇's avatar 刘勇

init

parents
Pipeline #447 failed with stages
> 1%
last 2 versions
not dead
module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint",
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
};
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
app
app.zip
api/config.js
.vscode
.env.development
debug.log
jsconfig.json
# excel-analysis
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "excel-analysis",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "npm run dev",
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"element-ui": "^2.15.3",
"stylus-loader": "^3.0.2",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0",
"xlsx": "^0.17.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"css-loader": "^5.2.6",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^2.2.1",
"style-loader": "^3.0.0",
"stylus": "^0.54.8",
"vue-template-compiler": "^2.6.11"
}
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<div id="app">
<router-view />
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
list-style: none;
font-size: 16px;
}
</style>
import consoleDev from '@/utils/console-dev';
const defaultConfig = {
devTime: '2021.6.29', // 开发时间
devVersion: `1.0`, // 开发版本
devBranch: 'master', // 开发分支
}
consoleDev(defaultConfig.devTime, defaultConfig.devVersion, defaultConfig.devBranch)
\ No newline at end of file
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
import Vue from "vue";
import VueRouter from "vue-router";
import globalConfig from '@/config/global-config'
Vue.use(VueRouter);
const routes = [
{
path: '/',
redirect: '/excel',
},
{
path: "/excel",
name: "Excel",
component: () => import('@/views/excel/excel.vue')
},
];
const router = new VueRouter({
routes,
});
export default router;
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {},
});
import util from './util';
const consoleConfig = [
{
key: "dev", // 注册key值
label: "devLog", // 标识
labelColor: "#00f", // 标识颜色
logColor: "#666", // 日志颜色
},
]
const dateColor = '#f0f';
const consoleDev = (time, version, branch) => {
const typeofBase = data => typeof data == 'string' || typeof data == 'number';
console.branch = () => {
let arr = [`%c**** branch => ${branch} ****`,`color:${dateColor}`];
console.log(...arr)
};
console.date = () => {
let arr = [`%c**** devTime => ${time} v${version} ****`,`color:${dateColor}`];
console.log(...arr)
};
console.buildtime = () => {
let meta = document.querySelector('meta[name="buildtime"]');
let buildTime = meta ? meta.getAttribute('date') : '';
let arr = [`%c**** buildTime => ${buildTime} ****`,`color:${dateColor}`];
console.log(...arr)
}
console.detail = () => {
console.branch();
console.date();
console.buildtime();
}
console.json = (...rest) => console.dev(...rest.map(item => typeofBase(item) ? item : util.deepCopy2(item)));
// 自动复制
console.copy = str => {
if(str) return util.copy(str);
// copy串联方法
let copyEvent = {
href: (token=true) => { // 复制地址 参数为是否带token 默认为true
let href = location.href;
let query = '';
if(token){
let hasToken = href.indexOf('token' > -1);
if(!hasToken){
let hasQuery = href.indexOf('?') > -1;
query = hasQuery ? '&' : '?';
query = query + 'token=' + store.state.login.token
}
}
let result = location.href + query;
return util.copy(result);
},
local: (key) => { // 复制本地存储项
let result = window.localStorage.getItem(key);
return util.copy(result);
},
session: (key) => { // 复制本地存储项
let result = window.sessionStorage.getItem(key);
return util.copy(result);
},
}
return copyEvent;
};
const generateConsole = ({key, label, labelColor, logColor}) => {
label ? null : label = defaultConfig.label;
labelColor ? null : labelColor = defaultConfig.labelColor;
logColor ? null : logColor = defaultConfig.logColor;
console[key] = (...rest) => {
if(!rest.length){
console.detail();
}else{
let argStr = [];
let argOther = [];
rest.forEach(item => {
item == undefined ? item = 'undefined' : null;
return typeofBase(item)
? argStr.push(item)
: argOther.push(item);
});
let join = argStr.join(' ')
let params = [];
if(join){
params.push(`%c${label} => %c${join}`);
params.push(`color:${labelColor}`);
params.push(`color:${logColor}`);
}else{
params.push(`%c${label} =>`);
params.push(`color:${labelColor}`);
}
params = params.concat(argOther);
params.length && console.log(...params);
}
}
}
consoleConfig.map(conf => generateConsole(conf))
}
export default consoleDev;
export default {
copy(str){
var oInput = document.createElement('input');
oInput.value = str;
document.body.appendChild(oInput);
oInput.select()
document.execCommand("Copy");
document.body.removeChild(oInput)
return str;
},
deepCopy: (p, c) => {
c = c
? c
: Array.isArray(p)
? [] : {};
for (let i in p) {
if (typeof p[i] === 'object' && p[i] != null) {
c[i] = (p[i] instanceof Array) ? [] : {}
util.deepCopy(p[i], c[i])
} else {
c[i] = p[i] === null ? '' : p[i]
}
}
return c
},
deepCopy2: item => JSON.parse(JSON.stringify(item)),
}
\ No newline at end of file
<template>
<div class="excel-container">
<el-row class="search-container" :gutter="20">
<el-col :span="6">
<div class="label">筛选小组:</div>
<el-select v-model="analysisType" placeholder="请选择" @change="selectGroup">
<el-option
v-for="item in group"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
<el-col :span="12">
<div class="label">筛选人员:</div>
<el-input v-model="searchList" placeholder="筛选人员"></el-input>
</el-col>
<el-col :span="6">
<el-button size="small" type="primary" @click="readExcel">选择表格</el-button>
</el-col>
</el-row>
<input type="file" ref="elFile" v-show="false" @change="uploadFile">
<div class="preview-excel">
<!-- 条形图数据 -->
<el-card class="box-card">
<div slot="header" class="content-header">
<span>条形图</span>
</div>
<div class="content-inner">
<el-table
:data="barChart"
stripe
style="width: 100%"
max-height="300"
>
<el-table-column
prop="name"
label="姓名"
>
</el-table-column>
<el-table-column
prop="mailFinishedTime"
label="P0P1已完成工时"
sortable
>
</el-table-column>
<el-table-column
prop="mainTotalTime"
label="P0P1总工时"
sortable
>
</el-table-column>
<el-table-column
prop="mainPercent"
label="P0P1工时完成率"
sortable
:sort-method="mainSortChange"
>
<template slot-scope="scope">
<span>{{ scope.row.mainPercent }}%</span>
</template>
</el-table-column>
<el-table-column
prop="totalFinishedTime"
label="已完成工时"
sortable
>
</el-table-column>
<el-table-column
prop="totalTime"
label="总工时"
sortable
>
</el-table-column>
<el-table-column
prop="percent"
label="总工时完成率"
sortable
:sort-method="sortChange"
>
<template slot-scope="scope">
<span>{{ scope.row.percent }}%</span>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
<!-- P0任务完成情况 -->
<el-card class="box-card">
<div slot="header" class="content-header">
<span>小组任务完成情况</span>
</div>
<div class="content-inner">
<el-table
:data="mainTaskStatus"
style="width: 100%">
<el-table-column
prop="finish"
label="已完成P0P1任务"
>
</el-table-column>
<el-table-column
prop="unfinished"
label="未完成P0P1任务"
>
</el-table-column>
<el-table-column
prop="total"
label="P0P1总任务">
</el-table-column>
<el-table-column
prop="totalFinish"
label="已完成总任务"
>
</el-table-column>
<el-table-column
prop="totalUnfinished"
label="未完成总任务"
>
</el-table-column>
<el-table-column
prop="totalQuantity"
label="总任务">
</el-table-column>
<el-table-column
prop="percentageComplete"
label="所有任务完成率">
</el-table-column>
<el-table-column
prop="average"
label="平均任务完成率">
</el-table-column>
</el-table>
</div>
</el-card>
</div>
</div>
</template>
<script>
import XLSX from 'xlsx'
export default {
name: "Excel",
data() {
return {
group: [ // 小组
{label: 'Web前端', value: 'Web'},
{label: 'Java', value: 'Java'},
],
before: {},
listTable: [],
analysisType: 'Web', // 选中小组 Web-前端 Java-后端
webList: ['李昊坤', '董梦佳', '曹鹏威', '吴玉鹏', '郑江艺', '陈晓飞', '安铁亮', '张高宾', '徐俊菲', '许文会', '郝继鹏', '刘勇', '刘子锋', '陈艺哲'],
searchList: '', // 筛选人员
demand: {}, // 当前遍历需求数据
sheet1: { // 表格1
filter: {}, // 表格原数据
taskData: {}, // 任务数据
taskTotalData: {}, // 任务总数据
},
barChart: [], // 条形图数据
mainTaskStatus: [], // P0任务完成情况
percentageComplete: 0, // 所有任务完成率
average: {}, // 平均任务完成率
};
},
created(){
this.selectGroup();
},
methods: {
sortChange(a, b){
return Number(a.percent) - Number(b.percent);
},
mainSortChange(a, b){
return Number(a.mainPercent) - Number(b.mainPercent);
},
selectGroup(){
if(this.analysisType == 'Web'){
this.searchList = this.webList.join(',');
} else {
this.searchList = '';
}
},
readExcel(){
let el = this.$refs.elFile;
el.click();
},
filterColData(itemData){ // 筛选字段
let obj = {};
for (const key in itemData) {
if(key == '开发'){
obj['开发人'] = itemData[key];
continue;
}
if(key.indexOf('优先级') > -1){
obj['优先级'] = itemData[key];
continue;
}
if(key.indexOf('任务') > -1 && key.indexOf('编号') > -1){
obj['任务编号'] = itemData[key];
continue;
}
if(key.indexOf('开发任务名称') > -1){
obj['开发任务名称'] = itemData[key];
continue;
}
if(key.indexOf('任务') > -1 && key.indexOf('状态') > -1){
obj['任务状态'] = itemData[key];
continue;
}
if(key.indexOf('需求编号') > -1){
obj['需求编号'] = itemData[key];
continue;
}
if(key.indexOf('需求名称') > -1){
obj['需求名称'] = itemData[key];
continue;
}
if(key.indexOf('预计工时') > -1){
obj['预计工时'] = itemData[key];
continue;
}
}
if(!obj['任务状态']) obj['任务状态'] = '未完成';
return obj;
},
checkSheetItem(sheetArray, sheetName, isCurrentMonth){ // isCurrentMonth为true表示本月数据
let sheetData = {};
sheetData.name = sheetName;
sheetData.data = [];
sheetData.developerData = {};
let {data:sheetArr, developerData} = sheetData;
sheetArray.map(item => {
if(item['需求编号']){
this.demand = item;
}
if(typeof this.demand['需求编号'] != 'number'){
return;
}
let developer = item['开发'];
let filterDeveloper = this.searchList.split(',');
if(!filterDeveloper.includes(developer)) { // 筛选开发人
return;
}
if(!developerData[developer]){
developerData[developer] = []
}
let itemData = Object.assign({}, this.demand, item);
itemData = this.filterColData(itemData); // 筛选字段
let valid = this.validData(itemData, isCurrentMonth)
if(valid){
developerData[developer].push(itemData);
sheetArr.push(itemData);
}
});
return sheetData;
},
validData(itemData, isCurrentMonth){ // 与上月数据进行对比,筛选掉上个月已完成的数据
let valid = true;
if(isCurrentMonth){
let beforeListTable = this.listTable[1];
if(beforeListTable && itemData['任务状态'] == '已完成'){
let beforeData = beforeListTable.developerData[itemData['开发人']];
beforeData.find(beforeItem => {
if(beforeItem['任务编号'] == itemData['任务编号']){
if(beforeItem['任务状态'] == '已完成' && itemData['任务状态'] == '已完成') {
valid = false;
}
return true;
}
})
}
}
return valid;
},
//解析excel
async uploadFile(e) {
const _file = e.target.files[0];
const fileReader = new FileReader();
fileReader.onload = (ev) => {
try {
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: 'binary'
});
this.analyzeSheet(workbook, 1);
this.analyzeSheet(workbook, 0);
this.analyzeData();
let elFile = this.$refs.elFile;
elFile.value = '';
} catch (e) {
console.error(e);
this.$message.warning('文件类型不正确!');
}
};
fileReader.readAsBinaryString(_file);
},
analyzeSheet(workbook, idx){ // idx为1表示上个月数据,0表示本月数据
let sheetName = Object.keys(workbook.Sheets)[idx];
if(idx == 1 && !sheetName) { // 上个月数据
return;
}
//循环读取每个文件
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);
let isCurrentMonth = idx == 0;
debugger
let sheetData = this.checkSheetItem(sheetArray, sheetName, isCurrentMonth);
this.listTable[idx] = sheetData;
},
analyzeData(){ // 分析数据
let sheet1 = this.listTable[0]; // 本月数据
let sheet1Filter = {};
let taskData = {};
let taskTotalData = {
task: {finish: 0, unfinished: 0, finishTime: 0, unfinishedTime: 0}, // 所有任务数据
mainTask: {finish: 0, unfinished: 0, finishTime: 0, unfinishedTime: 0}, // 主要任务数据
commonTask: {finish: 0, unfinished: 0, finishTime: 0, unfinishedTime: 0}, // 普通任务数据
};
for (const developer in sheet1.developerData) {
let item = sheet1.developerData[developer];
sheet1Filter[developer] = item;
taskData[developer] = {
task: {}, // 任务数据
mainTask: {finish: 0, unfinished: 0, finishTime: 0, unfinishedTime: 0}, // 主要任务数据
commonTask: {finish: 0, unfinished: 0, finishTime: 0, unfinishedTime: 0}, // 普通任务数据
totalFinished: 0, // 总完成任务数
totalFinishedTime: 0, // 总完成任务时间
totalUnFinished: 0, // 总未完成任务数
totalUnFinishedTime: 0, // 总未完成任务时间
};
let developerTask = taskData[developer];
item.map(task => {
if(!developerTask.task[task['优先级']]) developerTask.task[task['优先级']] = {finish: 0, unfinished: 0, finishTime: 0, unfinishedTime: 0};
let status = developerTask.task[task['优先级']];
let time = task['预计工时'] || 0
if(task['任务状态'] == '已完成') {
status.finish++;
status.finishTime += time;
developerTask.totalFinished++;
developerTask.totalFinishedTime += time;
taskTotalData.task.finish++;
taskTotalData.task.finishTime += time;
if(task['优先级'] == 'P0' || task['优先级'] == 'P1'){
developerTask.mainTask.finish++;
developerTask.mainTask.finishTime += time;
taskTotalData.mainTask.finish++;
taskTotalData.mainTask.finishTime += time;
} else {
developerTask.commonTask.finish++;
developerTask.commonTask.finishTime += time;
taskTotalData.commonTask.finish++;
taskTotalData.commonTask.finishTime += time;
}
} else {
status.unfinished++;
status.unfinishedTime += time;
developerTask.totalUnFinished++;
developerTask.totalUnFinishedTime += time;
taskTotalData.task.unfinished++;
taskTotalData.task.unfinishedTime += time;
if(task['优先级'] == 'P0' || task['优先级'] == 'P1'){
developerTask.mainTask.unfinished++;
developerTask.mainTask.unfinishedTime += time;
taskTotalData.mainTask.unfinished++;
taskTotalData.mainTask.unfinishedTime += time;
} else {
developerTask.commonTask.unfinished++;
developerTask.commonTask.unfinishedTime += time;
taskTotalData.commonTask.unfinished++;
taskTotalData.commonTask.unfinishedTime += time;
}
}
})
}
// let taskDataMap = {};
// let filterDeveloper = this.searchList.split(',');
// filterDeveloper.map(key => { // 排序
// taskDataMap[key] = taskData[key];
// })
this.sheet1.filter = sheet1Filter;
this.sheet1.taskData = taskData;
this.sheet1.taskTotalData = taskTotalData;
this.initEcharts();
},
initEcharts(){
console.json('this.sheet1', this.sheet1)
let {taskData, taskTotalData} = this.sheet1;
// 条形图
let barChart = [];
Object.keys(taskData).map(key => {
let {mainTask, totalFinishedTime, totalUnFinishedTime} = taskData[key];
let obj = {};
obj.name = key;
barChart.push(obj);
obj.totalTime = totalFinishedTime + totalUnFinishedTime;
obj.totalFinishedTime = totalFinishedTime;
obj.percent = obj.totalTime ? (obj.totalFinishedTime / obj.totalTime * 100).toFixed(2) : 100;
obj.mainTotalTime = mainTask.finishTime + mainTask.unfinishedTime;
obj.mailFinishedTime = mainTask.finishTime;
obj.mainPercent = obj.mainTotalTime ? (obj.mailFinishedTime / obj.mainTotalTime * 100).toFixed(2) : 100;
})
this.barChart = barChart;
// P0任务完成情况
let mainTaskStatus = {};
let {mainTask, task} = taskTotalData;
mainTaskStatus.finish = mainTask.finish;
mainTaskStatus.unfinished = mainTask.unfinished;
mainTaskStatus.total = mainTask.finish + mainTask.unfinished;
mainTaskStatus.totalFinish = task.finish;
mainTaskStatus.totalUnfinished = task.unfinished;
mainTaskStatus.totalQuantity = task.finish + task.unfinished;
// 所有任务完成率
let add = task.finish + task.unfinished;
let percentageComplete = add ? task.finish / add * 100 : 100;
percentageComplete = percentageComplete.toFixed(2);
this.percentageComplete = percentageComplete
mainTaskStatus.percentageComplete = percentageComplete + '%';
// 平均任务完成率
let average = {
percentageComplete: 0,
total: 0,
num: 0,
}
Object.keys(taskData).map(key => {
average.num++;
let item = taskData[key];
let add = item.totalFinished + item.totalUnFinished
let percent = add ? item.totalFinished / add * 100 : 100;
average.total += percent;
})
average.percentageComplete = (average.total / average.num).toFixed(2);
this.average = average;
mainTaskStatus.average = average.percentageComplete + '%';
this.mainTaskStatus = [mainTaskStatus];
},
}
};
</script>
<style lang="stylus" scope>
.excel-container{
padding: 20px;
.search-container {
margin-top: 10px;
.el-col {
height: 40px;
display: flex;
align-items: center;
.label {
flex-shrink: 0;
}
}
}
.box-card{
margin-top: 20px;
}
}
</style>
\ No newline at end of file
const dev = process.env.NODE_ENV !== 'production';
module.exports = {
publicPath: dev ? './' : '/',
devServer: {
port: 8091,
host: 'localhost',
https: false,
open: true,
// proxy: {
// target: '',
// changeOrigin: true,
// pathRewrite: {
// '^/': ''
// },
// },
},
configureWebpack: {
name: 'Excel数据分析',
module: {
rules: [
// {
// test: '\.styl$',
// loader: 'style-loader!css-loader!stylus-loader'
// }
]
},
},
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title= 'Excel数据分析'
return args
})
},
lintOnSave:false,
productionSourceMap: false,
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment