文章目录
一、前言
之前写过一个 bootstrap 版本的,但这次实现在APP上,就只有重写前端,后端也得变一变。
思路 用tab-bar 实现sheet的切换,兼容图片显示。
难点是 uni-table的后置渲染在v-html里并不上很顺利。

二、后端(.net8)
csharp
/// <summary>
/// 表格
/// </summary>
public static string excel(string filePath)
{
IWorkbook workbook;
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
if (Path.GetExtension(filePath) == ".xls")
workbook = new HSSFWorkbook(fs); // 处理 Excel 2003
else
workbook = new XSSFWorkbook(fs); // 处理 Excel 2007+
}
StringBuilder htmlBuilder = new StringBuilder();
for (int i = 0; i < workbook.NumberOfSheets; i++)
{
ISheet sheet = workbook.GetSheetAt(i);
htmlBuilder.AppendFormat(sheet.SheetName + ",");
}
htmlBuilder.Append(" [temp]");
for (int i = 0; i < workbook.NumberOfSheets; i++)
{
htmlBuilder.Append($"<table id='dtable{i}' border >");
ISheet sheet = workbook.GetSheetAt(i);
//标记插入图片后导致的位移
List<tude> InImg = new List<tude>();
//最大列、最大行
int maxcol = 0; int maxrow = sheet.LastRowNum;
//图片不占单元格,但是需要循环到位置
if (sheet is XSSFSheet xssfSheet2)
{
if (xssfSheet2.GetDrawingPatriarch() != null)
{
foreach (var drawing in xssfSheet2.GetDrawingPatriarch().GetShapes())
{
if (drawing is XSSFPicture picture)
{
int m1 = picture.ClientAnchor.Col2;
int m2 = picture.ClientAnchor.Row2;
if (m1 > maxcol) maxcol = m1;
if (m2 > maxrow) maxrow = m2;
}
}
}
}
for (int x = 0; x < maxrow; x++)//行
{
IRow row = sheet.GetRow(x);
htmlBuilder.Append("<tr>");
if (x == 0)//没图片就取第一行列数
{ if (row.LastCellNum > maxcol) maxcol = row.LastCellNum; }
for (int j = 0; j < maxcol; j++)//列
{
ICell cell; string cellValue;
if (row != null)
{
cell = row.GetCell(j);
cellValue = cell?.ToString() ?? "";
}
else cellValue = "";
// 检查单元格是否包含图片
bool hasImage = false;
if (sheet is XSSFSheet xssfSheet)
{
if (xssfSheet.GetDrawingPatriarch() != null)
{
foreach (var drawing in xssfSheet.GetDrawingPatriarch().GetShapes())
{
int nowrow = 0; int nowcol = 0;
if (drawing is XSSFPicture picture)
{
if (picture.ClientAnchor.Col1 == j && picture.ClientAnchor.Row1 == x)
{
byte[] imageBytes = ((XSSFPicture)drawing).PictureData.Data;
string base64Image = Convert.ToBase64String(imageBytes);
string imageFormat = ((XSSFPicture)drawing).PictureData.MimeType.Split('/')[1];
cellValue = $"<img src='data:image/{imageFormat};base64,{base64Image}'/>";
hasImage = true;
nowrow = picture.ClientAnchor.Row2 - picture.ClientAnchor.Row1;//图片占几行
nowcol = picture.ClientAnchor.Col2 - picture.ClientAnchor.Col1;//图片占列行
//标记坐标,解决多行多列导致的位移
for (int a = picture.ClientAnchor.Row1; a <= picture.ClientAnchor.Row2; a++)//行
{
for (int b = picture.ClientAnchor.Col1; b <= picture.ClientAnchor.Col2; b++)//列
{
tude t = new tude();
t.xx = b;//列 x
t.yy = a;//行 y
InImg.Add(t);
}
}
if (hasImage)
htmlBuilder.AppendFormat("<td rowspan='{1}' colspan='{2}' >{0}</td>", cellValue, nowrow, nowcol);
}
}
}
}
}
if (!hasImage)
{
if (InImg.Where(p => p.xx == j & p.yy == x).ToList().Count > 0) { }//坐标校对 是否被图片占用
else
{
htmlBuilder.AppendFormat("<td>{0}</td>", cellValue);
}
}
}
htmlBuilder.Append("</tr>");
}
//整个sheet没有内容,只有图
if (maxcol == 0 && maxrow == 0)
{
if (sheet is XSSFSheet xssfSheet)
{
foreach (var drawing in xssfSheet.GetDrawingPatriarch().GetShapes())
{
htmlBuilder.Append("<tr>");
if (drawing is XSSFPicture picture)
{
byte[] imageBytes = ((XSSFPicture)drawing).PictureData.Data;
string base64Image = Convert.ToBase64String(imageBytes);
string imageFormat = ((XSSFPicture)drawing).PictureData.MimeType.Split('/')[1];
string cellValue = $"<img src='data:image/{imageFormat};base64,{base64Image}'/>";
htmlBuilder.AppendFormat("<td>{0}</td>", cellValue);
}
htmlBuilder.Append("</tr>");
}
}
}
htmlBuilder.Append("</table>");
htmlBuilder.Append("[item]");
}
return htmlBuilder.ToString();
}
三、前端(uniapp)
typescript
<template>
<view class="tabs">
<scroll-view id="tab-bar" class="scroll-h" :scroll-x="true" :show-scrollbar="false"
:scroll-into-view="scrollInto">
<view v-for="(tab,index) in tabBars" :key="index" class="uni-tab-item" :id="index-1" :data-current="index"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==index ? 'uni-tab-item-title-active' : ''">{{tab}}</text>
</view>
</scroll-view>
<view class="line-h"></view>
<view class="swiper-box" >
<view class="swiper-item " v-for="(tab,index1) in contents" :key="index1">
<scroll-view class="uni-container" :scroll-x="true" :scroll-Y="true" v-show="index1==tabIndex" >
<view v-html="tab" class="uni-table" ></view> <!-- 动态渲染内容 -->
</scroll-view>
</view>
</view>
</view>
</template>
<script >
export default {
data() {
return {
contents: [],
cacheTab: [],
tabIndex: 0,
tabBars: [],
scrollInto: "",
showTips: false,
navigateFlag: false,
pulling: false,
refreshIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAB5QTFRFcHBw3Nzct7e39vb2ycnJioqK7e3tpqam29vb////D8oK7wAAAAp0Uk5T////////////ALLMLM8AAABxSURBVHja7JVBDoAgDASrjqj//7CJBi90iyYeOHTPMwmFZrHjYyyFYYUy1bwUZqtJIYVxhf1a6u0R7iUvWsCcrEtwJHp8MwMdvh2amHduiZD3rpWId9+BgPd7Cc2LIkPyqvlQvKxKBJ//Qwq/CacAAwDUv0a0YuKhzgAAAABJRU5ErkJggg=="
}
},
onLoad(o) {
//let data = "D:\\file\\3.xlsx"
let url=o.url;
this.LoadFile(url)
},
onNavigationBarButtonTap(e) {
this.navTo('scan');
},
onBackPress(options) {
this.navTo(this.$assist.gdata("mypage"))
// 阻止默认返回行为
return true;
},
methods: {
ontabtap(e) {
let index = e.target.dataset.current || e.currentTarget.dataset.current;
this.switchTab(index);
},
async navTo(url) {
this.$mRouter.push({
route: url
});
},
/*
ontabchange(e) {
let index = e.target.current || e.detail.current;
this.switchTab(index);
},
*/
switchTab(index) {
if (this.tabIndex === index) {
return;
}
this.tabIndex = index;
//this.scrollInto = this.tabBars[index].id;
console.log(this.tabIndex)
}, //加载表格
LoadFile(dataurl) {
let obj = {};
obj.str = dataurl;
obj.uid=this.$assist.gdata("user").id;
const toData = JSON.stringify(obj);
const params = {
"object": "File",
"method": "EXCEL",
"data": toData,
}
this.$request({
data: params
}).then((res) => {
if (res.data == 'noroot') {
this.$assist.showNo(this.$t("file.t1"));
} else if (res.data == 'nosrc') {
this.$assist.showNo(this.$t("file.t2"));
} else {
let content = res.data;
this.$nextTick(() => {
this.tabBars = content.split('[temp]')[0].split(',');
this.contents = content.split('[temp]')[1].split('[item]');
})
}
}, err => {
this.$assist.showNo(this.$t("login.t5"));
})
},
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-PLUS */
page {
width: 100%;
min-height: 100%;
display: flex;
}
/* #endif */
.tabs {
flex: 1;
flex-direction: column;
overflow: hidden;
background-color: #ffffff;
/* #ifndef APP-PLUS */
height: 100vh;
/* #endif */
}
.scroll-h {
// width: 750rpx;
width: 100%;
height: 80rpx;
flex-direction: row;
white-space: nowrap;
/* flex-wrap: nowrap; */
/* border-color: #cccccc;
border-bottom-style: solid;
border-bottom-width: 1px; */
}
.line-h {
height: 1rpx;
background-color: #cccccc;
}
.uni-tab-item {
display: inline-block;
flex-wrap: nowrap;
padding-left: 34rpx;
padding-right: 34rpx;
}
.uni-tab-item-title {
color: #555;
font-size: 30rpx;
height: 80rpx;
line-height: 80rpx;
flex-wrap: nowrap;
/* #ifndef APP-PLUS */
white-space: nowrap;
/* #endif */
}
.uni-tab-item-title-active {
color: #007AFF;
}
.swiper-box {
flex: 1;
}
uni-swiper{
height: 550px !important;
}
.uni-container{
height: 550px !important;
}
/* .swiper-item {
flex: 1;
flex-direction: row;
} */
.scroll-v {
flex: 1;
/* #ifndef MP-ALIPAY */
flex-direction: column;
/* #endif */
width: 750rpx;
width: 100%;
}
/* 手动添加 uni-table 样式 */
::v-deep .uni-table table {
width: 200%;
border: 1px solid #d7d9df ;
border-collapse: collapse;
}
.uni-table th,
::v-deep .uni-table td {
border: 0px ;
border-left: 1px solid #d7d9df ;
border-bottom: 1px solid #d7d9df ;
padding: 8px;
}
</style>