1、Univer官网地址,安装与引用按照官网解释安装引入
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>