silianpan

silianpan 查看完整档案

成都编辑  |  填写毕业院校某小公司  |  技术Leader 编辑 silianpan.cn 编辑
编辑

专注于web前端,spring boot,微服务架构。坚持原创技术分享,为开源贡献力量。

个人动态

silianpan 发布了文章 · 2020-10-17

跨平台文件在线预览解决方案(四)-Android和IOS原生插件

引言

前面写了多篇关于<跨平台文件在线预览解决方案>,不管使用pdf.js、LibreOffice,还是永中DCS,各自都有自己的优缺点,比如:pdf.js不支持双指放大缩小,LibreOffice加载缓慢,永中DCS收费等等。

本文基于uni-app平台实现了Office文档在线预览原生插件Seal-OfficeOnline,同时支持Android和iOS,欢迎下载使用~

Seal-OfficeOnline插件下载使用地址

<div class="half">

</div>

快速上手

demo工程地址

开发工具:HBuilderX

Step1. 下载demo工程,使用HBuilderX打开

Step2. 下载本文插件

插件名称:Seal-OfficeOnline

下载插件解压放置到项目根目录nativeplugins下,如图:

Step3. 选择manifest.json->App原生插件配置中加载本地插件

Step4. 使用插件

  • 在vue或nvue组件中,导入插件
var testModule = uni.requireNativePlugin("Seal-OfficeOnline")
  • 使用openFile方法预览Office文件,支持如下格式:pdf、txt、doc、docx、xls、xlsx、ppt、pptx
testModule.openFile({
    url: 'http://113.62.127.199:8090/fileUpload/1.xlsx',
    topBarBgColor: '#3394EC',
    topBarTextColor: '#FFFFFF',
    title: 'Office文档在线预览',
    isBackArrow: false,
    fileType: 'xlsx',
    fileName: '1'
});

openFile方法参数说明

url

url参数支持如下三种地址方式:

  • 文件网络地址,如:http://113.62.127.199:8090/fileUpload/1.xlsx
  • 手机本地文件地址,如:/data/user/0/com.HBuilder.UniPlugin/files/1.xlsx
  • 文件名,如:1.xlsx,访问默认目录文件,默认目录为:/data/user/0/com.HBuilder.UniPlugin

注意:手机本地地址目录需要有权限访问

title

title表示顶栏文本,默认为:SealOfficeOnline

topBarBgColor

topBarBgColor表示顶栏背景颜色,默认为:#177cb0(靛青)

topBarTextColor

topBarTextColor表示顶栏文本颜色,默认为:#FFFFFF(白色)

isBackArrow

isBackArrow表示是否显示返回按钮,默认为:true(显示)

fileType

fileType表示可以指定文件类型,如:xlsx,在url参数无法判断文件类型时,可以指定文件类型

fileName

fileName可以指定文件名,如:file1,注意此处不带文件扩展名,如果同时指定fileName和fileType,那么最后的文件名通过这两个参数组合起来,即:fileName.fileType

Android预览效果

预览docx

预览pptx

预览xlsx

预览pdf

预览txt

iOS预览效果

预览docx

预览pptx

预览xlsx

预览pdf

预览txt

转载请注明:我的技术分享 » 跨平台文件在线预览解决方案(四)-Android和IOS原生插件

查看原文

赞 0 收藏 0 评论 0

silianpan 发布了文章 · 2020-09-14

基于AntV G2实现一个通用可视化Vue插件

前言

AntV G2坚持自然、确定性、意义感、生长性的设计价值观。与其他可视化插件不同的是,G2是以数据驱动的高交互可视化图形语法,具有高度的易用性和可扩展性。

随着业务可视化不断发展,数据复杂度越来越高。实现一个通用的可视化插件越来越迫切。本文基于G2实现了一个可视化Vue插件——p-charts

提示:p-charts是基于AnV G2 v3.x最新版本,因为v3.x版本文档比较成熟。后续会紧跟v4.x版本进行升级。

目前,p-charts只实现了p-pie(饼图)、p-bar(条形图)、p-column(柱状图)、p-line-chart(折线图)。后续会对常用其他图形进行扩展。

案例

点这里

快速开始

安装

npm install -S p-charts
# or
yarn add p-charts

使用

// main.js
import PCharts from 'p-charts'
Vue.use(PCharts)

案例:饼图(p-pie)

<template>
  <p-pie
    :data="PieJson"
    :options="options"
    ref="pieRef"
    @pie-title-click="handleTitleClick"
    @pie-label-click="handleLabelClick"
  />
</template>

<script>
import PieJson from './data/pie1.json'
export default {
  data() {
    return {
      PieJson,
      options: {
        fieldMap: {
          time: 'year',
          name: 'budgetSubject',
          // 统计指标,可以更换
          value: 'budgetNum'
        },
        title: `总收入和总支出占比情况-预算数(单位:万元)`,
        colorList: ['#1890ff', '#37c661']
      }
    }
  },
  methods: {
    updateData() {
      this.$refs.pieRef.initData()
    },
    handleTitleClick() {
      console.log('title-click')
    },
    handleLabelClick(data) {
      console.log('label-click', data)
    }
  }
}
</script>

实现原理

本文以p-pie饼图实现为例。

选项和数据

p-charts的选项和数据采用props传递。组件中定义默认选项配置

defaultOptions: {
  // 标题
  title: '',
  // chart本身属性:width,height等
  chartProps: {
    height: 400
  },
  tooltipProps: {
    showTitle: false
  },
  // 图形半径
  radius: 0.7,
  // 图形颜色列表
  colorList: [],
  // 字段映射
  fieldMap: {
    name: 'budgetSubject',
    value: 'budgetFinalNum'
  },
  // 值单位
  valueUnit: '万元'
}

mouted生命周期中组合最新的选项

this.newOptions = { ...this.defaultOptions, ...this.options }

数据转换

数据转换采用@antv/data-set进行转换,转换方式非常多,比如:数据过滤、字段过滤、数据加工、数据排序、获取子集、数据补全、数据百分比、数据统计、数据分箱等,详细说明及使用参考Transform

本文p-charts数据转换主要采用两种:数据加工数据百分比

  • 数据转换:数据加工对数据字段值为null或undefined的值进行归0处理。
  • 数据百分比:数据百分比转换为类型percent,统计每项数据的百分比。
const ds = new DataSet()
const dv = ds.createView().source(data)
dv.transform({
  type: 'map',
  callback(row) {
    if (
      row[valueOp] === null ||
      row[valueOp] === undefined ||
      isNaN(row[valueOp])
    ) {
      row[valueOp] = 0
    }
    return row
  }
}).transform({
  type: 'percent',
  field: valueOp,
  dimension: nameOp,
  as: 'percent'
})

分组聚合

因为p-pie(饼图)没有分组聚合,所以本次以p-column(柱状图)为例说明,案例demo-column3

分组聚合配置为:

// 组合字段(分组字段)
groupField: 'name',
// 条目字段(明细字段、图例字段)
itemField: 'time',
// 字段映射
fieldMap: {
  time: 'year',
  name: 'budgetSubject',
  value: 'budgetFinalNum'
}
  • fieldMap:主要用于字段映射,基本图形主要有三个字段。

    • 时间线字段time:主要用于坐标轴(柱状图X轴,条形图Y轴)单项或分组这里的time字段不一定表示时间,有可能是其他字段,如部门,参考:案例demo-column3
    • 名称字段name:主要用于图例或数据说明字段
    • 值字段value:表示哪个字段的值
  • groupField:分组字段,主要用于组合分组,如demo-column3根据部门字段分组。这里的配置值是fieldMap的key,而不是value
  • itemField:条目字段(图例字段),表示数据值的字段说明,如demo-column3中每一项名称的支出。同样,这里的配置值是fieldMap的key,而不是value

根据以上说明,demo-column3配置如下:

分组:根据fieldMap中的time字段分组,即根据部门(govDept)分组;
条目字段为fieldMap中的name字段,即预算科目(subject);
值字段为:subtotal,即数据值。

groupField: 'time',
itemField: 'name',
fieldMap: {
  time: 'govDept',
  name: 'subject',
  value: 'subtotal'
},

根据以上配置原则,demo-column2配置如下:

groupField: 'name',
itemField: 'time',
fieldMap: {
  time: 'outType',
  name: 'subject',
  value: 'outValue'
}

demo-bar2条形图,根据年份分组如下:

fieldMap: {
  time: 'year',
  name: 'budgetSubject',
  value: 'budgetNum'
}

数据字段key重命名

为了让图例说明更加明确,需要对图例字段进行映射说明。

foldFields: ['subtotal', 'outBase', 'outProject'],
foldFieldsRename: {
  subtotal: '小计',
  outBase: '基本支出',
  outProject: '项目支出'
}
  • foldFields:主要重命名的字段
  • foldFieldsRename:重命名映射

重命名采用数据转换类型为fold(数据展开),参考p-column,配置如下:

dv.transform({
  type: 'fold',
  fields: foldFieldsOp,
  key: this.newOptions.fieldMap[this.newOptions.itemField],
  value: valueOp
})

案例地址demo-column2

其他选项说明

值单位valueUnit

值单位主要用于提示信息值的计量单位。默认值为“万元”、“个”或“人”

颜色列表colorList

colorList: ['#ff786b', '#0fdd7e', '#bc9dfb', '#ff2e6a', '#7effa2', '#e57ec0', '#2c818f', '#ff7730']

图形基本选项chartProps

图形基本选项有:height、width、padding、background等,具体参考文档Chart图表

chartProps: {
  height: 500,
  padding: [50, 120, 100, 120]
}

图例配置选项legendProps

图例配置选项有:position、title、background等,具体参考文档Legend图例

legendProps: {
  position: 'bottom'
}

提示信息配置选项tooltipProps

图例配置选项有:showTitle、offset、itemTpl、crosshairs等,参考文档Tooltip提示信息

tooltipProps: {
  showTitle: true
}

数据调整选项adjustProps

参考文档

adjustProps: [
  {
    type: 'dodge',
    marginRatio: 1 / 32
  }
]

图形文本配置选项labelProps

图形文本选项配置有:offset、textStyle等,参考文档Label 图形文本

labelProps: {
  textStyle: {
    fill: '#0050b3',
    fontSize: '12',
    // fontWeight: 'bold',
    rotate: -60
  }
}

X坐标轴配置选项xFieldProps

坐标轴Axis选项配置有:标题title、坐标轴线line、文本label、刻度线tickLine、网格grid等,参考文档Axis坐标轴

默认配置如下:

xFieldProps: {
  label: {
    textStyle: {
      rotate: 16
    }
  }
}

Y坐标轴配置选项yFieldProps

同上,默认配置如下:

yFieldProps: {
  line: {
    lineWidth: 2,
    lineDash: [3, 3]
  },
  label: {
    textStyle: {
      fill: '#aaaaaa'
    }
  },
  title: {
    offset: 60
  }
}

数据转换配置选项transformProps

数据转换配置类型非常多,参考本文章节“数据转换”,参考文档Transform数据转换

默认配置为空:

transformProps: []

p-map组件

p-map组件是基于AntV L7封装的行政区划填充图,参考AntV L7

目前只支持CountryLayer(中国地图),后续会继续完善优化,支持各省市区县地图

配置选项

场景Scene配置选项chartProps

参考文档场景Scene

默认配置如下:

chartProps: {
  map: new Mapbox({
    center: [116.2825, 39.9],
    pitch: 0,
    style: 'blank',
    zoom: 3,
    minZoom: 0,
    maxZoom: 10
  })
}
字段映射配置fieldMap

参考本文章节“分组聚合”,默认配置如下:

fieldMap: {
  name: 'name',
  value: 'num'
},

合作交流

作者博客,欢迎合作与交流,有兴趣的同学欢迎加入我们。提交issues

总结

p-charts是基于AntV G2的Vue插件,大大提升了数据可视化工作效率,后续会不断扩展壮大。

转载请注明:我的技术分享 » 基于AntV G2实现一个通用可视化Vue插件

查看原文

赞 0 收藏 0 评论 0

silianpan 发布了文章 · 2020-09-09

跨平台文件在线预览解决方案(三)- LibreOffice vs OpenOffice

前言

突然有一天在业务系统中发现OpenOffice转换word为pdf时,出现个别中文字丢失以及格式发生变化。这在业务系统中预览合同等重要附件是致命的。Google了半天也没找到问题所在。于是采用LibreOffice进行转换,看看转换效果。office文件在线预览原理一样,先转换成pdf,然后采用pdf. js预览。

问题复现

原word文件


OpenOffice转换后pdf文件


链接

快速开始

下载

地址

下载最新版本,目前是7.0.1

下载三个包:

安装

  • 主包
cd LibreOffice_7.0.1.2_Linux_x86-64_rpm/RPMS
yum localinstall -y *.rpm
  • SDK
cd LibreOffice_7.0.1.2_Linux_x86-64_rpm_sdk/RPMS
yum localinstall -y *.rpm
  • 语言包
cd LibreOffice_7.0.1.2_Linux_x86-64_rpm_langpack_zh-CN
yum localinstall -y *.rpm

启动

/opt/libreoffice7.0/program/soffice --headless --accept="socket,host=127.0.0.1,port=8101;urp;" --nofirststartwizard > /opt/server/libre.log 2>&1 &
# 自启动
将以上命令添加到/etc/rc.local中

测试

libreoffice7.0 --headless --invisible --convert-to pdf 原office文件 --outdir 输出目录

Java集成

安装jar

<dependency>
    <groupId>org.jodconverter</groupId>
    <artifactId>jodconverter-local</artifactId>
    <version>4.3.0</version>
</dependency>

配置文件libre.properties

在resources目录下新建libre.properties

# LibreOffice主目录
libreOfficeHome=/opt/libreoffice7.0
# 开启多个LibreOffice进程,每个端口对应一个进程
# portNumbers=2002,2003
portNumbers=8101
# 任务执行超时为5分钟
taskExecutionTimeoutMinutes=5
# 任务队列超时为1小时
taskQueueTimeoutHours=1

读取配置,并连接LibreOffice服务

新建类:OfficeManagerInstance.java

package cn.sccl.common.config;

import org.jodconverter.core.office.OfficeManager;
import org.jodconverter.local.office.LocalOfficeManager;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Properties;

@Component
public class OfficeManagerInstance {
    private static OfficeManager INSTANCE = null;

    public static synchronized void start() {
        officeManagerStart();
    }

    @PostConstruct
    private void init() {
        try {
            Properties properties = PropertiesLoaderUtils.loadAllProperties("libre.properties");
            String[] portNumbers = properties.getProperty("portNumbers", "").split(",");
            int[] ports = new int[portNumbers.length];

            for (int i = 0; i < portNumbers.length; i++) {
                ports[i] = Integer.parseInt(portNumbers[i]);
            }

            LocalOfficeManager.Builder builder = LocalOfficeManager.builder().install();
            builder.officeHome(properties.getProperty("libreOfficeHome", ""));
            builder.portNumbers(ports);
            builder.taskExecutionTimeout(Long.parseLong(properties.getProperty("taskExecutionTimeoutMinutes", "")) * 1000 * 60); // minute
            builder.taskQueueTimeout(Long.parseLong(properties.getProperty("taskQueueTimeoutHours", "")) * 1000 * 60 * 60); // hour

            INSTANCE = builder.build();
            officeManagerStart();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void officeManagerStart() {
        if (INSTANCE.isRunning()) {
            return;
        }

        try {
            INSTANCE.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

工具类LibreOfficeUtil

新建工具类:LibreOfficeUtil.java

package cn.sccl.common.util;

import cn.sccl.common.config.OfficeManagerInstance;
import org.jodconverter.local.JodConverter;

import java.io.File;

public class LibreOfficeUtil {
    /**
     * 利用 JodConverter 将 Offfice 文档转换为 PDF(要依赖 LibreOffice),该转换为同步转换,返回时就已经转换完成
     */
    public static boolean convertOffice2PDFSyncIsSuccess(File sourceFile, File targetFile) {
        try {
            OfficeManagerInstance.start();
            JodConverter.convert(sourceFile).to(targetFile).execute();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    /**
     * 利用 LibreOffice 将 Office 文档转换成 PDF,该转换是异步的,返回时,转换可能还在进行中,转换是否有异常也未可知
     * @param filePath       目标文件地址
     * @param targetFilePath 输出文件夹
     * @return 子线程执行完毕的返回值
     */
    public static int convertOffice2PDFAsync(String filePath, String fileName, String targetFilePath) throws Exception {
        String command;
        int exitStatus;
        String osName = System.getProperty("os.name");
        String outDir = targetFilePath.length() > 0 ? " --outdir " + targetFilePath : "";

        if (osName.contains("Windows")) {
            command = "cmd /c cd /d " + filePath + " && start soffice --headless --invisible --convert-to pdf ./" + fileName + outDir;
        } else {
            command = "libreoffice6.3 --headless --invisible --convert-to pdf:writer_pdf_Export " + filePath + fileName + outDir;
        }

        exitStatus = executeOSCommand(command);
        return exitStatus;
    }

    /**
     * 调用操作系统的控制台,执行 command 指令
     * 执行该方法时,并没有等到指令执行完毕才返回,而是执行之后立即返回,返回结果为 0,只能说明正确的调用了操作系统的控制台指令,但执行结果如何,是否有异常,在这里是不能体现的,所以,更好的姿势是用同步转换功能。
     */
    private static int executeOSCommand(String command) throws Exception {
        Process process;
        process = Runtime.getRuntime().exec(command); // 转换需要时间,比如一个 3M 左右的文档大概需要 8 秒左右,但实际测试时,并不会等转换结束才执行下一行代码,而是把执行指令发送出去后就立即执行下一行代码了。

        int exitStatus = process.waitFor();

        if (exitStatus == 0) {
            exitStatus = process.exitValue();
        }

        // 销毁子进程
        process.destroy();
        return exitStatus;
    }
}

使用方法

boolean ret = LibreOfficeUtil.convertOffice2PDFSyncIsSuccess(sourceFile, pdfFile);

效果对比

效果明显比OpenOffice效果好,就转换速率来说,差不多时间,这个就没有详细计算了。

中文乱码问题解决

不管是用LibreOffice还是OpenOffice,都会遇到中文乱码问题。统一解决办法如下:

环境:CentOS7

  • 查看字体目录vim /etc/fonts/fonts.conf,默认在/usr/share/fonts/
  • 安装中文字体
mkdir /usr/share/fonts/chinese
cd /usr/share/fonts/chinese
# 将windows字体拷贝到该目录,如下:搜索“简体”,拷贝所有

chmod 755 *.TTF
chmod 755 *.TTC
# (如果提示 mkfontscale: command not found,需自行安装 # yum install mkfontscale)
mkfontscale

mkfontdir

# (如果提示 fc-cache: command not found,则需要安装# yum install fontconfig )
fc-cache -fv

# 安装完成,重启LibreOffice或OpenOffice服务

赞助作者,互相交流

image
image

转载请注明:我的技术分享 » 跨平台文件在线预览解决方案(三)- LibreOffice vs OpenOffice

查看原文

赞 0 收藏 0 评论 0

silianpan 赞了文章 · 2020-09-08

基于unoconv的在线office预览

方案选择

这几天在搞在线文档预览,网上查了几种方案,

  • 第一种:使用google的在线预览 -> 国内被Q,pass

  • 第二种:使用第三方的,比如:永中dcs -> 要钱,pass

  • 第三种:先转换为pdf,在使用pdf在线预览插件预览 -> 对服务器负担比较大。

  • 第四种:自己写解析库 -> 我这实力,开玩笑呢?

查了查,也就第三种可行。不过我之前都是做的简单的web开发,想