Vue +Lucksheet实现预览编辑Excel 支持保存、导入、导出(附源码)

 代码:把相关lucksheet相关代码放到publick下面  (资源已上传)

父组件:

<template>
    <div>
     <a-row :gutter="24">
       <a-col :span="3">
        <a-card class="card">
        <time-select-common ref="timeSelect" @setTime="setTime" @queryExcel="queryExcel"></time-select-common>
        </a-card>
       </a-col>

       <a-col :span="21">
        <a-card class="card">
          <span class="table-page-search-submitButtons">
          <a-button type="primary"  key="import-data" style="margin-right: 20px"  @click="importExcelHandleOpen" v-hasPermi="['product:report:upload']"  >
            <a-icon type="import"  />
            导入
          </a-button>
          <a-button  key="export-data" style="margin-right: 20px"  @click="exportExcelHandleOpen" v-hasPermi="['product:report:upload']"  >
            <a-icon type="export"  />
            导出
          </a-button>
          <a-button  type="primary" v-hasPermi="['product:report:upload']" key="save-data"  @click="saveExcelHandleOpen"   >
            <a-icon type="default"  />
            保存
          </a-button>
          </span>
          <div :loading="loading">
            <a-spin tip="加载中..." :spinning="spinning">
            <sheet-table ref="sheetTable" @closeModal="excelVisible = false" @getExcelContent="queryExcel" @uploadExcel="uploadExcel"></sheet-table>
            </a-spin>
          </div>
        </a-card>
       </a-col>
     </a-row>
      <!-- 上传文件 -->
      <a-modal
        :title="'当前上传日期:'+selectDate"
        :visible="excelVisible"
        @cancel="importExcelHandleCancel"
        :footer="null"
        :maskClosable="false"
      >
      <file-upload-drg ref="fileUpload" @fileLook="fileLook" @uploadFile="importFile" :date="selectDate" @closeModal="excelVisible = false" @prievwExce="prievwExcel" @submitFile="submitFile"></file-upload-drg>
      </a-modal>
    </div>
</template>

<script>
  import moment from 'moment'
  import TimeSelectCommon from './component/time-select-common'
  import storage from 'store'
  import { ACCESS_TOKEN } from '@/store/mutation-types'
  import FileUploadDrg from '@/components/FileUploadDrg'
  //引入VueOfficeExcel组件
  import VueOfficeExcel from '@vue-office/excel'
  //引入相关样式
  import '@/assets/css/excel.css'
  import axios from 'axios'
  import {queryFiles} from '@/api/files/files'
  import { previewImg } from '@/api/files/files'
  import { getProductReport,saveProductReport } from '@/api/report/reportInfo'
  import SheetTable from '@/components/SheetTable'
  import { exportExcel } from '@/utils/lucksheetexport'
  export default {
    name: 'reportManage',
    components:{TimeSelectCommon,
      FileUploadDrg,
      VueOfficeExcel,
      SheetTable
    },
    props:{},
    data(){
      return{
        monthValue: moment().format('YYYY-MM'), // 当前月份,初始值为当前月份
        treeData: [], // 树的数据
        isCanUpload:true,
        excelVisible:false,
        // 用户导入参数
        upload: {
          // 是否显示弹出层(用户导入)
          open: false,
          // 弹出层标题(用户导入)
          title: '文件上传',
          // 是否禁用上传
          isUploading: false,
          // 是否更新已经存在的用户数据
          modelId: undefined,
          // 设置上传的请求头部
          headers: { Authorization: 'Bearer ' + storage.get(ACCESS_TOKEN) },
          // 上传的地址
          url: process.env.VUE_APP_API_BASE_URL + '/calc/calcParam/importData',
        },
        selectDate:'',
        excelData: [],
        excel:'',
        loading:false,
        downloadImgUrl:process.env.VUE_APP_API_BASE_URL + '/file/getFileByPath',
        option: {
          title: '生产日报', // 设定表格名称
          lang: 'zh', // 设定表格语言
          plugins: [],
          showinfobar: false,
          showtoolbar: true,
        },
        style: {
          margin: '0px',
          padding: '0px',
          width: '100%',
          height: '100%'
        },
        id:null,
        form:{},
        spinning:false
      }
    },
    created() {
    },
    mounted() {
    },
    methods:{

      importFile(file){
        this.$refs.sheetTable.importFile(file)
      },
      /** 导入excel窗体关闭 */
      importExcelHandleCancel(e) {
        this.excelVisible = false
      },
      /** 导入excel窗体开启 */
      importExcelHandleOpen(e) {
        if(this.$refs.timeSelect.isCanUpload ===true){
          this.$message.warning('请选择日期后上传')
          return
        }
        this.excelVisible = true
      },
      exportExcelHandleOpen() {
        exportExcel(luckysheet.getAllSheets(), '泗水热电公司'+this.selectDate.split('-')[0]+'年'+this.selectDate.split('-')[1]+'月'+this.selectDate.split('-')[2]+'日'+'生产日报')
      },
      saveExcelHandleOpen(){
        this.$refs.sheetTable.handleSave()
      },
      setTime(time){
        this.selectDate = time
      },
      /** 上传文件确定按钮**/
      submitFile(file){

      },
      rendered(){
        console.log('渲染完成')
      },
      prievwExcel(data){
        this.excelData = data
      },
      fileLook(file){
        console.log('文件流'+file)
        this.excel = file
          this.excelVisible = false
      },
      queryExcel(){
        console.log('日期'+this.selectDate)
        this.spinning = true
        const params = {
          name:this.selectDate
        }
        getProductReport(params).then((res) => {
          this.form = res.data

          console.log('from:'+this.form)
          this.spinning = false
          if (res.data && res.data.content) {
            var jsarr = JSON.parse(res.data.content)
            this.$refs.sheetTable.createSheet(jsarr)
          } else {
            var data = [
              {
                index: 0,
                name: 'sheet1',
                data:[]
              },
            ]
            this.$refs.sheetTable.createSheet(data)
          }
        })
      },
      getExcelContent(id){

      },
      uploadExcel(form){
        this.spinning = true
        const formData={
          content:form,
          name:this.selectDate
        }
        saveProductReport(formData).then((response) => {
          this.spinning = false
          if (response.code === 200) {
            this.$refs.timeSelect.getStatusByMouth()
            this.$message.success(response.msg,1)
          }else{
            this.$message.error(response.msg,1)
          }
        }).catch(()=>{

        }).finally(()=>{
          this.spinning = false
        })
      }
    }
  }
</script>

<style scoped lang="scss">
 .card{
   height: calc(100vh - 100px);
   overflow: auto;
   padding:10px;
   overflow-x:hidden
 }

  ::v-deep .x-spreadsheet-scrollbar{
    height: 10px!important;
  }
</style>

左侧时间组件

<template>
  <div>
    <a-month-picker
      v-model="monthValue"
      format="YYYY-MM"
      :allowClear="false"
      picker="month"
      style="width:100%"
      @change="handleMonthValueChange" />
    <br>
    <a-tree
      :show-line="true"
      :show-icon="true"
      :default-expanded-keys="['0-0']"
      :data-source="treeData"
      @select="onSelect"
    >
      <a-icon slot="icon" type="carry-out" />
      <a-tree-node key="0-0" disabled>
        <a-icon slot="icon" type="carry-out"/>
        <span slot="title" style="color: #1890ff">{{ monthValue }}</span>
        <a-tree-node :key="key" :title="item.title" v-for="(item,key) in treeDataObject"
           :class="{ 'selected-node': item.flag == 'true'}">
          <a-icon slot="icon" type="carry-out" />
        </a-tree-node>
      </a-tree-node>
    </a-tree>
  </div>
</template>

<script>
  import moment from 'moment'
  import { getStatusByMouth, saveProductReport } from '@/api/report/reportInfo'
  export default {
    name: 'TimeSelectCommon',
    components:{},
    props:{},
    data(){
      return{
        monthValue: moment().format('YYYY-MM'), // 当前月份,初始值为当前月份
        treeData: [], // 树的数据,
        treeDataObject:null,
        isCanUpload:true,
        selectData:'',
        currentMonth:''//当前月份
      }
    },
    created() {
      this.$nextTick(()=>{
        // this.initTreeData()
        this.getStatusByMouth()
      })

    },
    mounted() {
      this.currentMonth = moment().format('MM')
      console.log('当前月份'+this.currentMonth)
    },
    methods:{
      //初始化日期
      getStatusByMouth(){
        const formData={
          yearAndMonth:this.monthValue
        }
        getStatusByMouth(formData).then((response) => {
          if (response.code === 200 && response.data) {
            this.treeDataObject = response.data
            const sortedData = this.treeDataObject.sort((a, b) => {
              const dateA = new Date(`2023-${a.title}`)
              const dateB = new Date(`2023-${b.title}`)
              return dateB.getTime() - dateA.getTime()
            })
            this.treeDataObject = sortedData
          }
        }).catch(()=>{

        }).finally(()=>{
        })
      },
      // 初始化树的数据
      initTreeData() {
        const { year, month } = this.getYearAndMonth(this.monthValue) // 获取年份和月份
        const daysInMonth = moment(`${year}-${month}`, 'YYYY-MM').daysInMonth() // 获取当月的天数
        const today = moment().format('DD') // 当前日期的天数
        const parentNode = { // 父节点数据
          title: `${year}-${month}`, // 父节点标题
          key: '0-0',
          children: [], // 子节点数据
        }
        //获取当前月份天数
        const date = daysInMonth
        if(this.currentMonth !== Number(month)){ //选中了其他月份
          for (let i = parseInt(date); i >= 1; i--) {
            const day = moment(`${year}-${month}-${i}`, 'YYYY-MM-DD').format('MM-DD') // 当前日期的格式化字符串
            const childNode = {
              title: day,
              key: `0-0-${i}`, // 设置唯一的子节点 key 值
            }
            parentNode.children.push(childNode) // 将后面插入的日期依次比当前日期小,倒序插入到数组的最后面
          }

        }else{
          for (let i = parseInt(today); i >= parseInt(today); i--) {
            const day = moment(`${year}-${month}-${i}`, 'YYYY-MM-DD').format('MM-DD') // 当前日期的格式化字符串
            const childNode = {
              title: day,
              key: `0-0-${i}`, // 设置唯一的子节点 key 值
            }

            parentNode.children.unshift(childNode) // 将当前天插入到数组的最前面
          }
          for (let i = parseInt(today) - 1; i >= 1; i--) {
            const day = moment(`${year}-${month}-${i}`, 'YYYY-MM-DD').format('MM-DD') // 当前日期的格式化字符串
            const childNode = {
              title: day,
              key: `0-0-${i}`, // 设置唯一的子节点 key 值
            }
            parentNode.children.push(childNode) // 将后面插入的日期依次比当前日期小,倒序插入到数组的最后面
          }
        }

        this.treeData = [parentNode] // 将父节点数据设置为树的数据
        this.treeDataObject = this.treeData[0] && this.treeData[0].children
        console.log('节点数据'+JSON.stringify(this.treeDataObject))
      },

      // 处理月份选择事件
      handleMonthValueChange(val) {
        this.monthValue = moment(val).format('YYYY-MM')
        this.getStatusByMouth()
        // this.initTreeData() // 根据新的月份重新初始化树的数据
      },
      // 获取年份和月份
      getYearAndMonth(dateString) {
        const [year, month] = dateString.split('-')
        return { year, month }
      },
      onSelect(selectedKeys, info) {
        this.isCanUpload = false
        this.selectData = moment(new Date()).format('YYYY') +'-'+info.selectedNodes[0].componentOptions.propsData.title
        this.$emit('setTime',this.selectData)
        this.$emit('queryExcel')

      },
    }
  }
</script>

<style scoped lang="scss">
  ::v-deep .selected-node span:nth-child(2) {
    background-color: #97c0ec;
  }
</style>

封装的lucksheet组件直接拿来使用

lucksheet组件 

<template>
  <div>
    <div :id="'luckysheet_content_' + id" :style="style"></div>
  </div>
</template>

<script>
import LuckyExcel from 'luckyexcel'
export default {
  name: 'ReportContent',
  components: {
  },
  data() {
    return {
      formOpen:false,//是否有窗口已打开
      id: null,
      loading: false,
      form: null,
      selCell: null,
      option: {
        title: '报表维护', // 设定表格名称
        lang: 'zh', // 设定表格语言
        plugins: [],
        showinfobar: false,
        showtoolbar: true,
      },
      style: {
        margin: '0px',
        padding: '0px',
        position: 'absolute',
        width: '100%',
        height: '100%'
      },
    }
  },
  filters: {},
  created() {
    const id = ''
    this.id = id
    this.option.container = 'luckysheet_content_' + id
    this.$emit('getExcelContent',this.id)
  },
  activated() {},
  mounted() {
    this.style.height = document.body.scrollHeight - 120 + 'px'
  },
  beforeDestroy() {
    console.log('报表配置销毁')
    luckysheet.destroy(console.log('报表展示销毁成功'))
  },
  computed: {},
  watch: {},
  methods: {
    importFile(file){
      console.log('上传文件'+file)
      LuckyExcel.transformExcelToLucky(file, (exportJson, luckysheetfile) => {
        if (exportJson.sheets === null || exportJson.sheets.length === 0) {
          this.$message.error('无法读取excel文件的内容,当前不支持xls文件!')
          return
        }
        luckysheet.destroy()
        this.option.data = exportJson.sheets
        luckysheet.create(this.option)
        //关掉弹窗
        this.$emit('closeModal')
      })
    },
    createSheet(data) {
      var that = this
      const hook = {
        save: function () {
          //保存
          // that.handleSave()
        },
        //单元格选中回调
        cellMousedown: function (cell, postion, sheetFile, ctx) {
          that.selCell = postion
          that.selCell.v = cell
          console.log(that.selCell)
        },
        workbookCreateAfter: function (book) {
          // for (let i = 0; i < that.tmpCellData.length; i++) {
          //   const cd = that.tmpCellData[i]
          // }
        },
      }
      this.option.hook = hook
      this.option.data = data
      // this.option.showtoolbarConfig = showtoolbarConfig
      luckysheet.create(this.option)
      this.selCell = {
        r: 0,
        c: 0,
      }
    },
    setFormula(formula) {
      luckysheet.setCellValue(formula.r, formula.c, formula.v)
    },
    onClose() {
      this.formOpen=false
    },
    handleSave() {
      const allSheets = luckysheet.getAllSheets()
      for (var i = 0; i < allSheets.length; i++) {
        if (allSheets[i].celldata.length > 0) {
          this.num = ++this.num
        }
        allSheets[i].data = []
        allSheets[i].scrollLeft = 0
        allSheets[i].scrollTop = 0
        allSheets[i].luckysheet_select_save = []
        allSheets[i].luckysheet_selection_range = []
      }
      if (this.num === 0) {
        this.$message.error('报表数据不能为空')
        return
      }
      var jsonStr = JSON.stringify(allSheets)
      this.$emit('uploadExcel',jsonStr)
    },
  },
}
</script>
<style lang="less">
.luckysheet * {
  box-sizing: initial;
  outline: 0 !important;

}

.luckysheet-row-count-show{
  line-height: 1;
}
.luckysheet-scrollbar-ltr{
  z-index: 99!important;
}
.luckysheet-wa-calculate-size {
  z-index: 99 !important;
}
.luckysheet-module-selection{
  .ant-input{
    border-color: #40a9ff;
    border-right-width: 1px !important;
    outline: 0;
    box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
  }

  .ant-select-selection {
    border-color: #40a9ff;
    border-right-width: 1px !important;
    outline: 0;
    box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
  }

  .has-error {
    .ant-select-selection, .ant-input {
      border-color: #ff4d4f;
      border-right-width: 1px !important;
      outline: 0;
      box-shadow: 0 0 0 2px rgba(245, 34, 45, 0.2);
    }
  }
}
.luckysheet-input-box {
  position: fixed;
}
.luckysheet-wa-editor{
  z-index: 99!important;
}
.luckysheet-stat-area{
  background-color: unset;
}
.model-style {
  .ant-layout {
    background: #ffffff00;
    .ant-card {
      background: #fff0;
      .ant-card-head {
        padding:0;
        .ant-card-head-title{
          padding:0;
          div{
            padding: 16px 24px;
          }
        }
        .ant-card-extra{
          padding:0;
          i{
            padding: 16px 24px;
          }
        }
      }
      .ant-card-actions {
        margin: 0;
        padding: 10px 16px;
        list-style: none;
        background: #FFFFFF00;
        border-top: 1px solid #e8e8e8;
        zoom: 1;
        li {
          float: right;
          width: auto !important;
          margin: 0 0 0 8px;
        }
      }
    }
  }
}

#data-link-module {
  max-height: 500px;
  overflow-y: auto;
}
</style>

文件导入组件

<template>
  <div class="clearfix">
    <a-upload-dragger
      :action="uploadImgUrl"
      accept=".xlsx"
      :show-upload-list="true"
      :headers="headers"
      :fileList="fileList"
      :custom-request="customRequest"
      @change="handleChange"
    >
      <p class="ant-upload-drag-icon">
        <a-icon type="inbox" />
      </p>
      <p class="ant-upload-text">
        请点击或者将文件拖拽到此处上传
      </p>
    </a-upload-dragger>
<!--    <div class="buttons" style="text-align: right;margin-top:10px">-->
<!--      <a-button type="primary" @click="handleConfirm">确定</a-button>-->
<!--      <a-button style="margin-left: 8px" @click="handleCancel">取消</a-button>-->
<!--    </div>-->
  </div>
</template>

<script>
import storage from 'store'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import moment from 'moment'
import axios from 'axios'
export default {
  name: 'ImageUploadDrg',
  props: {
    value: {
      type: String,
      default: ''
    },
    type: {
      type: String,
      default: 'image'
    },
    count: {
      type: Number,
      default: 1
    },
    date:{

    }
  },
  components: {
  },
  data () {
    return {
      loading: false,
      open: false,
      uploadImgUrl: process.env.VUE_APP_API_BASE_URL + '/file/uploadExcelByType',
      headers: {
        Authorization: 'Bearer ' + storage.get(ACCESS_TOKEN)
      },
      previewData:null,
      uploadedCount: 0,
      fileList:[]
    }
  },
  mounted () {
  },
  methods: {
    handleCancel () {
      this.$emit('closeModal')
    },
    customRequest({ file, onProgress, onSuccess, onError }) {
      console.log('==========='+file)
      this.fileList = []
      // const date = new Date().toLocaleDateString();
      const fileType = file.type.split('/')[1];
      const formData = new FormData();
      formData.append('file', file);
      formData.append('date', this.date);
      axios.post(this.uploadImgUrl, formData, {
        headers: this.headers,
        onUploadProgress: progressEvent => {
          const percent = Math.floor((progressEvent.loaded / progressEvent.total) * 100);
          onProgress({ percent });
          this.$emit('uploadFile',file)
        }
      })
        .then(response => {
          onSuccess(response.data);
        })
        .catch(error => {
          onError(error);
        });
    },
    async handleChange (info) {
      if (info.file.status === 'uploading') {
        this.loading = true
        return
      }
      if (info.file.status === 'done') {
        if (info.file.response.code !== 200) {
          this.$message.error('上传失败' + info.file.response.msg)
          this.loading = false
          return
        }
        this.loading = false
        this.$emit('input', info.file.response.url)
      }

    },

    handleConfirm(){
      this.$emit('submitFile')
    }

  }
}
</script>
<style lang="less" scoped>
img {
  width: 128px;
  height: 128px;
}

</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉默是金~

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值