소스 검색

simple form validation added

Patrick Bozic 4 년 전
부모
커밋
be9597350d

+ 50 - 0
input-output-requirements.txt

@@ -0,0 +1,50 @@
+个人简要统计 Personal Collected:
+
+Input: 月 Month, 姓名 Employee Realname
+
+Output Data:
+日期 Date (sort ASC),
+消耗时间(单位:小时) Time Consumed,
+部门名称 Department Name,
+账号 Employee Account,
+姓名 Employee Realname
+
+============================================
+
+个人详情统计 Personal Details:
+
+Input: 月 Month, 姓名 Employee Realname
+
+Output Data:
+日期 Date (sort ASC),
+任务号 Task Number,
+项目名称 Project Name,
+项目号 Project Code,
+消耗时间(单位:小时) Time Consumed,
+部门名称 Department Name,
+姓名 Employee Realname,
+账号 Employee Account
+
+============================================
+
+部门简要统计 Department Collected:
+
+Input: 月 Month, 部门名称 Department Name
+
+Output Data:
+
+============================================
+
+部门详情统计 Department Details:
+
+Input: 月 Month, 部门名称 Department Name
+
+Output Data:
+
+============================================
+
+财务使用 Finance:
+
+Input: 月 Month
+
+Output Data:

+ 2 - 1
src/App.vue

@@ -20,7 +20,8 @@ export default {
   font-family: Avenir, Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  
+  width: 100%;
+  height: 100%;
   
 }
 </style>

+ 208 - 5
src/components/DepartmentCollected.vue

@@ -1,12 +1,55 @@
 <template>
   <div>
-    <b-navbar type="light" variant="faded">
-      <b-nav-form>
-        <b-form-input class="mr-sm-2" placeholder="Search"></b-form-input>
-        <b-button variant="outline-success" class="my-2 my-sm-0" type="submit">Search</b-button>
+    <div class="topbar-right"><b>月统计</b></div>
+    <b-navbar type="light" variant="faded" class="my-2">
+      <b-nav-form autocomplete="off">
+
+        <!-- <label for="input-month">月份:</label> -->
+        <b-input-group class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="calendar3"></b-icon>
+          </b-input-group-prepend>
+          <b-form-input :state="validDate" id="input-month" class="mr-sm-2" placeholder="选择月份" v-model="date" @click="openCalendar"></b-form-input>
+        </b-input-group>
+
+        <b-popover target="input-month" ref="calendar" placement="bottom">
+          <div class="year-select my-1">
+            <b-button variant="light" size="sm" class="year-minus" @click="yearDecrease"><b-icon icon="chevron-double-left"></b-icon></b-button>
+            <div class="year text-center">{{ year }} 年</div>
+            <b-button variant="light" size="sm" class="year-plus" @click="yearIncrease"><b-icon icon="chevron-double-right"></b-icon></b-button>
+          </div>
+          <div>
+            <table id="months" class="months">
+              <tr><td @click="setMonth(1)">一月</td><td @click="setMonth(2)">二月</td><td @click="setMonth(3)">三月</td><td @click="setMonth(4)">四月</td></tr>
+              <tr><td @click="setMonth(5)">五月</td><td @click="setMonth(6)">六月</td><td @click="setMonth(7)">七月</td><td @click="setMonth(8)">八月</td></tr>
+              <tr><td @click="setMonth(9)">九月</td><td @click="setMonth(10)">十月</td><td @click="setMonth(11)">十一月</td><td @click="setMonth(12)">十二月</td></tr>
+            </table>
+          </div>
+        </b-popover>
+
+        <!-- <label for="input-name">姓名:</label> -->
+        <b-input-group id="input-name" class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="people-fill"></b-icon>
+          </b-input-group-prepend>
+
+          <b-form-input :state="validDepartment" @input="selectDepartment" list="name" class="mr-sm-2" placeholder="请选择部门"></b-form-input>
+
+          <datalist id="name">
+            <option v-for="department in departmentList" :key="department.id" :value="department.id +' - '+ department.name"></option>
+          </datalist>
+
+        </b-input-group>
+
+        <b-button variant="primary" class="mx-1" @click="showData">查询</b-button>
+        <b-button variant="primary" class="mx-1" @click="exportData">导出</b-button>
       </b-nav-form>
     </b-navbar>
-    DepartmentCollected
+    <b-card class="content-data mx-3 text-center">
+      <div class="no-content my-4" v-if="!dataAvailable">请在上方输入搜索条件查询</div>
+      <b-table class="datatable" sticky-header="100%" hover small :items="dataTable" v-if="dataAvailable" :tbody-tr-class="tableRowColor"></b-table>
+    </b-card>
+    
   </div>
 </template>
 
@@ -15,11 +58,171 @@ export default {
   name: 'DepartmentCollected',
   props: {
     
+  },
+  data() {
+    return {
+      year: undefined,
+      month: undefined,
+      date: '',
+      departmentId:'',
+      departmentName:'',
+      departmentList: [],
+      dataAvailable: false,
+      dataTable: [],
+      validate: false
+    };
+  },
+  computed: {
+    validDate() {
+      return this.validate ? (this.year && this.month ? true : false) : null;
+    },
+    validDepartment() {
+      return this.validate ? (this.departmentId ? true : false) : null;
+    }
+  },
+  created() {
+    this.$http.get('/api/dept/list')
+      .then((result) => {
+        this.departmentList = result.data.data;
+        this.departmentList.sort((a, b) => {
+          return a.id - b.id;
+        });
+      });
+  },
+  mounted() {
+    this.year = new Date().getFullYear();
+    
+    
+  },
+  methods: {
+    reset() {
+      this.dataAvailable = false;
+      this.dataTable = [];
+    },
+    onItemClick(item){
+      console.log(item)
+    },
+    openCalendar() {
+      this.$refs.calendar.$emit('open');
+      this.reset();
+    },
+    clamp(value, min, max) {
+      if(value < min) { return min; }
+      else if(value > max) { return max; }
+      else { return value; }
+    },
+    yearDecrease() {
+      this.year = this.clamp(this.year - 1, 2000, 3000);
+    },
+    yearIncrease() {
+      this.year = this.clamp(this.year + 1, 2000, 3000);
+    },
+    setMonth(m) {
+      this.month = m;
+      this.date = m + '/' + this.year;
+      this.$refs.calendar.$emit('close');
+    },
+    selectDepartment(e) {
+      this.departmentId = e.split(' - ')[0];
+      this.departmentName = e.split(' - ')[1];
+      this.reset();
+    },
+    showData() {
+      if(this.year && this.month && this.departmentId) {
+        this.$http.post('/api/dept/byMonth', {
+          "date": (this.year + '-' + this.month + '-' + '01'),
+          "deptId": this.departmentId,
+          "pageNum": 0,
+          "pageSize": 100000,
+          "searchKey": ""
+        }).then((result) => {
+          
+          let data = result.data.data.list;
+          
+          data.sort((a, b) => {
+            return ('' + a.attr).localeCompare(b.attr);
+          });
+
+          data.forEach(entry => {
+            this.dataTable.push({
+              '日期': entry.date,
+              '消耗时间(单位:小时)': entry.consumed,
+              '部门名称': entry.deptName,
+              '姓名': entry.realname,
+              '账号': entry.account
+            });
+          });
+          this.dataAvailable = true;
+        });
+      } else {
+        this.validate = true;
+      }
+    },
+    exportData() {
+      if(this.dataAvailable === true && this.dataTable.length > 0) {
+        let csv = Object.keys(this.dataTable[0]).join() + '\n';
+        this.dataTable.forEach(row => {
+          csv += Object.values(row).join() + '\n';
+        });
+
+        let hiddenElement = document.createElement('a');
+        hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
+        hiddenElement.target = '_blank';
+        hiddenElement.download = this.year + '-' + this.month + '-' + this.departmentName + '.csv';
+        hiddenElement.click();
+      }
+    },
+    tableRowColor(item, type) {
+      console.log(item, type);
+      if (!item || type !== 'row') return
+      if (item['消耗时间(单位:小时)'] > 12) return 'table-danger'
+      if (item['消耗时间(单位:小时)'] < 8) return 'table-danger'
+    }
   }
 }
+
+
 </script>
 
 
 <style scoped>
+  .topbar-right {
+    width: 100%;
+    height: 7vh;
+    font-size: 1.2em;
+    padding: 16px 16px;
+    border-bottom: 2px solid lightgrey;
+  }
+  .content-data {
+    position: absolute;
+    height: 90%;
+    width: calc(100% - 30px);
+  }
+  .card-body {
+    padding: 0;
+  }
+  .year-select {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .year, .year-minus, .year-plus {
+    display: inline-block;
+  }
+  .year {
+    font-size: 1.3em;
+  }
+  .months > tr > td {
+    width: 80px;
+    height: 40px;
+    text-align: center;
+    cursor: pointer;
+  }
+  .months > tr > td:focus {
+    color: blue;
+  }
+  .datatable {
+    overflow-y: scroll;
+  }
   
 </style>

+ 205 - 4
src/components/DepartmentDetails.vue

@@ -1,11 +1,55 @@
 <template>
   <div>
-    <b-navbar type="light" variant="faded">
-      <b-nav-form>
-        <b-form-input class="mr-sm-2" placeholder="Search"></b-form-input>
-        <b-button variant="outline-success" class="my-2 my-sm-0" type="submit">Search</b-button>
+    <div class="topbar-right"><b>月详情</b></div>
+    <b-navbar type="light" variant="faded" class="my-2">
+      <b-nav-form autocomplete="off">
+
+        
+        <b-input-group class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="calendar3"></b-icon>
+          </b-input-group-prepend>
+          <b-form-input :state="validDate" id="input-month" class="mr-sm-2" placeholder="选择月份" v-model="date" @click="openCalendar"></b-form-input>
+        </b-input-group>
+
+        <b-popover target="input-month" ref="calendar" placement="bottom">
+          <div class="year-select my-1">
+            <b-button variant="light" size="sm" class="year-minus" @click="yearDecrease"><b-icon icon="chevron-double-left"></b-icon></b-button>
+            <div class="year text-center">{{ year }} 年</div>
+            <b-button variant="light" size="sm" class="year-plus" @click="yearIncrease"><b-icon icon="chevron-double-right"></b-icon></b-button>
+          </div>
+          <div>
+            <table id="months" class="months">
+              <tr><td @click="setMonth(1)">一月</td><td @click="setMonth(2)">二月</td><td @click="setMonth(3)">三月</td><td @click="setMonth(4)">四月</td></tr>
+              <tr><td @click="setMonth(5)">五月</td><td @click="setMonth(6)">六月</td><td @click="setMonth(7)">七月</td><td @click="setMonth(8)">八月</td></tr>
+              <tr><td @click="setMonth(9)">九月</td><td @click="setMonth(10)">十月</td><td @click="setMonth(11)">十一月</td><td @click="setMonth(12)">十二月</td></tr>
+            </table>
+          </div>
+        </b-popover>
+
+
+        <b-input-group id="input-name" class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="people-fill"></b-icon>
+          </b-input-group-prepend>
+
+          <b-form-input :state="validDepartment" @input="selectDepartment" list="name" class="mr-sm-2" placeholder="请选择部门"></b-form-input>
+
+          <datalist id="name">
+            <option v-for="department in departmentList" :key="department.id" :value="department.id +' - '+ department.name"></option>
+          </datalist>
+
+        </b-input-group>
+
+        <b-button variant="primary" class="mx-1" @click="showData">查询</b-button>
+        <b-button variant="primary" class="mx-1" @click="exportData">导出</b-button>
       </b-nav-form>
     </b-navbar>
+    <b-card class="content-data mx-3 text-center">
+      <div class="no-content my-4" v-if="!dataAvailable">请在上方输入搜索条件查询</div>
+      <b-table class="datatable" sticky-header="100%" hover small :items="dataTable" v-if="dataAvailable"></b-table>
+    </b-card>
+    
   </div>
 </template>
 
@@ -14,11 +58,168 @@ export default {
   name: 'DepartmentDetails',
   props: {
     
+  },
+  data() {
+    return {
+      year: undefined,
+      month: undefined,
+      date: '',
+      departmentId:'',
+      departmentName:'',
+      departmentList: [],
+      dataAvailable: false,
+      dataTable: [],
+      validate: false
+    };
+  },
+  computed: {
+    validDate() {
+      return this.validate ? (this.year && this.month ? true : false) : null;
+    },
+    validDepartment() {
+      return this.validate ? (this.departmentId ? true : false) : null;
+    }
+  },
+  created() {
+    this.$http.get('/api/dept/list')
+      .then((result) => {
+        this.departmentList = result.data.data;
+        this.departmentList.sort((a, b) => {
+          return a.id - b.id;
+        });
+      });
+  },
+  mounted() {
+    this.year = new Date().getFullYear();
+    
+    
+  },
+  methods: {
+    reset() {
+      this.dataAvailable = false;
+      this.dataTable = [];
+    },
+    onItemClick(item){
+      console.log(item)
+    },
+    openCalendar() {
+      this.$refs.calendar.$emit('open');
+      this.reset();
+    },
+    clamp(value, min, max) {
+      if(value < min) { return min; }
+      else if(value > max) { return max; }
+      else { return value; }
+    },
+    yearDecrease() {
+      this.year = this.clamp(this.year - 1, 2000, 3000);
+    },
+    yearIncrease() {
+      this.year = this.clamp(this.year + 1, 2000, 3000);
+    },
+    setMonth(m) {
+      this.month = m;
+      this.date = m + '/' + this.year;
+      this.$refs.calendar.$emit('close');
+    },
+    selectDepartment(e) {
+      this.departmentId = e.split(' - ')[0];
+      this.departmentName = e.split(' - ')[1];
+      this.reset();
+    },
+    showData() {
+      if(this.year && this.month && this.departmentId) {
+        this.$http.post('/api/dept/byDetail', {
+          "date": (this.year + '-' + this.month + '-' + '01'),
+          "deptId": this.departmentId,
+          "pageNum": 0,
+          "pageSize": 100000,
+          "searchKey": ""
+        }).then((result) => {
+          
+          let data = result.data.data.list;
+          
+          data.sort((a, b) => {
+            return new Date(a.date) - new Date(b.date);
+          });
+
+          data.forEach(entry => {
+            this.dataTable.push({
+              '日期': entry.date,
+              '任务号': entry.task,
+              '项目名称': entry.projectName,
+              '项目号': entry.project,
+              '消耗时间(单位:小时)': entry.consumed,
+              '部门名称': entry.deptName,
+              '姓名': entry.realname,
+              '账号': entry.account
+            });
+          });
+          this.dataAvailable = true;
+        });
+      } else {
+        this.validate = true;
+      }
+    },
+    exportData() {
+      if(this.dataAvailable === true && this.dataTable.length > 0) {
+        let csv = Object.keys(this.dataTable[0]).join() + '\n';
+        this.dataTable.forEach(row => {
+          csv += Object.values(row).join() + '\n';
+        });
+
+        let hiddenElement = document.createElement('a');
+        hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
+        hiddenElement.target = '_blank';
+        hiddenElement.download = this.year + '-' + this.month + '-' + this.departmentName + '.csv';
+        hiddenElement.click();
+      }
+    }
   }
 }
+
+
 </script>
 
 
 <style scoped>
+  .topbar-right {
+    width: 100%;
+    height: 7vh;
+    font-size: 1.2em;
+    padding: 16px 16px;
+    border-bottom: 2px solid lightgrey;
+  }
+  .content-data {
+    position: absolute;
+    height: 90%;
+    width: calc(100% - 30px);
+  }
+  .card-body {
+    padding: 0;
+  }
+  .year-select {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .year, .year-minus, .year-plus {
+    display: inline-block;
+  }
+  .year {
+    font-size: 1.3em;
+  }
+  .months > tr > td {
+    width: 80px;
+    height: 40px;
+    text-align: center;
+    cursor: pointer;
+  }
+  .months > tr > td:focus {
+    color: blue;
+  }
+  .datatable {
+    overflow-y: scroll;
+  }
   
 </style>

+ 0 - 25
src/components/Finance.vue

@@ -1,25 +0,0 @@
-<template>
-  <div>
-    <b-navbar type="light" variant="faded">
-      <b-nav-form>
-        <b-form-input class="mr-sm-2" placeholder="Search"></b-form-input>
-        <b-button variant="outline-success" class="my-2 my-sm-0" type="submit">Search</b-button>
-      </b-nav-form>
-    </b-navbar>
-    Finance
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'Finance',
-  props: {
-    
-  }
-}
-</script>
-
-
-<style scoped>
-  
-</style>

+ 144 - 0
src/components/FinanceBrief.vue

@@ -0,0 +1,144 @@
+<template>
+  <div>
+    <div class="topbar-right"><b>月统计</b></div>
+    <b-navbar type="light" variant="faded" class="my-2">
+      <b-nav-form autocomplete="off">
+
+        <!-- <label for="input-month">月份:</label> -->
+        <b-input-group class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="calendar3"></b-icon>
+          </b-input-group-prepend>
+          <b-form-input id="input-month" class="mr-sm-2" placeholder="选择月份" v-model="date" @click="openCalendar"></b-form-input>
+        </b-input-group>
+
+        <b-popover target="input-month" ref="calendar" placement="bottom">
+          <div class="year-select my-1">
+            <b-button variant="light" size="sm" class="year-minus" @click="yearDecrease"><b-icon icon="chevron-double-left"></b-icon></b-button>
+            <div class="year text-center">{{ year }} 年</div>
+            <b-button variant="light" size="sm" class="year-plus" @click="yearIncrease"><b-icon icon="chevron-double-right"></b-icon></b-button>
+          </div>
+          <div>
+            <table id="months" class="months">
+              <tr><td @click="setMonth(1)">一月</td><td @click="setMonth(2)">二月</td><td @click="setMonth(3)">三月</td><td @click="setMonth(4)">四月</td></tr>
+              <tr><td @click="setMonth(5)">五月</td><td @click="setMonth(6)">六月</td><td @click="setMonth(7)">七月</td><td @click="setMonth(8)">八月</td></tr>
+              <tr><td @click="setMonth(9)">九月</td><td @click="setMonth(10)">十月</td><td @click="setMonth(11)">十一月</td><td @click="setMonth(12)">十二月</td></tr>
+            </table>
+          </div>
+        </b-popover>
+
+        <b-button variant="primary" class="mx-1" @click="exportData">导出</b-button>
+      </b-nav-form>
+    </b-navbar>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FinanceBrief',
+  props: {
+    
+  },
+  data() {
+    return {
+      year: undefined,
+      month: undefined,
+      date: '',
+      dataAvailable: false,
+      dataTable: []
+    };
+  },
+  computed: {
+    
+  },
+  created() {
+    
+  },
+  mounted() {
+    this.year = new Date().getFullYear();
+  },
+  methods: {
+    reset() {
+      this.dataAvailable = false;
+      this.dataTable = [];
+    },
+    openCalendar() {
+      this.$refs.calendar.$emit('open');
+      this.reset();
+    },
+    clamp(value, min, max) {
+      if(value < min) { return min; }
+      else if(value > max) { return max; }
+      else { return value; }
+    },
+    yearDecrease() {
+      this.year = this.clamp(this.year - 1, 2000, 3000);
+    },
+    yearIncrease() {
+      this.year = this.clamp(this.year + 1, 2000, 3000);
+    },
+    setMonth(m) {
+      this.month = m;
+      this.date = m + '/' + this.year;
+      this.$refs.calendar.$emit('close');
+    },
+    exportData() {
+      let date = this.year + '-' + this.month + '-' + '01';
+      let url = '/api/finance/excelByMonth/' + date;
+      this.$http.get(url)
+      .then((result) => {
+        
+        let fileUrl = result.data.msg;
+        let hiddenElement = document.createElement('a');
+        hiddenElement.href = fileUrl;
+        hiddenElement.target = '_blank';
+        hiddenElement.click();
+        
+      });
+    }
+  }
+}
+
+
+</script>
+
+
+<style scoped>
+  .topbar-right {
+    width: 100%;
+    height: 7vh;
+    font-size: 1.2em;
+    padding: 16px 16px;
+    border-bottom: 2px solid lightgrey;
+  }
+  .content-data {
+    position: absolute;
+    height: 90%;
+    width: 98%;
+    overflow-y: scroll;
+  }
+  .year-select {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .year, .year-minus, .year-plus {
+    display: inline-block;
+  }
+  .year {
+    font-size: 1.3em;
+  }
+  .months > tr > td {
+    width: 80px;
+    height: 40px;
+    text-align: center;
+    cursor: pointer;
+  }
+  .months > tr > td:focus {
+    color: blue;
+  }
+  .datatable {
+    overflow-y: scroll;
+  }
+  
+</style>

+ 144 - 0
src/components/FinanceDetails.vue

@@ -0,0 +1,144 @@
+<template>
+  <div>
+    <div class="topbar-right"><b>月详情</b></div>
+    <b-navbar type="light" variant="faded" class="my-2">
+      <b-nav-form autocomplete="off">
+
+        <!-- <label for="input-month">月份:</label> -->
+        <b-input-group class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="calendar3"></b-icon>
+          </b-input-group-prepend>
+          <b-form-input id="input-month" class="mr-sm-2" placeholder="选择月份" v-model="date" @click="openCalendar"></b-form-input>
+        </b-input-group>
+
+        <b-popover target="input-month" ref="calendar" placement="bottom">
+          <div class="year-select my-1">
+            <b-button variant="light" size="sm" class="year-minus" @click="yearDecrease"><b-icon icon="chevron-double-left"></b-icon></b-button>
+            <div class="year text-center">{{ year }} 年</div>
+            <b-button variant="light" size="sm" class="year-plus" @click="yearIncrease"><b-icon icon="chevron-double-right"></b-icon></b-button>
+          </div>
+          <div>
+            <table id="months" class="months">
+              <tr><td @click="setMonth(1)">一月</td><td @click="setMonth(2)">二月</td><td @click="setMonth(3)">三月</td><td @click="setMonth(4)">四月</td></tr>
+              <tr><td @click="setMonth(5)">五月</td><td @click="setMonth(6)">六月</td><td @click="setMonth(7)">七月</td><td @click="setMonth(8)">八月</td></tr>
+              <tr><td @click="setMonth(9)">九月</td><td @click="setMonth(10)">十月</td><td @click="setMonth(11)">十一月</td><td @click="setMonth(12)">十二月</td></tr>
+            </table>
+          </div>
+        </b-popover>
+
+        <b-button variant="primary" class="mx-1" @click="exportData">导出</b-button>
+      </b-nav-form>
+    </b-navbar>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FinanceDetails',
+  props: {
+    
+  },
+  data() {
+    return {
+      year: undefined,
+      month: undefined,
+      date: '',
+      dataAvailable: false,
+      dataTable: []
+    };
+  },
+  computed: {
+    
+  },
+  created() {
+    
+  },
+  mounted() {
+    this.year = new Date().getFullYear();
+  },
+  methods: {
+    reset() {
+      this.dataAvailable = false;
+      this.dataTable = [];
+    },
+    openCalendar() {
+      this.$refs.calendar.$emit('open');
+      this.reset();
+    },
+    clamp(value, min, max) {
+      if(value < min) { return min; }
+      else if(value > max) { return max; }
+      else { return value; }
+    },
+    yearDecrease() {
+      this.year = this.clamp(this.year - 1, 2000, 3000);
+    },
+    yearIncrease() {
+      this.year = this.clamp(this.year + 1, 2000, 3000);
+    },
+    setMonth(m) {
+      this.month = m;
+      this.date = m + '/' + this.year;
+      this.$refs.calendar.$emit('close');
+    },
+    exportData() {
+      let date = this.year + '-' + this.month + '-' + '01';
+      let url = '/api/finance/excelByDetail/' + date;
+      this.$http.get(url)
+      .then((result) => {
+        console.log(result);
+        let fileUrl = result.data.data.path;
+        let hiddenElement = document.createElement('a');
+        hiddenElement.href = fileUrl;
+        hiddenElement.target = '_blank';
+        hiddenElement.click();
+        
+      });
+    }
+  }
+}
+
+
+</script>
+
+
+<style scoped>
+  .topbar-right {
+    width: 100%;
+    height: 7vh;
+    font-size: 1.2em;
+    padding: 16px 16px;
+    border-bottom: 2px solid lightgrey;
+  }
+  .content-data {
+    position: absolute;
+    height: 90%;
+    width: 98%;
+    overflow-y: scroll;
+  }
+  .year-select {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .year, .year-minus, .year-plus {
+    display: inline-block;
+  }
+  .year {
+    font-size: 1.3em;
+  }
+  .months > tr > td {
+    width: 80px;
+    height: 40px;
+    text-align: center;
+    cursor: pointer;
+  }
+  .months > tr > td:focus {
+    color: blue;
+  }
+  .datatable {
+    overflow-y: scroll;
+  }
+  
+</style>

+ 157 - 102
src/components/PersonalCollected.vue

@@ -1,45 +1,40 @@
 <template>
   <div>
+    <div class="topbar-right"><b>月统计</b></div>
     <b-navbar type="light" variant="faded" class="my-2">
-      <b-nav-form>
-
-        <!-- <label for="input-month">月份:</label> -->
-        <b-input-group class="">
-          <b-input-group-prepend is-text>
-            <b-icon icon="calendar3"></b-icon>
-          </b-input-group-prepend>
-          <b-form-input id="input-month" class="mr-sm-2" placeholder="选择月份" v-model="date" @click="openCalendar"></b-form-input>
-        </b-input-group>
-
-        <b-popover target="input-month" ref="calendar" placement="bottom">
-          <div class="year-select my-1">
-            <b-button variant="light" size="sm" class="year-minus" @click="yearDecrease"><b-icon icon="chevron-double-left"></b-icon></b-button>
-            <div class="year text-center">{{ year }} 年</div>
-            <b-button variant="light" size="sm" class="year-plus" @click="yearIncrease"><b-icon icon="chevron-double-right"></b-icon></b-button>
-          </div>
-          <div>
-            <table id="months" class="months">
-              <tr><td @click="setMonth(1)">一月</td><td @click="setMonth(2)">二月</td><td @click="setMonth(3)">三月</td><td @click="setMonth(4)">四月</td></tr>
-              <tr><td @click="setMonth(5)">五月</td><td @click="setMonth(6)">六月</td><td @click="setMonth(7)">七月</td><td @click="setMonth(8)">八月</td></tr>
-              <tr><td @click="setMonth(9)">九月</td><td @click="setMonth(10)">十月</td><td @click="setMonth(11)">十一月</td><td @click="setMonth(12)">十二月</td></tr>
-            </table>
-          </div>
-        </b-popover>
-
-
-
-
-
+      
 
+          <b-form-datepicker
+            id="start-date"
+            v-model="startTime"
+            v-bind="calendarLabels[locale] || {}"
+            :locale="locale"
+            :start-weekday="weekday"
+            :show-decade-nav="showDecadeNav"
+            :hide-header="hideHeader"
+            class="calendar danger"
+            :state="validStartTime"
+          ></b-form-datepicker>
+
+          <b-form-datepicker
+            id="end-date"
+            v-model="endTime"
+            v-bind="calendarLabels[locale] || {}"
+            :locale="locale"
+            :start-weekday="weekday"
+            :show-decade-nav="showDecadeNav"
+            :hide-header="hideHeader"
+            class="calendar mx-2"
+            :state="validEndTime"
+          ></b-form-datepicker>
 
 
-        <!-- <label for="input-name">姓名:</label> -->
         <b-input-group id="input-name" class="">
           <b-input-group-prepend is-text>
             <b-icon icon="person-square"></b-icon>
           </b-input-group-prepend>
 
-          <b-form-input @input="selectEmployee" list="name" class="mr-sm-2" placeholder="请输入姓名"></b-form-input>
+          <b-form-input :state="validUserAccount" @input="selectEmployee" list="name" class="mr-sm-2" placeholder="请输入姓名"></b-form-input>
 
           <datalist id="name">
             <option v-for="employee in employeeList" :key="employee.id" :value="employee.account+' - '+employee.realname"></option>
@@ -48,12 +43,12 @@
         </b-input-group>
 
         <b-button variant="primary" class="mx-1" @click="showData">查询</b-button>
-        <b-button variant="primary" class="mx-1">导出</b-button>
-      </b-nav-form>
+        <b-button variant="primary" class="mx-1" @click="exportData">导出</b-button>
+      
     </b-navbar>
     <b-card class="content-data mx-3 text-center">
-      <span class="no-content" v-if="!dataAvailable">请在上方输入搜索条件查询</span>
-      <b-table class="datatable" striped hover :items="dataTable" v-if="dataAvailable"></b-table>
+      <div class="no-content my-4" v-if="!dataAvailable">请在上方输入搜索条件查询</div>
+      <b-table class="datatable" sticky-header="100%" hover small :items="dataTable" v-if="dataAvailable" :tbody-tr-class="tableRowColor"></b-table>
     </b-card>
     
   </div>
@@ -73,21 +68,53 @@ export default {
       userAccount:'',
       employeeList: [],
       dataAvailable: false,
-      dataTable: []
+      dataTable: [],
+      startTime: undefined,
+      endTime: undefined,
+      locale: 'zh',
+      weekday: 1,
+      showDecadeNav: false,
+      hideHeader: true,
+      calendarLabels: {
+        zh: {
+          weekdayHeaderFormat: 'narrow',
+          labelPrevDecade: '过去十年',
+          labelPrevYear: '上一年',
+          labelPrevMonth: '上个月',
+          labelCurrentMonth: '当前月份',
+          labelNextMonth: '下个月',
+          labelNextYear: '明年',
+          labelNextDecade: '下一个十年',
+          labelToday: '今天',
+          labelSelected: '选定日期',
+          labelNoDateSelected: '未选择日期',
+          labelCalendar: '日历',
+          labelNav: '日历导航',
+          labelHelp: '使用光标键浏览日期'
+        }
+      },
+      validate: false
     };
   },
   computed: {
-    
+    validStartTime() {
+      return this.validate ? (this.startTime ? true : false) : null;
+    },
+    validEndTime() {
+      return this.validate ? (this.endTime ? true : false) : null;
+    },
+    validUserAccount() {
+      return this.validate ? (this.userAccount ? true : false) : null;
+    }
   },
   created() {
     this.$http.get('/api/personal/getUserList')
       .then((result) => {
         this.employeeList = result.data.data;
-        console.log(this.employeeList)
       });
   },
   mounted() {
-    this.year = new Date().getFullYear();
+    
     
     
   },
@@ -96,59 +123,92 @@ export default {
       this.dataAvailable = false;
       this.dataTable = [];
     },
-    onItemClick(item){
-      console.log(item)
-    },
-    openCalendar() {
-      this.$refs.calendar.$emit('open');
-      this.reset();
-    },
-    clamp(value, min, max) {
-      if(value < min) { return min; }
-      else if(value > max) { return max; }
-      else { return value; }
-    },
-    yearDecrease() {
-      this.year = this.clamp(this.year - 1, 2000, 3000);
-    },
-    yearIncrease() {
-      this.year = this.clamp(this.year + 1, 2000, 3000);
-    },
-    setMonth(m) {
-      this.month = m;
-      this.date = m + '/' + this.year;
-      this.$refs.calendar.$emit('close');
-    },
     selectEmployee(e) {
       this.userAccount = e.split(' - ')[0];
       this.reset();
     },
     showData() {
       
-      this.$http.post('/api/personal/workByMonthDetail', {
-        "account": this.userAccount,
-        "date": (this.year + '-' + this.month + '-' + '01'),
-        "pageNum": 0,
-        "pageSize": 500,
-        "searchKey": ""
-      }).then((result) => {
-        console.log(result.data.data.list);
-        let data = result.data.data.list;
-        data.forEach(entry => {
-          this.dataTable.push({
-            '日期': entry.date,
-            '任务号': entry.task,
-            '项目名称': entry.projectName,
-            '项目号': entry.code,
-            '消耗时间(单位:小时)': entry.consumed,
-            '部门名称': entry.deptName,
-            '姓名': entry.realname,
-            '账号': entry.account
+      if(this.startTime && this.endTime && this.userAccount) {
+        this.dataTable = [];
+        this.$http.post('/api/personal/workByDayDetail', {
+          "account": this.userAccount,
+          "startTime": this.startTime,
+          "endTime": this.endTime,
+          "pageNum": 0,
+          "pageSize": 1000,
+          "searchKey": ""
+        }).then((result) => {
+          
+          let data = result.data.data.list;
+
+          let dataSummedUp = [];
+          let days = [];
+          
+          data.forEach(entry => {
+            if(days.indexOf(entry.date) === -1) {
+              days.push(entry.date);
+            }
           });
+
+          days.forEach(day => {
+            let filtered = data.filter(entry => entry.date === day);
+            let sum = 0;
+            let total;
+
+            filtered.forEach(d => {
+              let number = Number(d.consumed);
+              
+              sum += number;
+              total = Number(parseFloat(sum).toPrecision(4));
+            });
+            dataSummedUp.push({
+              date: filtered[0].date,
+              consumed: total,
+              deptName: filtered[0].deptName,
+              realname: filtered[0].realname,
+              account: filtered[0].account
+            });
+          });
+
+          
+          dataSummedUp.sort((a, b) => {
+            return new Date(a.date) - new Date(b.date);
+          });
+
+          dataSummedUp.forEach(entry => {
+            this.dataTable.push({
+              '日期': entry.date,
+              '消耗时间(单位:小时)': entry.consumed,
+              '部门名称': entry.deptName,
+              '姓名': entry.realname,
+              '账号': entry.account
+            });
+          });
+          this.dataAvailable = true;
+        });
+      } else {
+        this.validate = true;
+      }
+    },
+    exportData() {
+      if(this.dataAvailable === true && this.dataTable.length > 0) {
+        let csv = Object.keys(this.dataTable[0]).join() + '\n';
+        this.dataTable.forEach(row => {
+          csv += Object.values(row).join() + '\n';
         });
-        this.dataAvailable = true;
-      });
 
+        let hiddenElement = document.createElement('a');
+        hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
+        hiddenElement.target = '_blank';
+        hiddenElement.download = this.startTime + '_' + this.endTime + '_' + this.userAccount + '.csv';
+        hiddenElement.click();
+      }
+    },
+    tableRowColor(item, type) {
+      if (!item || type !== 'row') return
+      if (item['消耗时间(单位:小时)'] > 12) return 'table-danger'
+      if (item['消耗时间(单位:小时)'] < 8) return 'table-danger'
     }
   }
 }
@@ -158,31 +218,26 @@ export default {
 
 
 <style scoped>
+  .topbar-right {
+    width: 100%;
+    height: 7vh;
+    font-size: 1.2em;
+    padding: 16px 16px;
+    border-bottom: 2px solid lightgrey;
+  }
   .content-data {
     position: absolute;
     height: 90%;
-    width: 98%;
-    overflow-y: scroll;
-  }
-  .year-select {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-  }
-  .year, .year-minus, .year-plus {
-    display: inline-block;
+    width: calc(100% - 30px);
   }
-  .year {
-    font-size: 1.3em;
+  .calendar {
+    width: 25%;
   }
-  .months > tr > td {
-    width: 80px;
-    height: 40px;
-    text-align: center;
-    cursor: pointer;
+  #input-name {
+    width: 25%;
   }
-  .months > tr > td:focus {
-    color: blue;
+  .card-body {
+    padding: 0;
   }
   .datatable {
     overflow-y: scroll;

+ 202 - 4
src/components/PersonalDetails.vue

@@ -1,11 +1,56 @@
 <template>
   <div>
-    <b-navbar type="light" variant="faded">
-      <b-nav-form>
-        <b-form-input class="mr-sm-2" placeholder="Search"></b-form-input>
-        <b-button variant="outline-success" class="my-2 my-sm-0" type="submit">Search</b-button>
+    <div class="topbar-right"><b>月详情</b></div>
+    <b-navbar type="light" variant="faded" class="my-2">
+      <b-nav-form autocomplete="off">
+
+        <!-- <label for="input-month">月份:</label> -->
+        <b-input-group class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="calendar3"></b-icon>
+          </b-input-group-prepend>
+          <b-form-input :state="validDate" id="input-month" class="mr-sm-2" placeholder="选择月份" v-model="date" @click="openCalendar"></b-form-input>
+        </b-input-group>
+
+        <b-popover target="input-month" ref="calendar" placement="bottom">
+          <div class="year-select my-1">
+            <b-button variant="light" size="sm" class="year-minus" @click="yearDecrease"><b-icon icon="chevron-double-left"></b-icon></b-button>
+            <div class="year text-center">{{ year }} 年</div>
+            <b-button variant="light" size="sm" class="year-plus" @click="yearIncrease"><b-icon icon="chevron-double-right"></b-icon></b-button>
+          </div>
+          <div>
+            <table id="months" class="months">
+              <tr><td @click="setMonth(1)">一月</td><td @click="setMonth(2)">二月</td><td @click="setMonth(3)">三月</td><td @click="setMonth(4)">四月</td></tr>
+              <tr><td @click="setMonth(5)">五月</td><td @click="setMonth(6)">六月</td><td @click="setMonth(7)">七月</td><td @click="setMonth(8)">八月</td></tr>
+              <tr><td @click="setMonth(9)">九月</td><td @click="setMonth(10)">十月</td><td @click="setMonth(11)">十一月</td><td @click="setMonth(12)">十二月</td></tr>
+            </table>
+          </div>
+        </b-popover>
+
+
+        
+        <b-input-group id="input-name" class="">
+          <b-input-group-prepend is-text>
+            <b-icon icon="person-square"></b-icon>
+          </b-input-group-prepend>
+
+          <b-form-input :state="validName" @input="selectEmployee" list="name" class="mr-sm-2" placeholder="请输入姓名"></b-form-input>
+
+          <datalist id="name">
+            <option v-for="employee in employeeList" :key="employee.id" :value="employee.account+' - '+employee.realname"></option>
+          </datalist>
+
+        </b-input-group>
+
+        <b-button variant="primary" class="mx-1" @click="showData">查询</b-button>
+        <b-button variant="primary" class="mx-1" @click="exportData">导出</b-button>
       </b-nav-form>
     </b-navbar>
+    <b-card class="content-data mx-3 text-center">
+      <div class="no-content my-4" v-if="!dataAvailable">请在上方输入搜索条件查询</div>
+      <b-table class="datatable" sticky-header="100%" hover small :items="dataTable" v-if="dataAvailable"></b-table>
+    </b-card>
+    
   </div>
 </template>
 
@@ -14,11 +59,164 @@ export default {
   name: 'PersonalDetails',
   props: {
     
+  },
+  data() {
+    return {
+      year: undefined,
+      month: undefined,
+      date: '',
+      userAccount:'',
+      employeeList: [],
+      dataAvailable: false,
+      dataTable: [],
+      validate: false
+    };
+  },
+  computed: {
+    validDate() {
+      return this.validate ? (this.year && this.month ? true : false) : null;
+    },
+    validName() {
+      return this.validate ? (this.userAccount ? true : false) : null;
+    }
+  },
+  created() {
+    this.$http.get('/api/personal/getUserList')
+      .then((result) => {
+        this.employeeList = result.data.data;
+      });
+  },
+  mounted() {
+    this.year = new Date().getFullYear();
+    
+    
+  },
+  methods: {
+    reset() {
+      this.dataAvailable = false;
+      this.dataTable = [];
+    },
+    onItemClick(item){
+      console.log(item)
+    },
+    openCalendar() {
+      this.$refs.calendar.$emit('open');
+      this.reset();
+    },
+    clamp(value, min, max) {
+      if(value < min) { return min; }
+      else if(value > max) { return max; }
+      else { return value; }
+    },
+    yearDecrease() {
+      this.year = this.clamp(this.year - 1, 2000, 3000);
+    },
+    yearIncrease() {
+      this.year = this.clamp(this.year + 1, 2000, 3000);
+    },
+    setMonth(m) {
+      this.month = m;
+      this.date = m + '/' + this.year;
+      this.$refs.calendar.$emit('close');
+    },
+    selectEmployee(e) {
+      this.userAccount = e.split(' - ')[0];
+      this.reset();
+    },
+    showData() {
+      
+      if(this.year && this.month && this.userAccount) {
+        this.$http.post('/api/personal/workByMonthDetail', {
+          "account": this.userAccount,
+          "date": (this.year + '-' + this.month + '-' + '01'),
+          "pageNum": 0,
+          "pageSize": 1000,
+          "searchKey": ""
+        }).then((result) => {
+          
+          let data = result.data.data.list;
+          
+          data.sort((a, b) => {
+            return new Date(a.date) - new Date(b.date);
+          });
+
+          data.forEach(entry => {
+            this.dataTable.push({
+              '日期': entry.date,
+              '任务号': entry.task,
+              '项目名称': entry.projectName,
+              '项目号': entry.code,
+              '消耗时间(单位:小时)': entry.consumed,
+              '部门名称': entry.deptName,
+              '姓名': entry.realname,
+              '账号': entry.account
+            });
+          });
+          this.dataAvailable = true;
+        });
+      } else {
+        this.validate = true;
+      }
+    },
+    exportData() {
+      if(this.dataAvailable === true && this.dataTable.length > 0) {
+        let csv = Object.keys(this.dataTable[0]).join() + '\n';
+        this.dataTable.forEach(row => {
+          csv += Object.values(row).join() + '\n';
+        });
+
+        let hiddenElement = document.createElement('a');
+        hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
+        hiddenElement.target = '_blank';
+        hiddenElement.download = this.year + '-' + this.month + '-' + this.userAccount + '.csv';
+        hiddenElement.click();
+      }
+    }
   }
 }
+
+
 </script>
 
 
 <style scoped>
+  .topbar-right {
+    width: 100%;
+    height: 7vh;
+    font-size: 1.2em;
+    padding: 16px 16px;
+    border-bottom: 2px solid lightgrey;
+  }
+  .content-data {
+    position: absolute;
+    height: 90%;
+    width: calc(100% - 30px);
+  }
+  .card-body {
+    padding: 0;
+  }
+  .year-select {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .year, .year-minus, .year-plus {
+    display: inline-block;
+  }
+  .year {
+    font-size: 1.3em;
+  }
+  .months > tr > td {
+    width: 80px;
+    height: 40px;
+    text-align: center;
+    cursor: pointer;
+  }
+  .months > tr > td:focus {
+    color: blue;
+  }
+  .datatable {
+    overflow-y: scroll;
+  }
   
 </style>

+ 12 - 5
src/components/Sidebar.vue

@@ -5,21 +5,28 @@
         <button v-b-toggle.accordion-1>个人使用</button>
       </b-card-header>
       <b-collapse id="accordion-1" visible accordion="my-accordion" role="tabpanel">
-        <button class="button-sub" @click="setContent(1)">个人简要统计</button>
-        <button class="button-sub" @click="setContent(2)">个人详情统计</button>
+        <button class="button-sub" @click="setContent(1)">统计</button>
+        <button class="button-sub" @click="setContent(2)">月详情</button>
       </b-collapse>
     
       <b-card-header header-tag="header" class="p-1" role="tab">
         <button v-b-toggle.accordion-2>部门使用</button>
       </b-card-header>
       <b-collapse id="accordion-2" accordion="my-accordion" role="tabpanel">
-        <button class="button-sub" @click="setContent(3)">部门简要统计</button>
-        <button class="button-sub" @click="setContent(4)">部门详情统计</button>
+        <button class="button-sub" @click="setContent(3)">统计</button>
+        <button class="button-sub" @click="setContent(4)">月详情</button>
       </b-collapse>
     
       <b-card-header header-tag="header" class="p-1" role="tab">
-        <button v-b-toggle.accordion-3 @click="setContent(5)">财务使用</button>
+        <button v-b-toggle.accordion-3>财务使用</button>
       </b-card-header>
+      <b-collapse id="accordion-3" accordion="my-accordion" role="tabpanel">
+        <button class="button-sub" @click="setContent(5)">月统计</button>
+        <button class="button-sub" @click="setContent(6)">月详情</button>
+      </b-collapse>
+
+
+      
       
   </div>
 </template>

+ 18 - 35
src/components/WorkingHours.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="main">
-    <div class="topbar">
-      <div class="topbar-left"><b>工时统计</b></div>
-      <div class="topbar-right"><b>个人简要统计</b></div>
-    </div>
+    
+    <div class="topbar-left"><b>工时统计</b></div>
+      
+    
     <div class="sidebar">
       <Sidebar @set-content="setContent"></Sidebar>
     </div>
@@ -12,8 +12,8 @@
       <PersonalDetails v-if="content === 2"></PersonalDetails>
       <DepartmentCollected v-if="content === 3"></DepartmentCollected>
       <DepartmentDetails v-if="content === 4"></DepartmentDetails>
-      <Finance v-if="content === 5"></Finance>
-
+      <FinanceBrief v-if="content === 5"></FinanceBrief>
+      <FinanceDetails v-if="content === 6"></FinanceDetails>
 
     </div>
   </div>
@@ -25,7 +25,8 @@ import PersonalCollected from './PersonalCollected.vue';
 import PersonalDetails from './PersonalDetails.vue';
 import DepartmentCollected from './DepartmentCollected.vue';
 import DepartmentDetails from './DepartmentDetails.vue';
-import Finance from './Finance.vue';
+import FinanceBrief from './FinanceBrief.vue';
+import FinanceDetails from './FinanceDetails.vue';
 
 export default {
   name: 'WorkingHours',
@@ -35,7 +36,8 @@ export default {
     PersonalDetails,
     DepartmentCollected,
     DepartmentDetails,
-    Finance
+    FinanceBrief,
+    FinanceDetails
   },
   props: {
     
@@ -59,19 +61,10 @@ export default {
     width: 100%;
     height: 100%;
   }
-  .topbar {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 7%;
-  }
   .topbar-left {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 12%;
-    height: 100%;
+    
+    width: 12vw;
+    height: 7vh;
     color: white;
     font-size: 1.2em;
     background-color:#304156;
@@ -79,19 +72,9 @@ export default {
     border-bottom: 2px solid white;
     
   }
-  .topbar-right {
-    position: absolute;
-    top: 0;
-    left: 12%;
-    width: 88%;
-    height: 100%;
-    font-size: 1.2em;
-    padding: 16px 16px;
-    border-bottom: 2px solid lightgrey;
-  }
   .sidebar {
     position: absolute;
-    top: 7%;
+    top: 7vh;
     left: 0;
     width: 12%;
     height: 93%;
@@ -100,9 +83,9 @@ export default {
   }
   .content {
     position: absolute;
-    top: 7%;
-    left: 12%;
-    width: 88%;
-    height: 93%;
+    top: 0;
+    left: 12vw;
+    width: 88vw;
+    height: 93vh;
   }
 </style>

+ 1 - 7
src/utils/http.js

@@ -1,12 +1,6 @@
 import axios from 'axios'
 
-
-
-
-
-axios.defaults.baseURL = 'http://192.168.0.135:8085'
+axios.defaults.baseURL = 'http://192.168.0.115:8085'
 axios.defaults.headers['X-Requested-with'] = 'XMLHttpRequest'
 
-
-
 export { axios }