Vue3+Ts+ElementPlus使用Univer实现在线编辑Excel(刚开始学习,记录下)

1、Univer官网地址,安装与引用按照官网解释安装引入

https://univer.ai/zh-cn/

2、有可能在使用时会出现样式引用报错,可以使用CDN方式在index.html中引入

    <link rel="stylesheet" href="https://unpkg.com/@univerjs/design/lib/index.css" />
    <link rel="stylesheet" href="https://unpkg.com/@univerjs/ui/lib/index.css" />
    <link rel="stylesheet" href="https://unpkg.com/@univerjs/docs-ui/lib/index.css" />
    <link rel="stylesheet" href="https://unpkg.com/@univerjs/sheets-ui/lib/index.css" />
    <link rel="stylesheet" href="https://unpkg.com/@univerjs/sheets-formula/lib/index.css" />
    <link rel="stylesheet" href="https://unpkg.com/@univerjs/sheets-numfmt/lib/index.css" />

3、创建一个customTable组件,在其内部定义一些方法与变量方便父子组件来回调用与传参,创建组件的意义在于可实现嵌套项目应用Univer初始化不报错


<template>
    
    <!-- <div id="app"> -->
                    <div ref="container" class="univer-container" style="flex:7;height: 100%;" ></div>
                    <!-- <div ref="container" class="univer-container" style="width:100%;height: calc(80vh);position: relative;"></div> -->
                <!-- </div> -->
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, toRaw, nextTick ,onBeforeUnmount, onUpdated} from 'vue'
import type { ElTable, TabsPaneContext } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import Utils, { GetData } from '../../../../http/tongyong'
import { useRouter } from 'vue-router';
import moment, { months } from 'moment'


// import "@univerjs/design/lib/index.css";
// import "@univerjs/ui/lib/index.css";
// import "@univerjs/docs-ui/lib/index.css";
// import "@univerjs/sheets-ui/lib/index.css";
// import "@univerjs/sheets-formula/lib/index.css";

import { Univer } from "@univerjs/core";
import { defaultTheme } from "@univerjs/design";

import { UniverFormulaEnginePlugin } from "@univerjs/engine-formula";
import { UniverRenderEnginePlugin } from "@univerjs/engine-render";

import { UniverUIPlugin } from "@univerjs/ui";

import { UniverDocsPlugin } from "@univerjs/docs";
import { UniverDocsUIPlugin } from "@univerjs/docs-ui";

import { UniverSheetsPlugin } from "@univerjs/sheets";
import { UniverSheetsFormulaPlugin } from "@univerjs/sheets-formula";
import { UniverSheetsUIPlugin } from "@univerjs/sheets-ui";

import { defineProps } from 'vue'
import { IWorkbookData } from "@univerjs/core";
import { FUniver } from "@univerjs/facade";
const props = defineProps({
    list: Array,
})
const data: IWorkbookData = <any>{};
const container = ref()
const univer = new Univer({
    theme: defaultTheme,
});
const univerAPI = ref()
const activeWorkbook = ref()
// const univerAPI = FUniver.newAPI(univer);
    // univerAPI.getActiveWorkbook()
//     univerAPI.onCommandExecuted((command) => {
//   // Press "Ctrl + Shift + I" to open the console and do some operations then you can see the command
//   console.log(command);
// });
onMounted(async () => {
    //在线编辑表格插件
    console.log('自定义表格组件初始化')

    univer.registerPlugin(UniverRenderEnginePlugin);
    univer.registerPlugin(UniverFormulaEnginePlugin);

    univer.registerPlugin(UniverUIPlugin, {
    container: container.value,
    header: true,
    footer: true,
    });

    univer.registerPlugin(UniverDocsPlugin, {
    hasScroll: false,
    });
    univer.registerPlugin(UniverDocsUIPlugin);

    univer.registerPlugin(UniverSheetsPlugin);
    univer.registerPlugin(UniverSheetsUIPlugin);
    univer.registerPlugin(UniverSheetsFormulaPlugin);

    univer.createUniverSheet({});
    univerAPI.value = FUniver.newAPI(univer);
    activeWorkbook.value = univerAPI.value.getActiveWorkbook();
    // 监听单元格命令
    univerAPI.value.onCommandExecuted((command) => {
        // Press "Ctrl + Shift + I" to open the console and do some operations then you can see the command
        const { id, type, params } = command;
        // console.log(command,'命令')
    });
    //赋值数据
    // univerAPI.value.executeCommand('sheet.command.set-range-values', {
    // value: { v: "Hello, Univer!" },
    // range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 }
    // });
    //监听选区变化
    //selection参数解释 endRow:结束行号,startRow:起始行号,endColumn:结束列号,startColumn:起始列号,
    activeWorkbook.value.onSelectionChange((selection) => {
        // console.log(selection,'选区变化')
        if(selection.length > 0){
            //获取当前激活的工作表
            const nowSheet = activeWorkbook.value.getActiveSheet();
            //获取当前工作表的取值
            const activeSheet = univerAPI.value.getActiveWorkbook().getActiveSheet();
            //起始行号、起始列号、长宽
            const range = activeSheet.getRange(selection[0].startRow, selection[0].startColumn, selection[0].endRow, selection[0].endColumn);
            const value = range.getValue();
            setParentMethod({selection:selection,value:value,sheetId:nowSheet._worksheet._sheetId})
        }
    });
})
onBeforeUnmount(() => {
    console.log('自定义表格组件销毁')
    univer.dispose();
});

//获取工作簿数据
const childMethodGetData = () => {
    // console.log('获取工作薄数据')
    if(activeWorkbook.value){
        // const activeWorkbook = univerAPI.getActiveWorkbook()
        const saveData = activeWorkbook.value.getSnapshot();
        return saveData;
    }
}
//赋值给某个单元格数据
const childMethodSetCellsData = (e:string) => {
    const activeSheet = univerAPI.value.getActiveWorkbook().getActiveSheet();
    const range = activeSheet.getRange(0, 0, 0, 0);
    // range.setValue(e);
    range.setValues([
        [{v: 1, t: 2, s: 'o188Hb'}, { v: 'B1' }],
        [{ v: 'A2' }, { v: 'B2' }],
    ]);
}

//点击单元格传给父级
const emits = defineEmits(["setParentMethod"])
const setParentMethod = (a) => {
    emits("setParentMethod", a)
}
// 暴露方法和属性给父组件
defineExpose({childMethodGetData,childMethodSetCellsData})
</script>

4、注意在初始化的时候,container.value:是自定义的组件变量,此方式会跟随父级组件大小;也可以用【app】,此方法会直接全屏显示,对项目使用路由的不太友好

univer.registerPlugin(UniverUIPlugin, {
    container: container.value,//或者'app'
    header: true,
    footer: true,
    });

5、创建一个父级页面,在其内部实现父级调用子级方法相互传参功能,注意高宽必须给出否则初始化失败


<template>
    <el-card>
    <template #header>
        <div class="card-header">
            <el-form :inline="true" :model="form" class="demo-form-inline" ref="ruleFormRef" size="small" label-position="top">
                <el-form-item label="报表名称" prop="name" style="width: 240px;">
                    <el-input v-model="form.name"  />
                </el-form-item>
                <el-form-item label="">
                    <!-- <el-button size="small" type="primary" style="margin-top: 24px;">导入现有模板</el-button> -->
                    <input type="file" @change="handleFileUpload">
                </el-form-item>
                <el-form-item label="分组名称" prop="fenzuName">
                    <el-input v-model="form.fenzuName" />
                </el-form-item>
                <el-form-item label="显示位置" prop="weizhi" style="width: 60px;">
                    <el-input v-model="form.weizhi" />
                </el-form-item>
                <el-form-item label="备注" prop="remark"  style="width: 240px;">
                    <el-input v-model="form.remark" />
                </el-form-item>
                <el-form-item label="">
                    <el-button size="small" type="primary" style="margin-top: 24px;" @click="submit">保存</el-button>
                
                </el-form-item>
                
            </el-form>
        </div>
    </template>
    <el-container style="height: calc(80vh);">
        <el-aside width="300px">
            <div class="CellsDataBox">
                <strong>单元格数据:[{{formCells.startRow}},{{formCells.startColumn}}]</strong>
                <div class="addCellsDate">
                    <el-button size="small" circle  type="primary" :icon="Plus" @click="addCells"></el-button>
                </div>
            </div>

            <div style="padding:10px;" v-for="(item,key) in CellsLists" :key="key">
                    <table>
                        <tr>
                            <td class="td1">内容属性</td>
                            <td  class="td3">
                                <el-icon style="margin-right:10px;" class="tableIcon" @click="EditCells(item)"><Edit /></el-icon>
                                <el-popconfirm title="确定要删除吗?" @confirm="CellsDataDel(item)" confirm-button-text="是"
                                    cancel-button-text="否" >
                                    <template #reference>
                                        <el-icon class="tableIcon"><Delete /></el-icon>
                                    </template>
                                </el-popconfirm>
                            </td>
                        </tr>
                        <tr>
                            <td class="td1">数据名称</td>
                            <td  class="td2">
                                {{item.name}}
                                <el-icon class="tableIcon" @click="CellsCopy('标题2')"><CopyDocument /></el-icon>
                            </td>
                        </tr>
                        <tr>
                            <td class="td1">科目名称</td>
                            <td  class="td2">{{item.kemu}}</td>
                        </tr>
                        <tr>
                            <td class="td1">取值字段</td>
                            <td  class="td2">{{item.ziduan}}</td>
                        </tr>
                        <tr>
                            <td class="td1">取值时效</td>
                            <td class="td2">{{item.shixiao}}</td>
                        </tr>
                    </table>
            </div>


        </el-aside>
        <el-main>
            
            <div id="app" :style="{height:contentHeight}">
                <customTableVue :list="[]" ref="childRef" @setParentMethod="setParentMethod"></customTableVue>
            </div>
        </el-main>
    </el-container>

  </el-card>
    <el-dialog
    v-model="cellsVisible"
    title="单元格数据"
    width="400"
    :before-close="cellsClose"
  >
    <el-form :model="form" class="form" :rules="rulesCells" ref="ruleFormRefCells" size="small"
        label-position="top">
            <el-row :gutter="20">
                <el-col :span="24" >
                    <el-form-item label="数据名称" prop="name">
                        <el-input v-model="formCells.name" />
                    </el-form-item>
                </el-col>
                <el-col :span="24" >
                    <el-form-item label="科目" prop="kemu">
						<el-select v-model="formCells.kemu" filterable placeholder="请选择" size="small">
							<el-option v-for="item in kemuOptions" :key="item.id" :label="item.subjectFullName"
								:value="item.id" />
						</el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12" >
                    <el-form-item label="取数字段" prop="ziduan">
						<el-select v-model="formCells.ziduan" filterable placeholder="请选择" size="small">
							<el-option v-for="item in ziduanOptions" :key="item.name" :label="item.name"
								:value="item.name" />
						</el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12" >
                    <el-form-item label="取数时效" prop="shixiao">
						<el-select v-model="formCells.shixiao" filterable placeholder="请选择" size="small">
							<el-option v-for="item in shixiaoOptions" :key="item.name" :label="item.name"
								:value="item.name" />
						</el-select>
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="cellsVisible = false">取消</el-button>
        <el-button type="primary" @click="cellsSave">
          确定
        </el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, toRaw, nextTick ,onBeforeUnmount, onUpdated, watch} from 'vue'
import { ElMessage, type ElTable, type TabsPaneContext } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import Utils, { GetData, TongYongGetCaiWu } from '../../../http/tongyong'
import { useRouter } from 'vue-router';
import moment, { months } from 'moment'

import {
    Plus,
} from '@element-plus/icons-vue'
import customTableVue from './components/customTable.vue';


const router = useRouter()
const { currentRoute } = useRouter();
const route = currentRoute.value;

const ruleFormRef = ref<FormInstance>()
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const form = reactive({
    name: "",
    fenzuName:"",
    weizhi: '0',
    remark: '',
})
// endRow:结束行号,startRow:起始行号,endColumn:结束列号,startColumn:起始列号
const formCells = reactive({
    name: "",
    kemu:0,
    ziduan: "余额",
    shixiao: "期末",
    startRow:0,
    endRow:0,
    startColumn:0,
    endColumn:0,
    sheetId:0
})
const rulesCells = reactive<FormRules>({
    name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
    kemu: [{ required: true,pattern: /^[1-9]*[1-9][0-9]*$/, message: '请选择科目' }],
    ziduan: [{ required: true, message: '请选择取数字段', trigger: 'blur' }],
    shixiao: [{ required: true, message: '请选择取数时效', trigger: 'blur' }],
})

const kemuOptions= ref<any>([])
const ziduanOptions = ref<any>([])
const shixiaoOptions = ref<any>([])
const getSelect = async () =>{
    kemuOptions.value = [{ id: 0, subjectFullName: '请选择科目' }]
    const res = await TongYongGetCaiWu("/financial/subject/voucher/select?showAll=true") as any
    if(res.success){
        if(res.data.length>0){
            kemuOptions.value = kemuOptions.value.concat(res.data as [])
        }
    }
    ziduanOptions.value = [{name:'发生额'},{name:'借方发生额'},{name:'贷方发生额'},{name:'余额'},{name:'借方余额'},{name:'贷方余额'}]
    shixiaoOptions.value = [{name:'期初'},{name:'期末'},{name:'年初'}]
}

//表格
const contentHeight=ref()

const univerAPI = ref()
onMounted(async () => {
    getSelect()
})
const AllCellsLists = ref<any>([])
const CellsLists = ref<any>([])
//单元格数据操作
const cellsVisible = ref(false)
const isEdit=ref(false)
const addCells = () =>{
    isEdit.value = false;
    formCells.name = '';
    formCells.kemu = 0;
    formCells.ziduan = '余额';
    formCells.shixiao = '期末';
    cellsVisible.value = true;
}
const cellsClose = () =>{
    cellsVisible.value = false;
}
//保存单元格数据
const cellsSave = () =>{
    if(!formCells.name){
        ElMessage({
            message: '请输入数据名称',
            type: 'warning',
        })
        return false;
    }
    if(!formCells.kemu || formCells.kemu == 0){
        ElMessage({
            message: '请选择科目',
            type: 'warning',
        })
        return false;
    }
    // if(childRef.value){
    let a = {
        name:formCells.name,
        kemu:formCells.kemu,
        ziduan:formCells.ziduan,
        shixiao:formCells.shixiao,
        startRow:formCells.startRow,
        endRow:formCells.endRow,
        startColumn:formCells.startColumn,
        endColumn:formCells.endColumn,
        sheetId:formCells.sheetId
    }
    if(isEdit.value){
        AllCellsLists.value.forEach((item:any,index:number)=>{
            if(item.startRow == formCells.startRow && item.endRow == formCells.endRow && item.startColumn == formCells.startColumn && item.endColumn == formCells.endColumn && item.sheetId == formCells.sheetId){
                AllCellsLists.value.splice(index,1,a)
            }
        })
    }else{
        AllCellsLists.value.push(a)
    }
    console.log(AllCellsLists.value,'单元格数据总列表')
    // }
    //筛选单元格数据
    getCellsData()
    cellsVisible.value = false;
}
//编辑保存单元格数据
const EditCells = async (row:any) => {
    isEdit.value = true;
    formCells.name = row.name;
    formCells.kemu = row.kemu;
    formCells.ziduan = row.ziduan;
    formCells.shixiao = row.shixiao;
    formCells.startRow = row.startRow;
    formCells.endRow = row.endRow;
    formCells.startColumn = row.startColumn;
    formCells.endColumn = row.endColumn;
    cellsVisible.value = true;
}
//删除单元格数据
const CellsDataDel = async (row:any) => {
    let newlist = <any>[];
    AllCellsLists.value.forEach((item:any,index:number)=>{
        if(item.startRow == row.startRow && item.endRow == row.endRow && item.startColumn == row.startColumn && item.endColumn == row.endColumn && item.sheetId == row.sheetId && item.name == row.name){

        }else{
            newlist.push(item)
        }
    })
    AllCellsLists.value = newlist;
    //筛选单元格数据
    getCellsData()
}
const CellsCopy = (t:string) => {
    navigator.clipboard.writeText(t).then(
        () => 
        ElMessage({
            message: '复制成功!',
            type: 'success',
        }),
        (error) => ElMessage({
            message: '复制失败,'+error,
            type: 'warning',
        })
    );
}

//定义子组件
const childRef = ref()
//保存整个表格
const submit = async () => {
    if(childRef.value){
        let tableData = childRef.value.childMethodGetData();
        //组装表格数据
        if(form.name && form.name != ''){
            let a = {
                name:form.name,
                fenzuName:form.fenzuName,
                weizhi:form.weizhi,
                remark:form.remark,
                cells:AllCellsLists.value,
                tableData: tableData
            }
            console.log(a,'表格数据')
        }else{
            ElMessage({
                message: '请先输入报表名称',
                type: 'warning',
            })
        }
    }
}

///接收子组件传过来的
const setParentMethod = async (command: any) => {
    // console.log(command,'command接收子组件传过来的值')
    formCells.startRow = command.selection[0].startRow
    formCells.startColumn = command.selection[0].startColumn
    formCells.endRow = command.selection[0].endRow
    formCells.endColumn = command.selection[0].endColumn
    formCells.sheetId = command.sheetId
    getCellsData()
}
//筛选单元格数据
const getCellsData = async () => {
    CellsLists.value = [];
    // console.log(AllCellsLists.value,'总数据')
    AllCellsLists.value.forEach((item:any,index:number)=>{
        if(item.startRow == formCells.startRow && item.startColumn == formCells.startColumn && item.endRow == formCells.endRow&& item.endColumn == formCells.endColumn && item.sheetId == formCells.sheetId){
            CellsLists.value.push(item)
        }
    })
    // console.log(CellsLists.value,'筛选出来的单元格数据')
}


const handleFileUpload = (event) => {
      const file = event.target.files[0];
      
      // 使用 FileReader 对象读取文件内容示例
      const reader = new FileReader();
      reader.onload = (e) => {
        const fileContent = e.target?.result;
        console.log(fileContent)
        // 处理文件内容
      };
    //   console.log(file)
    //   reader.readAsText(file);
    }
</script>
<style lang="scss" scoped>
// .univer-container {
//   width: 100%;
//   height: 100%;
//   overflow: hidden;
// }

// /* Also hide the menubar */
// :global(.univer-menubar) {
//   display: none;
// }
.CellsDataBox{
    background-color: rgb(245, 246, 247);
    border-bottom: 1px solid rgb(224, 226, 228);
    border-top: 1px solid rgb(224, 226, 228);
    padding: 9px;
    font-size: 14px;
}
.addCellsDate{
    float: right;
}
table {
        // border-left: 1px solid #dadada;
        // border-top: 1px solid #dadada;
        // border-bottom: 1px solid #dadada;
        // padding: 0;
        // border-spacing: 0;
        border-collapse: collapse;
        border:1px solid #000;
        width:100%;
}
th, td {
  border: 1px solid #000; /* 设置单元格的边框为单线,颜色为黑色 */
}
.td1{
    width:80px;
    font-size:14px;
    font-weight:600;
    text-align:center;
}
.td2{
    font-size:14px;
    text-align:left;
}
.td3{
    font-size:14px;
    text-align:right;
}
.tableIcon:hover{
    color:red;
    cursor: pointer;
}
</style>

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要安装Vue3、TypeScriptElement Plus。 安装命令: ``` npm install vue@next vue-router@next typescript@latest --save-dev npm install element-plus --save ``` 接下来,创建一个Vue3 TypeScript项目,并在`main.ts`中引入Element Plus。 ```typescript import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; import ElementPlus from 'element-plus'; import 'element-plus/lib/theme-chalk/index.css'; const app = createApp(App); app.use(router); app.use(ElementPlus); app.mount('#app'); ``` 在`App.vue`中,创建一个表单,包括车牌号码和设备号码两个输入框,并添加一个绑定按钮。 ```html <template> <div class="app"> <form class="form"> <el-form-item label="车牌号码"> <el-input v-model="carNumber"></el-input> </el-form-item> <el-form-item label="设备号码"> <el-input v-model="deviceNumber"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="bindDevice">绑定</el-button> </el-form-item> </form> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; export default defineComponent({ name: 'App', setup() { const carNumber = ref(''); const deviceNumber = ref(''); const bindDevice = () => { // TODO: 实现绑定设备的逻辑 }; return { carNumber, deviceNumber, bindDevice, }; }, }); </script> ``` 在`bindDevice`方法中,可以调用API将车辆和设备进行绑定。这里使用mock数据模拟API请求。 ```typescript const bindDevice = () => { // TODO: 实现绑定设备的逻辑 console.log(`车牌号码:${carNumber.value},设备号码:${deviceNumber.value}`); // mock API请求 setTimeout(() => { console.log('绑定成功!'); carNumber.value = ''; deviceNumber.value = ''; }, 2000); }; ``` 最后,运行项目即可看到界面和绑定设备的功能。 完整代码如下: ```html <template> <div class="app"> <form class="form"> <el-form-item label="车牌号码"> <el-input v-model="carNumber"></el-input> </el-form-item> <el-form-item label="设备号码"> <el-input v-model="deviceNumber"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="bindDevice">绑定</el-button> </el-form-item> </form> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; export default defineComponent({ name: 'App', setup() { const carNumber = ref(''); const deviceNumber = ref(''); const bindDevice = () => { console.log(`车牌号码:${carNumber.value},设备号码:${deviceNumber.value}`); // mock API请求 setTimeout(() => { console.log('绑定成功!'); carNumber.value = ''; deviceNumber.value = ''; }, 2000); }; return { carNumber, deviceNumber, bindDevice, }; }, }); </script> <style scoped> .app { display: flex; justify-content: center; align-items: center; height: 100vh; } .form { width: 400px; } </style> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值