# 自定义打印

# 背景介绍

通常情况下,纷享销客中打印模板功能可以满足大多数的打印需求,但是像一些特殊的报表打印,打印模板便无能为力。

这篇文章教您如何开发自定义组件来定制打印页面。

# 书写template

大体内容就是用html去构建表格。

<template>
{    <div class="cus-demo" v-if="dData">
        <h1 class="text-center">资金使用审批表</h1>
        <div class="flex bd">
            <div class="flex flex-1 flex-col">
                <div class="flex">
                    <div class="w-120 text-center bd">收款单位</div>
                    <div class="flex-1 padding-5 bd">{{{ dData.field_uhcib__c }}}</div>
                </div>
                <div class="flex">
                    <div class="w-60 text-center bd">开户银行</div>
                    <div class="flex-1">
                        <div class="flex">
                            <div class="w-60 text-center bd">开户行</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_utckZ__c }}}</div>
                        </div>
                        <div class="flex">
                            <div class="w-60 text-center bd">账户</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_oQgRl__c }}}</div>
                        </div>
                        <div class="flex">
                            <div class="w-60 text-center bd">地址</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_v82bd__c }}}</div>
                        </div>
                    </div>
                </div>
                <div class="flex">
                    <div class="w-60 text-center bd">本次付款金额</div>
                    <div class="flex-1">
                        <div class="flex">
                            <div class="w-60 text-center bd">大写</div>
                            <div class="flex-1 padding-5 bd">{{{ digitUppercase(dData.field_8P18m__c) }}}</div>
                        </div>
                        <div class="flex">
                            <div class="w-60 text-center bd">小写</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_8P18m__c }}}</div>
                        </div>
                    </div>
                </div>
                <div class="flex">
                    <div class="w-120 text-center bd">付款方式</div>
                    <div class="flex-1 padding-5 bd">{{{ dData.field_fJY1p__c }}}</div>
                </div>
                <div class="flex">
                    <div class="flex-1 bd">
                        <div class="text-center">付款事由</div>
                        <div class="text-center padding-5">{{{ dData.field_fwjy1__c }}}</div>
                    </div>
                    <div class="flex-1 bd">
                        <div class="text-center">经办部门意见</div>
                        <div class="text-center padding-5">{{{ dData.field_36nKK__c }}}</div>
                    </div>
                    <div class="flex-1 bd">
                        <div class="text-center">项目负责人意见</div>
                        <div class="text-center padding-5">{{{ dData.field_J3N26__c }}}</div>
                    </div>
                </div>
            </div>
        </div>
        <div class="text-right no-print">点击浏览器的打印按钮直接进行打印(该文字不会被打印)</div>
    </div>
</template>

# 书写script

我们先来理一下思路:

  1. 获取当前详情的完整数据。这里依赖uiaction中的userData

  2. 获取当前详情所属业务对象的字段描述。这里依赖FxUI提供的api

  3. 有了数据和描述,我们可以解析出能够被用户看的懂的数据。这里依赖FxUI提供的api

根据上述的步骤,我们组织逻辑如下:

<script>
    export default {
        props: ['userData'], //上述提到的第一步。自定义组件是通过userData来接手uiaction中的数据
        data() {
            return {
                dData: null
            }
        },
        created() {
            this.getData();
        },
        mounted() {
            //触发弹窗位置的重新计算
            this.$emit('action', {type: 'resize'});
        },
        methods: {
            getData() {
                const userData = this.userData;
                //上述提到的第二步。获取对象描述
                FxUI.objectApi.fetch_describe(userData.object_describe_api_name).then((data) => {
                    const {
                        fields
                    } = data.objectDescribe;

                    //上述提到的第三步。解析数据
                    let result = {};
                    Object.keys(fields).forEach(fApiName => {
                        result[fApiName] = (FxUI.objectApi.format_field_value || PAAS.format_field_value)(fields[fApiName], userData[fApiName], userData);
                    })

                    //最后一步。赋值渲染
                    this.dData = result;
                    this.$nextTick(() => {
                        //数据渲染到页面后,会导致高度变化。此时我们需要让弹窗重新计算位置
                        this.$emit('action', {type: 'resize'});
                    })
                })
            }
        }
    }
</script>

另外,需求中有涉及金额大小写转换的场景,于是我们优化代码,在组件中提供相应的方法 digitUppercase,示例如下:

<script>
export default {
    props: ['userData', 'data'],
    data() {
        return {
            dData: null
        }
    },
    created() {
        this.getData();
    },
    mounted() {
        this.$emit('action', {type: 'resize'});
    },
    methods: {
        //金额小写转大写
        digitUppercase(n) {
            var fraction = ['角', '分'];
            var digit = [
                '零', '壹', '贰', '叁', '肆',
                '伍', '陆', '柒', '捌', '玖'
            ];
            var unit = [
                ['元', '万', '亿'],
                ['', '拾', '佰', '仟']
            ];
            var head = n < 0 ? '欠' : '';
            n = Math.abs(n);
            var s = '';
            for (var i = 0; i < fraction.length; i++) {
                s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');
            }
            s = s || '整';
            n = Math.floor(n);
            for (var i = 0; i < unit[0].length && n > 0; i++) {
                var p = '';
                for (var j = 0; j < unit[1].length && n > 0; j++) {
                    p = digit[n % 10] + unit[1][j] + p;
                    n = Math.floor(n / 10);
                }
                s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
            }
            return head + s.replace(/(零.)*零元/, '元')
                .replace(/(零.)+/g, '零')
                .replace(/^整$/, '零元整');
        },
        getData() {
            const userData = this.userData;
            //获取描述然后解析数据
            FxUI.objectApi.fetch_describe(userData.object_describe_api_name).then((data) => {
                const {
                    fields
                } = data.objectDescribe;
                let result = {};
                Object.keys(fields).forEach(fApiName => {
                    result[fApiName] = (FxUI.objectApi.format_field_value || PAAS.format_field_value)(fields[fApiName], userData[fApiName], userData);
                })
                this.dData = result;
                this.$nextTick(() => {
                    this.$emit('action', {type: 'resize'});
                })
            })
        }
    }
}
</script>

# 书写style

<style lang="less">
    .cus-demo {
        .demo {
            max-width: 1200px;
        }
        h1 {
            font-size: 28px;
            font-weight: bold;
            margin-bottom: 15px;
        }
        div {
            box-sizing: border-box;
            line-height: 34px;
        }
        .flex {
            display: flex;
        }
        .flex-1 {
            flex: 1;
        }
        .flex-col {
            flex-direction: column;
        }
        .w-120 {
            width: 120px;
        }
        .w-60 {
            width: 60px;
        }
        .text-center {
            text-align: center;
        }
        .text-right {
            text-align: right;
        }
        .padding-5 {
            padding: 0 10px;
        }
        .bd {
            border: 1px solid #ccc;
        }
        &--rotate {
            transform: rotate(90deg);
        }
    }
</style>

因为涉及打印,我们希望打印自定义组件中的内容,这里我们通过媒体查询的方式去屏蔽整个页面不需要打印的部分,代码优化如下:

注意:这种方式是个hack,@吴敬 会提供其他健康的方式来达到此目的,这里就不多阐述了。

<style lang="less">
    @media print {
        body {
            height: auto!important;
        }
        #crm-layout {
            display: none;
        }
        #app-portal {
            display: none;
        }
        .detail-dialog-v3 {
            display: none;
        }
        .el-dialog__header {
            display: none!important;
        }
        .f-qx-container {
            display: none;
        }
        .fx-dialog {
            margin-top: 0!important;
        }
        .no-print {
            display: none!important;
        }
    }
</style>

# 最终全部代码如下

<template>
    <div class="cus-demo" v-if="dData">
        <h1 class="text-center">资金使用审批表</h1>
        <div class="flex bd">
            <div class="flex flex-1 flex-col">
                <div class="flex">
                    <div class="w-120 text-center bd">收款单位</div>
                    <div class="flex-1 padding-5 bd">{{{ dData.field_uhcib__c }}}</div>
                </div>
                <div class="flex">
                    <div class="w-60 text-center bd">开户银行</div>
                    <div class="flex-1">
                        <div class="flex">
                            <div class="w-60 text-center bd">开户行</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_utckZ__c }}}</div>
                        </div>
                        <div class="flex">
                            <div class="w-60 text-center bd">账户</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_oQgRl__c }}}</div>
                        </div>
                        <div class="flex">
                            <div class="w-60 text-center bd">地址</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_v82bd__c }}}</div>
                        </div>
                    </div>
                </div>
                <div class="flex">
                    <div class="w-60 text-center bd">本次付款金额</div>
                    <div class="flex-1">
                        <div class="flex">
                            <div class="w-60 text-center bd">大写</div>
                            <div class="flex-1 padding-5 bd">{{{ digitUppercase(dData.field_8P18m__c) }}}</div>
                        </div>
                        <div class="flex">
                            <div class="w-60 text-center bd">小写</div>
                            <div class="flex-1 padding-5 bd">{{{ dData.field_8P18m__c }}}</div>
                        </div>
                    </div>
                </div>
                <div class="flex">
                    <div class="w-120 text-center bd">付款方式</div>
                    <div class="flex-1 padding-5 bd">{{{ dData.field_fJY1p__c }}}</div>
                </div>
                <div class="flex">
                    <div class="flex-1 bd">
                        <div class="text-center">付款事由</div>
                        <div class="text-center padding-5">{{{ dData.field_fwjy1__c }}}</div>
                    </div>
                    <div class="flex-1 bd">
                        <div class="text-center">经办部门意见</div>
                        <div class="text-center padding-5">{{{ dData.field_36nKK__c }}}</div>
                    </div>
                    <div class="flex-1 bd">
                        <div class="text-center">项目负责人意见</div>
                        <div class="text-center padding-5">{{{ dData.field_J3N26__c }}}</div>
                    </div>
                </div>
            </div>
        </div>
        <div class="text-right no-print">点击浏览器的打印按钮直接进行打印(该文字不会被打印)</div>
    </div>
</template>
<script>
export default {
    props: ['userData', 'data'],
    data() {
        return {
            dData: null
        }
    },
    created() {
        this.getData();
    },
    mounted() {
        this.$emit('action', {type: 'resize'});
    },
    methods: {
        //金额小写转大写
        digitUppercase(n) {
            var fraction = ['角', '分'];
            var digit = [
                '零', '壹', '贰', '叁', '肆',
                '伍', '陆', '柒', '捌', '玖'
            ];
            var unit = [
                ['元', '万', '亿'],
                ['', '拾', '佰', '仟']
            ];
            var head = n < 0 ? '欠' : '';
            n = Math.abs(n);
            var s = '';
            for (var i = 0; i < fraction.length; i++) {
                s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');
            }
            s = s || '整';
            n = Math.floor(n);
            for (var i = 0; i < unit[0].length && n > 0; i++) {
                var p = '';
                for (var j = 0; j < unit[1].length && n > 0; j++) {
                    p = digit[n % 10] + unit[1][j] + p;
                    n = Math.floor(n / 10);
                }
                s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
            }
            return head + s.replace(/(零.)*零元/, '元')
                .replace(/(零.)+/g, '零')
                .replace(/^整$/, '零元整');
        },
        getData() {
            const userData = this.userData;
            //获取描述然后解析数据
            FxUI.objectApi.fetch_describe(userData.object_describe_api_name).then((data) => {
                const {
                    fields
                } = data.objectDescribe;
                let result = {};
                Object.keys(fields).forEach(fApiName => {
                    result[fApiName] = (FxUI.objectApi.format_field_value || PAAS.format_field_value)(fields[fApiName], userData[fApiName], userData);
                })
                this.dData = result;
                this.$nextTick(() => {
                    this.$emit('action', {type: 'resize'});
                })
            })
        }
    }
}
</script>
<style lang="less">
    .cus-demo {
        .demo {
            max-width: 1200px;
        }
        h1 {
            font-size: 28px;
            font-weight: bold;
            margin-bottom: 15px;
        }
        div {
            box-sizing: border-box;
            line-height: 34px;
        }
        .flex {
            display: flex;
        }
        .flex-1 {
            flex: 1;
        }
        .flex-col {
            flex-direction: column;
        }
        .w-120 {
            width: 120px;
        }
        .w-60 {
            width: 60px;
        }
        .text-center {
            text-align: center;
        }
        .text-right {
            text-align: right;
        }
        .padding-5 {
            padding: 0 10px;
        }
        .bd {
            border: 1px solid #ccc;
        }
        &--rotate {
            transform: rotate(90deg);
        }
    }

    //该样式仅作demo使用,具体更合理的方式,请咨询@吴敬
    @media print {
        body {
            height: auto!important;
        }
        #crm-layout {
            display: none;
        }
        #app-portal {
            display: none;
        }
        .detail-dialog-v3 {
            display: none;
        }
        .el-dialog__header {
            display: none!important;
        }
        .f-qx-container {
            display: none;
        }
        .fx-dialog {
            margin-top: 0!important;
        }
        .no-print {
            display: none!important;
        }
    }
</style>

# 配置UI按钮

假设上述自定义组件的API Name是fund_approval_form__c,UI按钮绑定的自定义函数代码如下:

Map objectData = context.data as Map

UIAction uiaction = OpenDialogAction.build(){
    title = "资金使用审批表"
    width = "750px"
    component {
        apiName = "fund_approval_form__c"
    }
    
    userData = objectData
}
return uiaction

# 实际的展示效果

<video src="https://a9.fspage.com/FSR/uipaas/demo-print.mov" style="width: 100%;" controls></video>
显示代码 复制代码
lastUpdate: 5/3/2023, 11:40:26 PM