|
@@ -1,5 +1,6 @@
|
|
|
<template>
|
|
<template>
|
|
|
<view class="page-wrappe">
|
|
<view class="page-wrappe">
|
|
|
|
|
+ <cus-header title=' ' bgColor="transparent"></cus-header>
|
|
|
<view id="pdfContainer" class="pdf-container" :style="{'transform':'scale('+scale+')', 'height': containerScaledHeight + 'px'}">
|
|
<view id="pdfContainer" class="pdf-container" :style="{'transform':'scale('+scale+')', 'height': containerScaledHeight + 'px'}">
|
|
|
<!-- 封面 -->
|
|
<!-- 封面 -->
|
|
|
<view class="cd_box fm2 adffc" style="margin-top: 20px;height: 868px;">
|
|
<view class="cd_box fm2 adffc" style="margin-top: 20px;height: 868px;">
|
|
@@ -89,6 +90,7 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
<!-- 多维度 -->
|
|
<!-- 多维度 -->
|
|
|
|
|
+ <canvas type="2d" id="table-canvas" canvas-id="table-canvas" class="offscreen-canvas"></canvas>
|
|
|
<template v-if="reportData&&reportData.dimensionAnalysis&&reportData.dimensionAnalysis.length">
|
|
<template v-if="reportData&&reportData.dimensionAnalysis&&reportData.dimensionAnalysis.length">
|
|
|
<view class="cd_box adffc" style="border: none;" v-for="(item,index) in reportData.dimensionAnalysis" :key="index">
|
|
<view class="cd_box adffc" style="border: none;" v-for="(item,index) in reportData.dimensionAnalysis" :key="index">
|
|
|
<view class="v2-top adfacjb" :style="{'background-image':'url('+'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+typeDict[item.title]+'_title_bg.png)'}">
|
|
<view class="v2-top adfacjb" :style="{'background-image':'url('+'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+typeDict[item.title]+'_title_bg.png)'}">
|
|
@@ -100,32 +102,32 @@
|
|
|
<img class="vb-img2" :src="'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+typeDict[item.title]+'_box_img2.png'">
|
|
<img class="vb-img2" :src="'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+typeDict[item.title]+'_box_img2.png'">
|
|
|
<view class="v2-p2">{{ item.desc }}</view>
|
|
<view class="v2-p2">{{ item.desc }}</view>
|
|
|
<view class="v2-p2" style="margin-top: 16px;">评分总体分布</view>
|
|
<view class="v2-p2" style="margin-top: 16px;">评分总体分布</view>
|
|
|
- <view class="vb-table" :style="{'border':'1px solid '+item.bcolor,'margin-top':'22px'}">
|
|
|
|
|
- <view class="vbt-th adfac" :class="{'black':item.title==='目的与动机'}" :style="{'background':item.thcolor}">
|
|
|
|
|
- <view class="vbtt-w1">主题</view>
|
|
|
|
|
- <view class="vbtt-w2">最低分</view>
|
|
|
|
|
- <view class="vbtt-w2">平均分</view>
|
|
|
|
|
- <view class="vbtt-w2">最高分</view>
|
|
|
|
|
- <view class="vbtt-w3">问卷陈述</view>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="vbt-pre adfac" v-for="i in 5" :key="item">
|
|
|
|
|
- <view class="vbtp-left vbtt-w1 adfacjc" :class="{'black':item.title==='目的与动机'}" :style="{'background':item.titlecolor,'padding':'0 16px'}">{{ '宗旨共融同心共识' }}</view>
|
|
|
|
|
- <view class="vbtp-num vbtt-w2" :style="{'border-bottom':'1px solid '+item.bcolor}">{{ 3 }}</view>
|
|
|
|
|
- <view class="vbtp-num vbtt-w2 green" :style="{'border-bottom':'1px solid '+item.bcolor}">{{ 8 }}</view>
|
|
|
|
|
- <view class="vbtp-num vbtt-w2" :style="{'border-bottom':'1px solid '+item.bcolor}">{{ 1 }}</view>
|
|
|
|
|
- <view class="vbtp-desc" :style="{'border-bottom':'1px solid '+item.bcolor}">
|
|
|
|
|
- <view class="vbtpd-title">{{ '团队成员能够共同阐述其共享目的,并且在团队使命上保持高度一致。' }}</view>
|
|
|
|
|
- <view class="xr_tb adfac">
|
|
|
|
|
- <view class="xt_pre p1"></view>
|
|
|
|
|
- <view class="xt_pre p2"></view>
|
|
|
|
|
- <view class="xt_pre p3"></view>
|
|
|
|
|
- <view class="xt_score adfac" :style="{'left':(4*2)+'%','width':((25-4)*2)+'%'}">
|
|
|
|
|
- <view class="xts_num red">{{ 4 }}</view>
|
|
|
|
|
- <view class="xts_box"></view>
|
|
|
|
|
- <view class="xts_num green">{{ 25 }}</view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
|
|
+ <view class="vb-table" :style="{'border':'1px solid '+item.bcolor,'margin-top':'22px'}">
|
|
|
|
|
+ <view class="vbt-th adfac" :class="{'black':item.title==='目的与动机'}" :style="{'background':item.thcolor}">
|
|
|
|
|
+ <view class="vbtt-w1">主题</view>
|
|
|
|
|
+ <view class="vbtt-w2">最低分</view>
|
|
|
|
|
+ <view class="vbtt-w2">平均分</view>
|
|
|
|
|
+ <view class="vbtt-w2">最高分</view>
|
|
|
|
|
+ <view class="vbtt-w3">问卷陈述</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="vbt-pre adfac" v-for="(tableRow, rowIndex) in tableDataSource" :key="rowIndex">
|
|
|
|
|
+ <view class="vbtp-left vbtt-w1 adfacjc" :class="{'black':item.title==='目的与动机'}" :style="{'background':item.titlecolor,'padding':'0 16px'}">{{ tableRow.theme }}</view>
|
|
|
|
|
+ <view class="vbtp-num vbtt-w2" :style="{'border-bottom':'1px solid '+item.bcolor}">{{ tableRow.minScore }}</view>
|
|
|
|
|
+ <view class="vbtp-num vbtt-w2 green" :style="{'border-bottom':'1px solid '+item.bcolor}">{{ tableRow.avgScore }}</view>
|
|
|
|
|
+ <view class="vbtp-num vbtt-w2" :style="{'border-bottom':'1px solid '+item.bcolor}">{{ tableRow.maxScore }}</view>
|
|
|
|
|
+ <view class="vbtp-desc" :style="{'border-bottom':'1px solid '+item.bcolor}">
|
|
|
|
|
+ <view class="vbtpd-title">{{ tableRow.statement }}</view>
|
|
|
|
|
+ <view class="xr_tb adfac">
|
|
|
|
|
+ <view class="xt_pre p1"></view>
|
|
|
|
|
+ <view class="xt_pre p2"></view>
|
|
|
|
|
+ <view class="xt_pre p3"></view>
|
|
|
|
|
+ <view class="xt_score adfac" :style="{'left':(tableRow.range[0]*2)+'%','width':((tableRow.range[1]-tableRow.range[0])*2)+'%'}">
|
|
|
|
|
+ <view class="xts_num red">{{ tableRow.range[0] }}</view>
|
|
|
|
|
+ <view class="xts_box"></view>
|
|
|
|
|
+ <view class="xts_num green">{{ tableRow.range[1] }}</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
@@ -147,6 +149,7 @@
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <view class="pdf_btn" @click="createPdf">生成PDF</view>
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -166,6 +169,56 @@
|
|
|
components:{ lEchart },
|
|
components:{ lEchart },
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
|
|
+ tableDataSource: [
|
|
|
|
|
+ {
|
|
|
|
|
+ theme: '宗旨共融同心共识',
|
|
|
|
|
+ minScore: 1,
|
|
|
|
|
+ avgScore: 8,
|
|
|
|
|
+ maxScore: 1,
|
|
|
|
|
+ statement: '团队成员能够共同阐述其共享目的,并且在团队使命上保持高度一致。',
|
|
|
|
|
+ range: [4, 25] // 范围条的数据
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ theme: '价值引领行践合一',
|
|
|
|
|
+ minScore: 3,
|
|
|
|
|
+ avgScore: 1,
|
|
|
|
|
+ maxScore: 4,
|
|
|
|
|
+ statement: '团队经常根据其愿景、使命、目的和价值观来评估它们所做的事情及自身的行为。',
|
|
|
|
|
+ range: [8, 30]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ theme: '使命驱动热忱贡献',
|
|
|
|
|
+ minScore: 5,
|
|
|
|
|
+ avgScore: 12,
|
|
|
|
|
+ maxScore: 1,
|
|
|
|
|
+ statement: '团队对它们为实现宗旨和愿景所面临的挑战与目标充满热情,并相信它们的工作为世界带来积极的贡献。',
|
|
|
|
|
+ range: [10, 40]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ theme: '团队优先人尽其才',
|
|
|
|
|
+ minScore: 12,
|
|
|
|
|
+ avgScore: 5,
|
|
|
|
|
+ maxScore: 1,
|
|
|
|
|
+ statement: '团队成员(包括领导者)将团队优先事项置于个人优先事务之上,并在分配工作任务时充分发挥每个人的优势。',
|
|
|
|
|
+ range: [5, 22]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ theme: '审时度势与时俱进',
|
|
|
|
|
+ minScore: 1,
|
|
|
|
|
+ avgScore: 23,
|
|
|
|
|
+ maxScore: 1,
|
|
|
|
|
+ statement: '团队定期(每隔数月)审视目标与优先事项,以确保它们能够适应外部环境的变化。',
|
|
|
|
|
+ range: [15, 35]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ theme: '快乐工作具成就感',
|
|
|
|
|
+ minScore: 1,
|
|
|
|
|
+ avgScore: 21,
|
|
|
|
|
+ maxScore: 1,
|
|
|
|
|
+ statement: '团队成员(包括领导者)对所做的工作以及与同事共事感到快乐并从中获得成就感',
|
|
|
|
|
+ range: [4, 24]
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
reportData: null,
|
|
reportData: null,
|
|
|
isChartReady: false,
|
|
isChartReady: false,
|
|
|
scale:1,
|
|
scale:1,
|
|
@@ -223,7 +276,8 @@
|
|
|
color: '#0096D8'
|
|
color: '#0096D8'
|
|
|
}
|
|
}
|
|
|
],
|
|
],
|
|
|
- };
|
|
|
|
|
|
|
+ pdfImages:[],
|
|
|
|
|
+ };
|
|
|
},
|
|
},
|
|
|
mounted() {
|
|
mounted() {
|
|
|
// reportData.value = props.reportData;
|
|
// reportData.value = props.reportData;
|
|
@@ -244,13 +298,282 @@
|
|
|
]
|
|
]
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-
|
|
|
|
|
this.calculateScaleAndPosition();
|
|
this.calculateScaleAndPosition();
|
|
|
uni.onWindowResize(() => {
|
|
uni.onWindowResize(() => {
|
|
|
this.calculateScaleAndPosition();
|
|
this.calculateScaleAndPosition();
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ async createPdf(){
|
|
|
|
|
+ uni.showLoading({
|
|
|
|
|
+ title:'正在生成PDF所需的图片...'
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ const ztzdfxImgPromise = this.downloadZtzdfxImg();
|
|
|
|
|
+ const dimensionImagePromises = this.reportData.dimensionAnalysis.map(d => {
|
|
|
|
|
+ return this.generateTableImage(d,this.tableDataSource);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const allImageUrls = await Promise.all([
|
|
|
|
|
+ ztzdfxImgPromise,
|
|
|
|
|
+ ...dimensionImagePromises
|
|
|
|
|
+ ]);
|
|
|
|
|
+ this.pdfImages = allImageUrls;
|
|
|
|
|
+
|
|
|
|
|
+ uni.hideLoading();
|
|
|
|
|
+ this.$showToast(`生成成功,共计${this.pdfImages.length}张`);
|
|
|
|
|
+ console.log(this.pdfImages);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ uni.hideLoading();
|
|
|
|
|
+ console.error('生成图片过程中发生错误:', error);
|
|
|
|
|
+ uni.showToast({ title: '生成图片失败,请重试', icon: 'none' });
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @description 使用 Canvas 绘制表格并生成图片
|
|
|
|
|
+ * @param {Object} dimensionData 维度数据
|
|
|
|
|
+ * @param {Array} tableData 表格数据
|
|
|
|
|
+ * @returns {Promise<string>} 返回生成的图片临时文件路径
|
|
|
|
|
+ */
|
|
|
|
|
+ generateTableImage(dimensionData, tableData) {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ const query = uni.createSelectorQuery().in(this);
|
|
|
|
|
+ query.select('#table-canvas')
|
|
|
|
|
+ .fields({ node: true, size: true })
|
|
|
|
|
+ .exec(async (res) => {
|
|
|
|
|
+ if (!res || !res[0] || !res[0].node) {
|
|
|
|
|
+ return reject('获取Canvas节点失败');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const canvasNode = res[0].node;
|
|
|
|
|
+ const ctx = canvasNode.getContext('2d');
|
|
|
|
|
+ const dpr = uni.getSystemInfoSync().pixelRatio;
|
|
|
|
|
+
|
|
|
|
|
+ // --- 1. 定义布局和尺寸常量
|
|
|
|
|
+ const TABLE_WIDTH = 548;
|
|
|
|
|
+ const HEADER_HEIGHT = 38;
|
|
|
|
|
+ const ROW_HEIGHT = 49; // 行高固定为 49px
|
|
|
|
|
+ const FONT_FAMILY = 'sans-serif';
|
|
|
|
|
+ const COL_WIDTHS = { theme: 72, min: 49, avg: 49, max: 49, statement: 329 }; // 主题宽度72px
|
|
|
|
|
+ const COL_POSITIONS = {
|
|
|
|
|
+ theme: 0,
|
|
|
|
|
+ min: COL_WIDTHS.theme,
|
|
|
|
|
+ avg: COL_WIDTHS.theme + COL_WIDTHS.min,
|
|
|
|
|
+ max: COL_WIDTHS.theme + COL_WIDTHS.min + COL_WIDTHS.avg,
|
|
|
|
|
+ statement: COL_WIDTHS.theme + COL_WIDTHS.min * 3
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const CANVAS_HEIGHT = HEADER_HEIGHT + tableData.length * ROW_HEIGHT;
|
|
|
|
|
+ const CANVAS_WIDTH = TABLE_WIDTH;
|
|
|
|
|
+
|
|
|
|
|
+ canvasNode.width = CANVAS_WIDTH * dpr;
|
|
|
|
|
+ canvasNode.height = CANVAS_HEIGHT * dpr;
|
|
|
|
|
+ ctx.scale(dpr, dpr);
|
|
|
|
|
+
|
|
|
|
|
+ ctx.fillStyle = '#FFFFFF';
|
|
|
|
|
+ ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
|
|
|
+ ctx.strokeStyle = dimensionData.bcolor;
|
|
|
|
|
+ ctx.lineWidth = 1;
|
|
|
|
|
+ ctx.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 4. 绘制表头 ---
|
|
|
|
|
+ const isBlackHeader = dimensionData.title === '目的与动机';
|
|
|
|
|
+ ctx.fillStyle = dimensionData.thcolor;
|
|
|
|
|
+ ctx.fillRect(1, 1, CANVAS_WIDTH - 2, HEADER_HEIGHT - 1);
|
|
|
|
|
+ ctx.fillStyle = isBlackHeader ? '#000000' : '#FFFFFF';
|
|
|
|
|
+ ctx.font = `bold 10px ${FONT_FAMILY}`;
|
|
|
|
|
+ ctx.textAlign = 'center';
|
|
|
|
|
+ ctx.textBaseline = 'middle';
|
|
|
|
|
+
|
|
|
|
|
+ ctx.fillText('主题', COL_POSITIONS.theme + COL_WIDTHS.theme / 2, HEADER_HEIGHT / 2);
|
|
|
|
|
+ ctx.fillText('最低分', COL_POSITIONS.min + COL_WIDTHS.min / 2, HEADER_HEIGHT / 2);
|
|
|
|
|
+ ctx.fillText('平均分', COL_POSITIONS.avg + COL_WIDTHS.avg / 2, HEADER_HEIGHT / 2);
|
|
|
|
|
+ ctx.fillText('最高分', COL_POSITIONS.max + COL_WIDTHS.max / 2, HEADER_HEIGHT / 2);
|
|
|
|
|
+ ctx.fillText('问卷陈述', COL_POSITIONS.statement + COL_WIDTHS.statement / 2, HEADER_HEIGHT / 2);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 5. 循环绘制每一行---
|
|
|
|
|
+ tableData.forEach((row, index) => {
|
|
|
|
|
+ const y = HEADER_HEIGHT + index * ROW_HEIGHT;
|
|
|
|
|
+
|
|
|
|
|
+ ctx.fillStyle = dimensionData.titlecolor;
|
|
|
|
|
+ ctx.fillRect(1, y, COL_WIDTHS.theme - 1, ROW_HEIGHT);
|
|
|
|
|
+ // 绘制白色下边框
|
|
|
|
|
+ ctx.strokeStyle = '#FFFFFF';
|
|
|
|
|
+ ctx.lineWidth = 1;
|
|
|
|
|
+ ctx.beginPath();
|
|
|
|
|
+ ctx.moveTo(1, y + ROW_HEIGHT - 1);
|
|
|
|
|
+ ctx.lineTo(COL_WIDTHS.theme - 1, y + ROW_HEIGHT - 1);
|
|
|
|
|
+ ctx.stroke();
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制其他单元格的下边框
|
|
|
|
|
+ ctx.strokeStyle = dimensionData.bcolor;
|
|
|
|
|
+ ['min', 'avg', 'max', 'statement'].forEach(key => {
|
|
|
|
|
+ ctx.beginPath();
|
|
|
|
|
+ ctx.moveTo(COL_POSITIONS[key], y + ROW_HEIGHT);
|
|
|
|
|
+ ctx.lineTo(COL_POSITIONS[key] + COL_WIDTHS[key], y + ROW_HEIGHT);
|
|
|
|
|
+ ctx.stroke();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ctx.textAlign = 'center';
|
|
|
|
|
+ ctx.textBaseline = 'middle';
|
|
|
|
|
+ ctx.fillStyle = isBlackHeader ? '#000000' : '#FFFFFF';
|
|
|
|
|
+ ctx.font = `10px ${FONT_FAMILY}`; // 主题文字大小
|
|
|
|
|
+ this.drawWrappedText(ctx, row.theme, COL_POSITIONS.theme + COL_WIDTHS.theme / 2, y + ROW_HEIGHT / 2, 12, COL_WIDTHS.theme - 32);
|
|
|
|
|
+
|
|
|
|
|
+ ctx.font = `bold 14px ${FONT_FAMILY}`; // 分数数字加粗
|
|
|
|
|
+ ctx.fillStyle = '#667E90';
|
|
|
|
|
+ ctx.fillText(row.minScore, COL_POSITIONS.min + COL_WIDTHS.min / 2, y + ROW_HEIGHT / 2);
|
|
|
|
|
+ ctx.fillStyle = '#27AE60';
|
|
|
|
|
+ ctx.fillText(row.avgScore, COL_POSITIONS.avg + COL_WIDTHS.avg / 2, y + ROW_HEIGHT / 2);
|
|
|
|
|
+ ctx.fillStyle = '#667E90';
|
|
|
|
|
+ ctx.fillText(row.maxScore, COL_POSITIONS.max + COL_WIDTHS.max / 2, y + ROW_HEIGHT / 2);
|
|
|
|
|
+
|
|
|
|
|
+ // 5.3 绘制问卷陈述列
|
|
|
|
|
+ const statementX = COL_POSITIONS.statement;
|
|
|
|
|
+ const statementPadding = 10;
|
|
|
|
|
+ ctx.textAlign = 'left';
|
|
|
|
|
+ ctx.textBaseline = 'top';
|
|
|
|
|
+ ctx.fillStyle = '#193D59';
|
|
|
|
|
+ ctx.font = `9px ${FONT_FAMILY}`;
|
|
|
|
|
+ this.drawWrappedText(ctx, row.statement, statementX + statementPadding, y + 8, 10, COL_WIDTHS.statement - statementPadding * 2); // (请求 #4) 行高
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制范围指示器
|
|
|
|
|
+ const rangeBarY = y + 33; // 调整位置,使其距离标题 11px
|
|
|
|
|
+ const rangeBarWidth = COL_WIDTHS.statement - statementPadding * 2;
|
|
|
|
|
+ const rangeBarHeight = 4; // 背景高度 4px
|
|
|
|
|
+ const rangeBarX = statementX + statementPadding;
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制三段色背景
|
|
|
|
|
+ const segWidth = rangeBarWidth / 3;
|
|
|
|
|
+ ctx.fillStyle = '#BA8EB4'; // 颜色1
|
|
|
|
|
+ ctx.fillRect(rangeBarX, rangeBarY, segWidth, rangeBarHeight);
|
|
|
|
|
+ ctx.fillStyle = '#66BDBD'; // 颜色2
|
|
|
|
|
+ ctx.fillRect(rangeBarX + segWidth, rangeBarY, segWidth, rangeBarHeight);
|
|
|
|
|
+ ctx.fillStyle = '#AFCDF5'; // 颜色3
|
|
|
|
|
+ ctx.fillRect(rangeBarX + segWidth * 2, rangeBarY, segWidth, rangeBarHeight);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 开始绘制滑块 ---
|
|
|
|
|
+ const scaleFactor = rangeBarWidth / 50;
|
|
|
|
|
+ const rangeLeft = row.range[0] * scaleFactor;
|
|
|
|
|
+ const rangeWidth = (row.range[1] - row.range[0]) * scaleFactor;
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制中间的连接条
|
|
|
|
|
+ const connectorY = rangeBarY - (8 - rangeBarHeight) / 2; // 垂直居中
|
|
|
|
|
+ const connectorHeight = 8;
|
|
|
|
|
+ ctx.fillStyle = '#199C9C';
|
|
|
|
|
+ ctx.fillRect(rangeBarX + rangeLeft, connectorY, rangeWidth, connectorHeight);
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制左右数字框
|
|
|
|
|
+ const numBoxPadding = { h: 7, v: 4 };
|
|
|
|
|
+ const numBoxFont = `bold 12px ${FONT_FAMILY}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 封装一个绘制数字框的函数
|
|
|
|
|
+ const drawNumberBox = (text, side) => {
|
|
|
|
|
+ ctx.font = numBoxFont;
|
|
|
|
|
+ const metrics = ctx.measureText(text);
|
|
|
|
|
+ const boxWidth = metrics.width + numBoxPadding.h * 2;
|
|
|
|
|
+ const boxHeight = 12 + numBoxPadding.v * 2; // 12是字号
|
|
|
|
|
+
|
|
|
|
|
+ let x;
|
|
|
|
|
+ if (side === 'left') {
|
|
|
|
|
+ x = rangeBarX + rangeLeft - boxWidth / 2;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ x = rangeBarX + rangeLeft + rangeWidth - boxWidth / 2;
|
|
|
|
|
+ }
|
|
|
|
|
+ const boxY = connectorY + (connectorHeight - boxHeight) / 2;
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制阴影
|
|
|
|
|
+ ctx.shadowColor = 'rgba(118, 30, 106, 0.08)';
|
|
|
|
|
+ ctx.shadowBlur = 10;
|
|
|
|
|
+ ctx.shadowOffsetY = 4;
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制圆角矩形背景
|
|
|
|
|
+ ctx.fillStyle = '#FFFFFF';
|
|
|
|
|
+ ctx.beginPath();
|
|
|
|
|
+ ctx.moveTo(x + 4, boxY);
|
|
|
|
|
+ ctx.arcTo(x + boxWidth, boxY, x + boxWidth, boxY + boxHeight, 4);
|
|
|
|
|
+ ctx.arcTo(x + boxWidth, boxY + boxHeight, x, boxY + boxHeight, 4);
|
|
|
|
|
+ ctx.arcTo(x, boxY + boxHeight, x, boxY, 4);
|
|
|
|
|
+ ctx.arcTo(x, boxY, x + boxWidth, boxY, 4);
|
|
|
|
|
+ ctx.closePath();
|
|
|
|
|
+ ctx.fill();
|
|
|
|
|
+
|
|
|
|
|
+ // 重置阴影,避免影响边框
|
|
|
|
|
+ ctx.shadowColor = 'transparent';
|
|
|
|
|
+ ctx.shadowBlur = 0;
|
|
|
|
|
+ ctx.shadowOffsetY = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制边框
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(131, 52, 120, 0.19)';
|
|
|
|
|
+ ctx.lineWidth = 1;
|
|
|
|
|
+ ctx.stroke();
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制文字
|
|
|
|
|
+ ctx.fillStyle = side === 'left' ? '#904A87' : '#199C9C';
|
|
|
|
|
+ ctx.textAlign = 'center';
|
|
|
|
|
+ ctx.textBaseline = 'middle';
|
|
|
|
|
+ ctx.fillText(text, x + boxWidth / 2, boxY + boxHeight / 2);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ drawNumberBox(row.range[0].toString(), 'left');
|
|
|
|
|
+ drawNumberBox(row.range[1].toString(), 'right');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // --- 6. 生成图片文件 ---
|
|
|
|
|
+ uni.canvasToTempFilePath({
|
|
|
|
|
+ canvas: canvasNode,
|
|
|
|
|
+ success: async (result) => {
|
|
|
|
|
+ console.log('图片生成成功!', result.tempFilePath);
|
|
|
|
|
+ const fileurl = await this.uploadFilePromise(result.tempFilePath);
|
|
|
|
|
+ console.log(fileurl, 'fileurl');
|
|
|
|
|
+ resolve(fileurl);
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: (err) => {
|
|
|
|
|
+ console.error('图片生成失败', err);
|
|
|
|
|
+ uni.showToast({ title: '图片生成失败', icon: 'none' });
|
|
|
|
|
+ reject(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, this);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @description 辅助函数:在Canvas中绘制可自动换行的文本
|
|
|
|
|
+ * @param {CanvasRenderingContext2D} ctx
|
|
|
|
|
+ * @param {string} text 要绘制的文本
|
|
|
|
|
+ * @param {number} x 起始x坐标
|
|
|
|
|
+ * @param {number} y 起始y坐标(对于居中对齐,这是中心y;对于top对齐,这是第一行的y)
|
|
|
|
|
+ * @param {number} lineHeight 行高
|
|
|
|
|
+ * @param {number} maxWidth 最大宽度
|
|
|
|
|
+ */
|
|
|
|
|
+ drawWrappedText(ctx, text, x, y, lineHeight, maxWidth) {
|
|
|
|
|
+ let words = text.split('');
|
|
|
|
|
+ let line = '';
|
|
|
|
|
+ let lines = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (let n = 0; n < words.length; n++) {
|
|
|
|
|
+ let testLine = line + words[n];
|
|
|
|
|
+ let metrics = ctx.measureText(testLine);
|
|
|
|
|
+ if (metrics.width > maxWidth && n > 0) {
|
|
|
|
|
+ lines.push(line);
|
|
|
|
|
+ line = words[n];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ line = testLine;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ lines.push(line);
|
|
|
|
|
+
|
|
|
|
|
+ let startY;
|
|
|
|
|
+ if (ctx.textBaseline === 'middle') {
|
|
|
|
|
+ startY = y - (lineHeight * (lines.length - 1)) / 2;
|
|
|
|
|
+ } else { // top
|
|
|
|
|
+ startY = y;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < lines.length; i++) {
|
|
|
|
|
+ ctx.fillText(lines[i], x, startY + (i * lineHeight));
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
calculateScaleAndPosition() {
|
|
calculateScaleAndPosition() {
|
|
|
uni.getSystemInfo({
|
|
uni.getSystemInfo({
|
|
|
success: (res) => {
|
|
success: (res) => {
|
|
@@ -275,20 +598,23 @@
|
|
|
}).exec();
|
|
}).exec();
|
|
|
},
|
|
},
|
|
|
downloadZtzdfxImg(){
|
|
downloadZtzdfxImg(){
|
|
|
- if (!this.isChartReady) return console.log('图表尚未准备好');
|
|
|
|
|
-
|
|
|
|
|
- const chartRef = this.$refs.ztzdfxRef;
|
|
|
|
|
- if (!chartRef) return console.log('无法找到图表组件');
|
|
|
|
|
-
|
|
|
|
|
- chartRef.canvasToTempFilePath({
|
|
|
|
|
- success: async (res) => {
|
|
|
|
|
- const imgUrl = await this.uploadFilePromise(res.tempFilePath);
|
|
|
|
|
- console.log(imgUrl,'imgUrl');
|
|
|
|
|
- },
|
|
|
|
|
- fail: (err) => {
|
|
|
|
|
- console.log('生成图片失败:', err);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ return new Promise(resolve=>{
|
|
|
|
|
+ if (!this.isChartReady) return console.log('图表尚未准备好');
|
|
|
|
|
+
|
|
|
|
|
+ const chartRef = this.$refs.ztzdfxRef;
|
|
|
|
|
+ if (!chartRef) return console.log('无法找到图表组件');
|
|
|
|
|
+
|
|
|
|
|
+ chartRef.canvasToTempFilePath({
|
|
|
|
|
+ success: async (res) => {
|
|
|
|
|
+ const imgUrl = await this.uploadFilePromise(res.tempFilePath);
|
|
|
|
|
+ console.log(imgUrl,'imgUrl');
|
|
|
|
|
+ resolve(imgUrl)
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: (err) => {
|
|
|
|
|
+ console.log('生成图片失败:', err);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ })
|
|
|
},
|
|
},
|
|
|
uploadFilePromise(url) {
|
|
uploadFilePromise(url) {
|
|
|
return new Promise((resolve, reject) => {
|
|
return new Promise((resolve, reject) => {
|
|
@@ -433,6 +759,23 @@
|
|
|
transform-origin: top left;
|
|
transform-origin: top left;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ .offscreen-canvas {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ top: -9999px;
|
|
|
|
|
+ left: -9999px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .pdf_btn{
|
|
|
|
|
+ padding: 15rpx 20rpx;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ background: #189B9B;
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ right: 30rpx;
|
|
|
|
|
+ bottom: 100rpx;
|
|
|
|
|
+ z-index: 1000;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
@import '../static/pdf.scss';
|
|
@import '../static/pdf.scss';
|
|
|
</style>
|
|
</style>
|