代码提交

test
zhangdi 1 year ago
parent b917f98738
commit 025e59ad5d
  1. 83
      common/betone_components/betone-list.vue
  2. 1
      main.js
  3. 180
      manifest.json
  4. 156
      package-lock.json
  5. 2
      package.json
  6. 299
      pages/inspection/components/details.vue
  7. 39
      pages/inspection/quest.vue
  8. 538
      pages/submission/components/details.vue
  9. 281
      pages/submission/recordsdetails.vue
  10. 57
      uni_modules/sp-html2canvas-render/changelog.md
  11. 110
      uni_modules/sp-html2canvas-render/components/sp-html2canvas-render/sp-html2canvas-render.vue
  12. 86
      uni_modules/sp-html2canvas-render/package.json
  13. 11
      uni_modules/sp-html2canvas-render/readme.md
  14. 255
      uni_modules/sp-html2canvas-render/utils/index.js
  15. 34
      uni_modules/sp-html2pdf-render/changelog.md
  16. 277
      uni_modules/sp-html2pdf-render/components/sp-html2pdf-render/sp-html2pdf-render.vue
  17. 85
      uni_modules/sp-html2pdf-render/package.json
  18. 11
      uni_modules/sp-html2pdf-render/readme.md
  19. 261
      uni_modules/sp-html2pdf-render/utils/index.js
  20. 2
      utils/website.js

@ -33,10 +33,13 @@
</view>
<view class="con-btn">
<!-- 客户 -->
<u-button size="mini" plain style="margin-right: 32rpx"
@click.stop="openWorkOrder(item)">查看</u-button>
<u-button size="mini" plain style="margin-right: 32rpx"
@click="openModel(item, 'details')">下载</u-button>
<block v-if="dataTypes == 1">
<!-- 工单详情 查看维修详情 -->
<u-button size="mini" plain style="margin-right: 32rpx"
@click.stop="openWorkOrder(item)">查看</u-button>
<u-button v-if="item.status == -1" size="mini" :plain="true" style="margin-right: 32rpx"
:hair-line="true" type="primary" @click="subOrder(item)">提报</u-button>
@ -53,8 +56,7 @@
<!-- 客服 -->
<block v-if="dataTypes == 2">
<!-- 查看的提报单 -->
<u-button size="mini" plain style="margin-right: 32rpx"
@click.stop="openWorkOrder(item)">查看</u-button>
<u-button v-if="item.status == 0" size="mini" :plain="true" style="margin-right: 32rpx"
@click.stop="sendOrders(item)" type="primary">接单</u-button>
<u-button v-if="item.status == 7" size="mini" :plain="true" style="margin-right: 32rpx"
@ -67,8 +69,7 @@
<!-- 维修负责人 -->
<block v-if="dataTypes == 3">
<!-- 查看维修详情 -->
<u-button size="mini" plain style="margin-right: 32rpx"
@click.stop="openWorkOrder(item)">查看</u-button>
<u-button v-if="item.status == 3" size="mini" :plain="true" style="margin-right: 32rpx"
:hair-line="true" type="primary" @click="orderConfirm(item)">审批</u-button>
@ -77,11 +78,10 @@
<!-- 维修人员 -->
<block v-if="dataTypes == 4">
<!-- 查看维修详情 -->
<u-button size="mini" plain style="margin-right: 32rpx"
@click.stop="openWorkOrder(item)">查看</u-button>
<u-button v-if="item.status == 1" size="mini" :plain="true" style="margin-right: 32rpx"
type="primary" @click="openModel(item,'submitOrder')">确认</u-button>
type="primary" @click="openModel(item, 'submitOrder')">确认</u-button>
<u-button v-if="item.status == 1" size="mini" :plain="true" style="margin-right: 32rpx"
type="primary" @click="openModel(item, 'turn')">驳回</u-button>
<u-button v-if="item.status == 2" size="mini" :plain="true" style="margin-right: 32rpx"
@ -103,14 +103,14 @@
<betone-modal :title="'关闭原因'" :confirmText="'确认'" v-model="cloneVisible" @cancel="cloneVisible = false"
@confirm="confirmCloseSub">
<view class="modal-text">
<u-input :border="true" v-model="closeReason" placeholder="请输入" style="margin-bottom: 32rpx" />
<u-input :border="true" v-model="closeReason" placeholder="请输入" style="margin-bottom: 32rpx" />
</view>
</betone-modal>
<!-- 维修人员驳回 -->
<betone-modal v-model="turnShow" :title="'驳回原因'" @confirm="turnFn" @cancel="turnShow = false">
<view class="modal-text">
<u-input :border="true" v-model="repaiRejectReason" placeholder="请输入" style="margin-bottom: 32rpx" />
<u-input :border="true" v-model="repaiRejectReason" placeholder="请输入" style="margin-bottom: 32rpx" />
</view>
</betone-modal>
@ -127,18 +127,27 @@
请确认付款是否已经完成
</view>
</betone-modal>
<!-- 维修人员确认接单 -->
<betone-modal v-model="submitOrderShow" :title="'提示'" @confirm="submitOrderShowFn" @cancel="submitOrderShow = false">
<!-- 维修人员确认接单 -->
<betone-modal v-model="submitOrderShow" :title="'提示'" @confirm="submitOrderShowFn"
@cancel="submitOrderShow = false">
<view class="modal-text">
请确认是否进行接单
</view>
</betone-modal>
<betone-loading ref="BetLoading" />
<betone-modal v-model="detailsShow" :title="'下载内容'" @confirm="detailsShowFn" @cancel="detailsShow = false">
<recordsdetails :data="rowInfo" :detailId="rowInfo.id" id="capture"></recordsdetails>
</betone-modal>
<sp-html2canvas-render domId="capture" ref="renderRef" @renderOver="renderOver" pdfFileName="这是一个pdf文件"
@beforeSavePDF="beforeSavePDF"
@successSavePDF="successSavePDF"></sp-html2canvas-render>
</view>
</template>
<script>
import recordsdetails from '@/pages/submission/components/details.vue'
export default {
props: {
tabList: {
@ -150,6 +159,9 @@ export default {
default: ''
}
},
components: {
recordsdetails
},
data() {
return {
searchInfo: {
@ -163,14 +175,14 @@ export default {
待维修: " #409eff",
确认方案: "#409eff",
维修中: '#e6a23',
待评价:"#67c23a",
待付款:"#67c23a",
维修中:"#e6a23",
已驳回:"#f56c6c",
待审批:"#409eff",
已通过:"#409eff",
待确认:"#67c23a",
待提交:'#67c23a',
待评价: "#67c23a",
待付款: "#67c23a",
维修中: "#e6a23",
已驳回: "#f56c6c",
待审批: "#409eff",
已通过: "#409eff",
待确认: "#67c23a",
待提交: '#67c23a',
已完成: "#909399",
},
@ -199,7 +211,9 @@ export default {
paymentShow: false,//
submitOrderShow:false,//
submitOrderShow: false,//
detailsShow: false,//
};
},
computed: {
@ -451,6 +465,9 @@ export default {
if (type == 'submitOrder') {
this.submitOrderShow = true
}
if (type == 'details') {
this.detailsShow = true
}
this.rowInfo = item
},
@ -552,7 +569,7 @@ export default {
})
},
//
submitOrderShowFn(){
submitOrderShowFn() {
let query_ = {
id: this.rowInfo.id
}
@ -572,7 +589,25 @@ export default {
url: `/pages/order/location${this.$u.queryParams(param)}`,
})
}
},
detailsShowFn() {
// h2cRenderDom
this.$refs.renderRef.h2pRenderDom();
},
renderOver(e) {
// ebase64
// this.imageSrc = e
console.log("==== renderOver :", e);
// canvas
},
beforeSavePDF(e) {
// epdfbase64
console.log("==== beforeSavePDF :", e);
},
successSavePDF(path) {
// epdf
console.log("==== successSavePDF :", path);
},
},
};
</script>

@ -20,6 +20,7 @@ const app = new Vue({
import request from '@/utils/request.js';
Vue.use(request, app);
// http接口API集中管理引入部分
import httpApi from '@/api/api.js'
Vue.use(httpApi, app)

@ -1,20 +1,20 @@
{
"name" : "科研医疗建筑运维平台",
"appid" : "__UNI__BB25171",
"description" : "",
"versionName" : "1.0.2",
"versionCode" : 1,
"transformPx" : false,
"app-plus" : {
"nvueCompiler" : "uni-app",
"compilerVersion" : 3,
"modules" : {
"Geolocation" : {},
"Maps" : {}
"name": "科研医疗建筑运维平台",
"appid": "__UNI__BB25171",
"description": "",
"versionName": "1.0.2",
"versionCode": 1,
"transformPx": false,
"app-plus": {
"nvueCompiler": "uni-app",
"compilerVersion": 3,
"modules": {
"Geolocation": {},
"Maps": {}
},
"distribute" : {
"android" : {
"permissions" : [
"distribute": {
"android": {
"permissions": [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
@ -34,102 +34,106 @@
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.LOCATION\"/>"
"<uses-permission android:name=\"android.permission.LOCATION\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>"
],
"minSdkVersion" : 19,
"targetSdkVersion" : 28
"minSdkVersion": 19,
"targetSdkVersion": 28
},
"ios" : {
"privacyDescription" : {
"NSLocationWhenInUseUsageDescription" : "",
"NSLocationAlwaysUsageDescription" : "",
"NSLocationAlwaysAndWhenInUseUsageDescription" : ""
"ios": {
"privacyDescription": {
"NSLocationWhenInUseUsageDescription": "",
"NSLocationAlwaysUsageDescription": "",
"NSLocationAlwaysAndWhenInUseUsageDescription": ""
},
"idfa" : false,
"UIBackgroundModes" : "audio,location",
"dSYMs" : false
"idfa": false,
"UIBackgroundModes": "audio,location",
"dSYMs": false
},
"sdkConfigs" : {
"ad" : {},
"push" : {
"unipush" : {}
"sdkConfigs": {
"ad": {},
"push": {
"unipush": {}
},
"maps" : {
"amap" : {
"name" : "amapAjPJDZFvd",
"appkey_ios" : "a9632baa2c4ae13a34c1fb265dda41f5",
"appkey_android" : "a9632baa2c4ae13a34c1fb265dda41f5"
"maps": {
"amap": {
"name": "amapAjPJDZFvd",
"appkey_ios": "a9632baa2c4ae13a34c1fb265dda41f5",
"appkey_android": "a9632baa2c4ae13a34c1fb265dda41f5"
}
},
"geolocation" : {
"system" : {
"__platform__" : [ "ios", "android" ]
"geolocation": {
"system": {
"__platform__": [
"ios",
"android"
]
}
}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
"icons": {
"android": {
"hdpi": "unpackage/res/icons/72x72.png",
"xhdpi": "unpackage/res/icons/96x96.png",
"xxhdpi": "unpackage/res/icons/144x144.png",
"xxxhdpi": "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
"ios": {
"appstore": "unpackage/res/icons/1024x1024.png",
"ipad": {
"app": "unpackage/res/icons/76x76.png",
"app@2x": "unpackage/res/icons/152x152.png",
"notification": "unpackage/res/icons/20x20.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"proapp@2x": "unpackage/res/icons/167x167.png",
"settings": "unpackage/res/icons/29x29.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"spotlight": "unpackage/res/icons/40x40.png",
"spotlight@2x": "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
"iphone": {
"app@2x": "unpackage/res/icons/120x120.png",
"app@3x": "unpackage/res/icons/180x180.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"notification@3x": "unpackage/res/icons/60x60.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"settings@3x": "unpackage/res/icons/87x87.png",
"spotlight@2x": "unpackage/res/icons/80x80.png",
"spotlight@3x": "unpackage/res/icons/120x120.png"
}
}
}
},
"nativePlugins" : {}
"nativePlugins": {}
},
"compatible" : {
"runtimeVersion" : "2.9.2",
"compilerVersion" : "2.9.5",
"ignoreVersion" : true
"compatible": {
"runtimeVersion": "2.9.2",
"compilerVersion": "2.9.5",
"ignoreVersion": true
},
//"ignoreVersion" : true //trueHBuilderX1.9.0
"quickapp" : {},
"mp-weixin" : {
"appid" : "wx81368cd255de5182",
"setting" : {
"urlCheck" : true,
"minified" : true
"quickapp": {},
"mp-weixin": {
"appid": "wx81368cd255de5182",
"setting": {
"urlCheck": true,
"minified": true
},
"lazyCodeLoading" : "requiredComponents",
"optimization" : {
"subPackages" : true
"lazyCodeLoading": "requiredComponents",
"optimization": {
"subPackages": true
},
"permission" : {}
"permission": {}
},
"h5" : {
"sdkConfigs" : {
"maps" : {
"amap" : {
"key" : "a35a37c732890b60c4ff0aadd318d0aa",
"securityJsCode" : "7b6d007df2a0d16719b34ec5b7fce504",
"serviceHost" : ""
"h5": {
"sdkConfigs": {
"maps": {
"amap": {
"key": "a35a37c732890b60c4ff0aadd318d0aa",
"securityJsCode": "7b6d007df2a0d16719b34ec5b7fce504",
"serviceHost": ""
}
}
}
}
}
}

156
package-lock.json generated

@ -4,6 +4,79 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/runtime": {
"version": "7.26.0",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
},
"@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmmirror.com/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true
},
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
},
"btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
},
"canvg": {
"version": "3.0.10",
"resolved": "https://registry.npmmirror.com/canvg/-/canvg-3.0.10.tgz",
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
"optional": true,
"requires": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
}
}
},
"core-js": {
"version": "3.39.0",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.39.0.tgz",
"integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
"optional": true
},
"css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"requires": {
"utrie": "^1.0.2"
}
},
"dompurify": {
"version": "2.5.7",
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.7.tgz",
"integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==",
"optional": true
},
"echarts": {
"version": "5.5.1",
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
@ -13,11 +86,40 @@
"zrender": "5.6.0"
}
},
"fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
},
"html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"requires": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
}
},
"jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"jspdf": {
"version": "2.5.2",
"resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.2.tgz",
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
"requires": {
"@babel/runtime": "^7.23.2",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"canvg": "^3.0.6",
"core-js": "^3.6.0",
"dompurify": "^2.5.4",
"fflate": "^0.8.1",
"html2canvas": "^1.0.0-rc.5"
}
},
"miniprogram-sm-crypto": {
"version": "0.3.13",
"resolved": "https://registry.npmmirror.com/miniprogram-sm-crypto/-/miniprogram-sm-crypto-0.3.13.tgz",
@ -26,6 +128,32 @@
"jsbn": "^1.1.0"
}
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true
},
"raf": {
"version": "3.4.1",
"resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"optional": true,
"requires": {
"performance-now": "^2.1.0"
}
},
"regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"optional": true
},
"sm-crypto": {
"version": "0.3.13",
"resolved": "https://registry.npmmirror.com/sm-crypto/-/sm-crypto-0.3.13.tgz",
@ -34,11 +162,39 @@
"jsbn": "^1.1.0"
}
},
"stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"optional": true
},
"svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"optional": true
},
"text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"requires": {
"utrie": "^1.0.2"
}
},
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
},
"utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"requires": {
"base64-arraybuffer": "^1.0.2"
}
},
"zrender": {
"version": "5.6.0",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",

@ -15,6 +15,8 @@
"license": "ISC",
"dependencies": {
"echarts": "^5.5.1",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.2",
"miniprogram-sm-crypto": "^0.3.13",
"sm-crypto": "^0.3.13"
}

@ -0,0 +1,299 @@
<template>
<view class="page-css">
<view class="info">
<view class="info_title">基本信息</view>
<view class="info_item">
<text class="item_left_txt">单位名称</text>
<text class="item_right_txt">{{ detailForm.deptName }}</text>
</view>
<view class="info_item">
<text class="item_left_txt">巡检内容</text>
<text class="item_right_txt">{{ detailForm.taskContent }}</text>
</view>
</view>
<view class="info">
<view class="info_title">巡检配置</view>
<view class="info_item">
<text class="item_left_txt">巡检内容</text>
<text class="item_right_txt">{{ detailForm.taskContent }}</text>
</view>
<view class="info_item">
<text class="item_left_txt">任务开始时间</text>
<text class="item_right_txt">{{ detailForm.startTime }}</text>
</view>
<view class="info_item" v-if="!(detailForm.taskStatus == 0 || detailForm.taskStatus == 1)">
<text class="item_left_txt">预约上门时间</text>
<text class="item_right_txt">{{ detailForm.reservationTime }}</text>
</view>
</view>
<view class="info">
<view class="info_title">巡检任务</view>
<u-collapse :accordion="false">
<u-collapse-item v-for="(item, index) in detailForm.details" :key="index" :open="true">
<view slot="title">
<text class="collapse-title">实验室楼层{{ item.floorName }}</text>
</view>
<view v-for="(v, vindex) in item.details" :key="vindex" class="collapse-room">
<view>房间名称{{ v.deptName }}</view>
<view v-for="(s, sindex) in v.details" :key="sindex" class="n-box">
<u-tag :text="sindex + 1" type="info" class="left-tag" />
<view class="n-box-title"> {{ s.checkContent }}</view>
<view class="n-box-info"> {{ s.craft }}</view>
<view class="info_item">
<text class="item_left_txt">巡检周期</text>
<text class="item_right_txt">
{{ periodName(s.period) }}
</text>
</view>
<view class="info_item" v-if="s.status >= 0">
<text class="item_left_txt">状态</text>
<text class="item_right_txt">
{{ s.status == '0' ? '正常' : '异常' }}
</text>
</view>
<view class="info_item" v-if="s.picUrl != ''">
<text class="item_left_txt">现场照片</text>
<text class="item_right_txt">
<image class="item_right_img" :src="s.picUrl"></image>
</text>
</view>
<view class="info_item" v-if="s.signUrl != ''">
<text class="item_left_txt">签字</text>
<text class="item_right_txt">
{{ s.signUrl }}
</text>
</view>
<view class="info_item" v-if="s.remark != ''">
<text class="item_left_txt">备注</text>
<text class="item_right_txt">
{{ s.remark }}
</text>
</view>
<view class="info_item" v-if="s.price >= 0">
<text class="item_left_txt">价格</text>
<text class="item_right_txt">
{{ s.price }}
</text>
</view>
<view class="info_item" v-if="s.isRepair >= 0">
<text class="item_left_txt">是否维修</text>
<text class="item_right_txt">
{{ s.status == '0' ? '否' : '是' }}
</text>
</view>
</view>
</view>
</u-collapse-item>
</u-collapse>
</view>
</view>
</template>
<script>
export default {
props:{
data:{
type:Object,
default:{}
},
detailId:{
type:String,
default:''
}
},
computed: {
dataTypes() {
return this.$store.state.dataType
},
},
data() {
return {
detailForm: {},
optionsType: '',//
planOrderId: '',//
}
},
mounted(){
this.getDetails()
},
methods: {
getDetails() {
let query_ = {
id: this.detailId
}
this.$u.api.getTaskDetail(query_).then(res => {
if (res.code == 200) {
this.detailForm = res.data
}
})
},
//
periodName(value) {
if (value == 1) {
return '月'
}
if (value == 2) {
return '季度'
}
if (value == 3) {
return '半年'
}
},
}
}
</script>
<style lang="scss" scoped>
.page-css {
width: 100%;
padding: 0;
padding-top: 26rpx;
.info {
margin: 0 24rpx 20rpx;
border-radius: 20rpx;
background-color: rgba(255, 255, 255, 1);
padding: 6rpx 0 34rpx;
.info_title {
line-height: 60rpx;
color: rgba(0, 0, 0, 1);
font-size: 32rpx;
text-align: left;
font-family: SourceHanSansSC-bold;
margin: 18rpx 0 0rpx 20rpx;
font-weight: bold;
}
.info_item {
padding: 4rpx 20rpx 0;
display: flex;
align-items: center;
justify-content: space-between;
.item_left_txt {
color: rgba(108, 108, 108, 1);
font-size: 28rpx;
text-align: left;
font-family: SourceHanSansSC-regular;
width: 210rpx;
margin-right: 20rpx;
line-height: 60rpx;
}
.item_right_txt {
max-width: calc(100% - 220rpx);
color: rgba(0, 0, 0, 1);
font-size: 28rpx;
// text-align: left;
text-align: justify;
font-family: SourceHanSansSC-regular;
image{
width: 100rpx;
height: 100rpx;
}
}
}
}
.repair-btn {
width: 100%;
height: 150rpx;
display: flex;
justify-content: space-between;
/* 水平均匀分布 */
margin-top: 32rpx;
padding: 0 32rpx;
/deep/.u-btn {
width: 48%;
}
}
.n-box {
border: 1px solid rgba(239, 239, 239, 1);
padding: 32rpx 54rpx 0;
margin-top: 24rpx;
margin-bottom: 24rpx;
position: relative;
.right-tag {
position: absolute;
right: 0;
top: 0;
}
.left-tag {
position: absolute;
left: 0;
top: 0;
padding-left: 10rpx;
padding-right: 10rpx;
}
.n-box-title {
color: rgba(0, 0, 0, 1);
font-size: 28rpx;
text-align: left;
}
.n-box-info {
line-height: 32rpx;
color: rgba(154, 154, 154, 1);
font-size: 24rpx;
padding-top: 12rpx;
padding-bottom: 12rpx;
}
.info_item {
width: 100%;
display: flex;
flex-direction: row;
margin: 0rpx 0 0;
.item_left_txt {
color: rgba(108, 108, 108, 1);
font-size: 28rpx;
text-align: left;
width: 170rpx;
margin-right: 20rpx;
line-height: 60rpx;
}
.item_right_txt {
flex: 1;
max-width: calc(100% - 220rpx);
color: rgba(0, 0, 0, 1);
font-size: 28rpx;
// text-align: left;
text-align: justify;
font-family: SourceHanSansSC-regular;
/deep/.u-radio-group {
flex-wrap: nowrap;
padding-right: 48rpx;
}
.btn {
width: 260rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 8rpx;
background-color: rgba(255, 255, 255, 1);
color: rgba(58, 98, 215, 1);
font-size: 28rpx;
text-align: center;
border: 2rpx dashed rgba(58, 98, 215, 1);
}
}
}
}
/deep/.u-collapse {
padding: 0 16rpx;
}
}
</style>

@ -37,6 +37,8 @@
<view class="con-btn">
<u-button size="mini" plain style="margin-right: 32rpx"
@click="taskDetails(item)">查看</u-button>
<u-button size="mini" plain style="margin-right: 32rpx"
@click="openModel(item,'details')">下载</u-button>
<!-- 巡检任务 客户 确认-->
<block v-if="subsectionCurrent == '0' && dataTypes == 1 && item.taskStatus == 3">
@ -111,13 +113,22 @@
<betone-loading ref="BetLoading" />
<betone-tabbar ref="tabbarRef" v-if="!planOrderId" />
<betone-modal v-model="detailsShow" :title="'下载内容'" @confirm="detailsShowFn" @cancel="detailsShow = false">
<recordsdetails :data="rowInfo" :detailId="rowInfo.id" id="capture"></recordsdetails>
</betone-modal>
<sp-html2canvas-render domId="capture" ref="renderRef" @renderOver="renderOver" pdfFileName="这是一个pdf文件"
@beforeSavePDF="beforeSavePDF"
@successSavePDF="successSavePDF"></sp-html2canvas-render>
</view>
</template>
<script>
import recordsdetails from './components/details.vue'
export default {
components: {
recordsdetails
},
data() {
return {
type: '',//
@ -174,6 +185,8 @@ export default {
repairCompleteShow: false,//
paymentShow: false,//
detailsShow:false,//pdf
};
},
computed: {
@ -305,7 +318,7 @@ export default {
},
//
openWorkOrder(item, type) {
let param = {
id: item.id,
type: 'view',
@ -358,6 +371,9 @@ export default {
if (type == 'paymentInfo') {
this.paymentShow = true
}
if(type == 'details'){
this.detailsShow = true
}
this.rowInfo = item
},
//
@ -650,7 +666,24 @@ export default {
}
},
detailsShowFn() {
// h2cRenderDom
this.$refs.renderRef.h2pRenderDom();
},
renderOver(e) {
// ebase64
// this.imageSrc = e
console.log("==== renderOver :", e);
// canvas
},
beforeSavePDF(e) {
// epdfbase64
console.log("==== beforeSavePDF :", e);
},
successSavePDF(path) {
// epdf
console.log("==== successSavePDF :", path);
},
},
};
</script>

@ -0,0 +1,538 @@
<template>
<view class="page-css"
:style="{ 'padding-bottom': (detailForm.status == 5 && dataTypes == 1) ? '180rpx' : '20rpx' }"
ref="contentToExport">
<view class="">
<view class="repair_detail">
<view class="repair_title">{{ dataTypes == 1 ? '故障信息' : '故障详情' }}</view>
<view class="detail_item">
<text class="item_left_txt">故障描述</text>
<text class="item_right_txt reason">{{ detailForm.faultDescribe }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">故障位置</text>
<text class="item_right_txt">{{ detailForm.faultLocation }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">设备名称</text>
<text class="item_right_txt">{{ detailForm.deviceName }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">故障类别</text>
<text class="item_right_txt">{{ detailForm.faultType }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">故障视频</text>
<view class="item_right_txt video">
<video v-show="videoUrl != ''" :src="videoUrl" style="width: 450rpx; height: 320rpx"
controls></video>
<video v-if="detailForm.videoAttaches && detailForm.videoAttaches.length > 0"
:src="detailForm.videoAttaches[0].link" style="width: 148px; height: 148px"
controls></video>
</view>
</view>
<view class="detail_item">
<text class="item_left_txt">故障图片</text>
<view class="item_right_txt video">
<image style="width: 130rpx;height: 140rpx;margin-bottom: 10rpx;"
v-for="item in detailForm.picAttaches" :key="item.id" :src="item.link" alt="" />
</view>
</view>
</view>
<!-- 维修详情 -->
<view class="repair_detail"
v-if="(dataTypes == 1 || dataTypes == 2) && (detailForm.status == 5 || detailForm.status == 6 || detailForm.status == 7 || detailForm.status == 8 || detailForm.status == 9) || (dataTypes == 4 && !(detailForm.status == 0 || detailForm.status == 1 || detailForm.status == 2)) || (dataTypes == 3) && (detailForm.closeReason == '')">
<view class="repair_title">维修详情</view>
<view class="detail_item">
<text class="item_left_txt">维修人</text>
<text class="item_right_txt">{{ detailForm.repairPersonName }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">设备名称</text>
<text class="item_right_txt">{{ detailForm.deviceName }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">故障原因</text>
<text class="item_right_txt reason">{{ detailForm.faultCause }}</text>
</view>
<view class="detail_item" style="margin-top: 10rpx;">
<text class="item_left_txt">处理方法</text>
<text class="item_right_txt reason">{{ detailForm.processMethod }}</text>
</view>
<view class="detail_item" v-show="detailForm.materials && detailForm.materials.length != 0">
<text class="item_left_txt">维修材料</text>
<view class="item_right_table">
<view class="table_box">
<view class="table_txt left">物料名称</view>
<view class="table_txt right">金额</view>
</view>
<view class="table_box table_item" v-for="item in detailForm.materials" :key="item.id">
<view class="table_txt left">{{ item.materialName }}</view>
<view class="table_txt right">{{ item.materialPrice }}</view>
</view>
<view class="table_box table_sub">
<view class="table_txt left">合计</view>
<view class="table_txt right">{{ totalPrice }}</view>
</view>
<view class="table_box table_sub">
<view class="table_txt left">折扣</view>
<view class="table_txt right">{{ discount }}<text v-show="discount != ''"></text></view>
</view>
<view class="table_box table_sub">
<view class="table_txt left">折后价格</view>
<view class="table_txt right">{{ discountPrice }}</view>
</view>
</view>
</view>
</view>
<!-- 客服查看派单详情 -->
<view class="repair_detail"
v-if="((detailForm.status != 0) && dataTypes == 2) || (dataTypes == 1 && !(detailForm.status == 0 || detailForm.status == -1)) || dataTypes == 4">
<view class="repair_title">处理情况</view>
<view class="detail_item">
<text class="item_left_txt">接单时间</text>
<text class="item_right_txt reason">{{ detailForm.dispatchTime }}</text>
</view>
<view class="detail_item" v-if="dataTypes != 1">
<text class="item_left_txt">计划完成时间</text>
<text class="item_right_txt reason">{{ detailForm.planCompleteTime }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">客服意见</text>
<text class="item_right_txt reason">{{ detailForm.customerOpinion }}</text>
</view>
</view>
<!-- 关闭详情 -->
<view class="repair_detail" v-if="detailForm.closeReason != ''">
<view class="repair_title">关闭详情</view>
<view class="detail_item">
<text class="item_left_txt">关闭原因</text>
<text class="item_right_txt">{{ detailForm.closeReason }}</text>
</view>
</view>
<!-- 驳回 -->
<view class="repair_detail" v-if="detailForm.repaiRejectReason != ''">
<view class="repair_title">驳回详情</view>
<view class="detail_item">
<text class="item_left_txt">驳回原因</text>
<text class="item_right_txt">{{ detailForm.repaiRejectReason }}</text>
</view>
</view>
<view class="repair_detail" v-if="detailForm.status != -1">
<view class="repair_title">其他信息</view>
<view class="detail_item">
<text class="item_left_txt">提报人</text>
<text class="item_right_txt">{{ detailForm.informantName }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">提报人电话</text>
<text class="item_right_txt">{{ detailForm.informantPhone }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">提报单号</text>
<text class="item_right_txt">{{ detailForm.requirementCode }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">提报时间</text>
<text class="item_right_txt">{{ detailForm.createTime }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props:{
data:{
type:Object,
default:{}
},
detailId:{
type:String,
default:''
}
},
computed: {
dataTypes() {
return this.$store.state.dataType
},
},
data() {
return {
totalPrice: '1084.00',
discount: '9',
discountPrice: '975.60',
detailForm: {},
videoUrl: '',
userInfo: {},
repairDetails: {
reportPeople: '',//
deviceName: '',//
faultReason: '',//
dealMethods: '',//
isMaterial: null,//
materialTable: [],//
},
imageSrc: '',//
}
},
mounted() {
//
this.userInfo = uni.getStorageSync("userinfo");
this.getDetail()
//
},
methods: {
getDetail() {
let query_ = {
id: this.detailId
}
this.$u.api.getBreakdownInfo(query_).then(res => {
if (res.code == 200) {
this.detailForm = res.data
this.videoUrl = res.data.videoAttaches && res.data.videoAttaches.length > 0 ? res.data.videoAttaches[0].link : ''
this.totalPrice = res.data.totalPrice == -1 ? res.data.materials.reduce((sum, item) => sum + item.materialPrice, 0) : res.data.totalPrice
this.discount = res.data.discount == -1 ? '' : res.data.discount
this.discountPrice = res.data.discountPrice == -1 ? '' : res.data.discountPrice
this.repairDetails.reportPeople = this.detailForm.repairPersonName //
this.repairDetails.deviceName = this.detailForm.deviceName //
}
})
}
}
}
</script>
<style lang="scss" scoped>
.page-css {
width: 100%;
padding: 0;
padding-top: 26rpx;
padding-bottom: 180rpx;
.status_box {
margin: 0 24rpx 20rpx;
height: 180rpx;
border-radius: 20rpx;
background-color: rgba(255, 255, 255, 1);
color: rgba(16, 16, 16, 1);
font-size: 36rpx;
text-align: left;
font-family: -bold;
.status_top {
width: 100%;
padding: 40rpx 20rpx 30rpx 30rpx;
// background-color: red;
display: flex;
justify-content: space-between;
align-items: center;
.status_txt {
color: rgba(0, 0, 0, 1);
font-size: 36rpx;
text-align: left;
font-family: SourceHanSansSC-bold;
font-weight: bold;
}
.load_more {
color: rgba(108, 108, 108, 1);
font-size: 28rpx;
text-align: left;
font-family: SourceHanSansSC-regular;
display: flex;
align-items: center;
}
}
.status_bottom {
display: flex;
justify-content: space-between;
align-items: center;
color: rgba(190, 190, 190, 1);
font-size: 28rpx;
text-align: left;
font-family: SourceHanSansSC-regular;
.status_date {
margin-right: 22rpx;
}
}
}
.fault_detail {
margin: 0 24rpx 20rpx;
height: 180rpx;
border-radius: 20rpx;
background-color: rgba(255, 255, 255, 1);
}
.repair_detail {
margin: 0 24rpx 20rpx;
border-radius: 20rpx;
background-color: rgba(255, 255, 255, 1);
padding: 6rpx 0 34rpx;
.repair_title {
line-height: 60rpx;
color: rgba(0, 0, 0, 1);
font-size: 32rpx;
text-align: left;
font-family: SourceHanSansSC-bold;
margin: 18rpx 0 0rpx 20rpx;
font-weight: bold;
}
.detail_item {
padding: 4rpx 20rpx 0;
display: flex;
align-items: center;
justify-content: space-between;
.item_left_txt {
color: rgba(108, 108, 108, 1);
font-size: 28rpx;
text-align: left;
font-family: SourceHanSansSC-regular;
width: 210rpx;
margin-right: 20rpx;
line-height: 60rpx;
}
.item_right_txt {
max-width: calc(100% - 220rpx);
color: rgba(0, 0, 0, 1);
font-size: 28rpx;
// text-align: left;
text-align: justify;
font-family: SourceHanSansSC-regular;
}
.item_right_table {
width: calc(100% - 220rpx);
margin-top: 10rpx;
.table_box {
width: 100%;
height: 60rpx;
background-color: #e8e8e8;
border: 1px solid #bbbbbb;
font-size: 14px;
color: #101010;
line-height: 60rpx;
display: flex;
.left {
width: 72%;
height: 100%;
text-align: center;
border-right: 2rpx solid #bbbbbb;
}
.right {
width: 28%;
height: 100%;
text-align: center;
}
}
.table_item {
background-color: #fff;
border-top: none;
}
.table_sub {
border-top: none;
background-color: #fff;
}
}
.reason {
word-wrap: warp;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
/* 定义显示的行数 */
overflow: hidden;
text-overflow: ellipsis;
}
.video {
margin-top: 10rpx;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
}
}
.evaluate_box {
width: 100%;
height: 154rpx;
bottom: 0;
position: fixed;
background: #fff;
display: flex;
align-items: center;
justify-content: flex-end;
.evaluate_btn {
width: 160rpx;
height: 60rpx;
border-radius: 8rpx;
background-color: rgba(255, 255, 255, 1);
color: rgba(58, 98, 215, 1);
font-size: 24rpx;
text-align: center;
font-family: PingFangSC-regular;
border: 2rpx solid rgba(58, 98, 215, 1);
margin-right: 26rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.repair_form {
padding: 0 32rpx;
}
.repair-btn {
width: 100%;
display: flex;
justify-content: space-around;
/* 水平均匀分布 */
margin-top: 32rpx;
padding: 0 32rpx;
/deep/.u-btn {
width: 100%;
}
}
/deep/.popup-content {
display: flex;
flex-direction: column;
overflow: hidden;
}
.select-box {
width: 100%;
.select-box-item {
width: 100%;
border-radius: 20rpx;
background-color: rgba(249, 249, 249, 1);
margin-bottom: 32rpx;
line-height: 60rpx;
color: rgba(108, 108, 108, 1);
font-size: 28rpx;
padding: 24rpx;
}
}
.material-box {
flex: 1;
background: #fff;
margin: 0;
overflow: auto;
// padding-bottom: 32rpx;
.material-item {
height: 300rpx;
border-radius: 20rpx;
background-color: rgba(249, 249, 249, 1);
margin: 32rpx 0 0;
padding: 32rpx;
display: flex;
flex-direction: row;
.title-wrapper {}
.item-right {
flex: 1;
.material-title {
display: flex;
flex-direction: row;
color: rgba(108, 108, 108, 1);
font-size: 28rpx;
line-height: 60rpx;
.material-title-name {
color: rgba(51, 51, 51, 1);
font-size: 36rpx;
font-weight: 550;
padding-right: 16rpx;
}
.material-title-code {
flex: 1;
}
}
.material-text {
line-height: 60rpx;
color: rgba(108, 108, 108, 1);
font-size: 28rpx;
}
.material-num {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
}
}
.material-btn {
width: 100%;
display: flex;
justify-content: space-between;
/* 水平均匀分布 */
margin-top: 32rpx;
padding: 0 32rpx;
/deep/.u-btn {
width: 48%;
}
}
.selectForm {
width: 100%;
padding: 26rpx;
.form-item {
float: left;
width: calc(100% - 26rpx - 26rpx - 140rpx);
.material-text {
width: 50%;
float: left;
}
}
.form-label {
width: 140rpx;
text-align: center;
}
}
}
</style>

@ -1,7 +1,8 @@
<template>
<view class="page-css"
:style="{ 'padding-bottom': (detailForm.status == 5 && dataTypes == 1) ? '180rpx' : '20rpx' }">
<view class="">
:style="{ 'padding-bottom': (detailForm.status == 5 && dataTypes == 1) ? '180rpx' : '20rpx' }"
ref="contentToExport">
<view class="" id="capture">
<view class="repair_detail">
<view class="repair_title">{{ dataTypes == 1 ? '故障信息' : '故障详情' }}</view>
<view class="detail_item">
@ -85,7 +86,8 @@
</view>
</view>
<!-- 客服查看派单详情 -->
<view class="repair_detail" v-if="((detailForm.status != 0) && dataTypes == 2)||(dataTypes == 1&&!(detailForm.status == 0||detailForm.status == -1))||dataTypes == 4">
<view class="repair_detail"
v-if="((detailForm.status != 0) && dataTypes == 2) || (dataTypes == 1 && !(detailForm.status == 0 || detailForm.status == -1)) || dataTypes == 4">
<view class="repair_title">处理情况</view>
<view class="detail_item">
<text class="item_left_txt">接单时间</text>
@ -116,30 +118,7 @@
<text class="item_right_txt">{{ detailForm.repaiRejectReason }}</text>
</view>
</view>
<!-- 主管审批 审批详情 -->
<!-- <view class="repair_detail" v-if="detailForm.approveResult >= 0">
<view class="repair_title">主管审批</view>
<view class="detail_item">
<text class="item_left_txt">审批结果</text>
<text class="item_right_txt">{{ detailForm.approveResult == 1 ? '通过' : '驳回' }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">审批意见</text>
<text class="item_right_txt">{{ detailForm.approveRemark }}</text>
</view>
</view> -->
<!-- 客户审批 审批详情 -->
<!-- <view class="repair_detail" v-if="detailForm.approveResult >= 0">
<view class="repair_title">客户审批</view>
<view class="detail_item">
<text class="item_left_txt">审批结果</text>
<text class="item_right_txt">{{ detailForm.approveResult == 1 ? '通过' : '驳回' }}</text>
</view>
<view class="detail_item">
<text class="item_left_txt">审批意见</text>
<text class="item_right_txt">{{ detailForm.approveRemark }}</text>
</view>
</view> -->
<view class="repair_detail" v-if="detailForm.status != -1">
<view class="repair_title">其他信息</view>
<view class="detail_item">
@ -158,27 +137,17 @@
<text class="item_left_txt">提报时间</text>
<text class="item_right_txt">{{ detailForm.createTime }}</text>
</view>
<!-- <view class="detail_item" v-show="detailForm.status != 0">
<text class="item_left_txt">系统接单时间</text>
<text class="item_right_txt">{{ detailForm.dispatchTime }}</text>
</view>
<view class="detail_item" v-show="detailForm.status != 0">
<text class="item_left_txt">客服意见</text>
<text class="item_right_txt reason">{{ detailForm.customerOpinion }}</text>
</view> -->
</view>
<!-- -->
<!-- <view class="repair_detail" v-if="(detailForm.status != 1) && dataTypes == 4">
<view class="repair_title">处理情况</view>
<view class="detail_item">
<text class="item_left_txt">接单时间</text>
<text class="item_right_txt reason">{{ detailForm.status }}</text>
</view>
</view> -->
</view>
</view>
</template>
<script>
import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'
export default {
computed: {
dataTypes() {
@ -187,23 +156,6 @@ export default {
},
data() {
return {
tableList: [
{
id: '001',
goodsName: '物料1',
goodsPrice: '1000.00'
},
{
id: '002',
goodsName: '物料2',
goodsPrice: '59.00'
},
{
id: '003',
goodsName: '物料3',
goodsPrice: '25.00'
},
],
totalPrice: '1084.00',
discount: '9',
discountPrice: '975.60',
@ -211,18 +163,6 @@ export default {
detailForm: {},
videoUrl: '',
userInfo: {},
//
isMaterialList: [
{
text: '是',
value: 1
},
{
text: '否',
value: 2
}
],
//
repairDetails: {
reportPeople: '',//
deviceName: '',//
@ -231,37 +171,7 @@ export default {
isMaterial: null,//
materialTable: [],//
},
rules: {
faultReason: [
{
type: "string",
required: true,
message: "请输入",
trigger: ["blur", "change"],
}
],
dealMethods: [
{
type: "string",
required: true,
message: "请输入",
trigger: ["blur", "change"],
}
]
},
aterialShow: false,//
goodsearchInfo: {
goodsName: '',
},
goodsTableData: [],
goodspage: {
current: 1,
size: 10,
},
isGoodsNomore: false,
selectGoodsList: []
imageSrc: '',//
}
},
onLoad(options) {
@ -284,7 +194,6 @@ export default {
},
mounted() {
this.getGoodsList()
},
methods: {
getDetail() {
@ -304,150 +213,28 @@ export default {
}
})
},
//
repairSubmit() {
this.$refs.repairDetailsForm.setRules(this.rules);
// this.repairDetailsForm
this.$refs.repairDetailsForm.validate(valid => {
if (valid) {
if (this.repairDetails.isMaterial == null) {
uni.showToast({ title: "是否申领物料", icon: "none" });
return false
}
var data = []
if (this.repairDetails.isMaterial == 1 && this.selectGoodsList.length <= 0) {
uni.showToast({ title: "请选择需要申领的物料信息", icon: "none" });
return false
}
//
if (this.selectGoodsList.find(item => item.goodsNum == 0)) {
uni.showToast({ title: "申领数量不能为0", icon: "none" });
return false
}
this.selectGoodsList.forEach((item, index) => {
data.push({
orderId: this.detailForm.id,
materialName: item.name,
materialCount: item.goodsNum,
materialPrice: item.price,
bigClassId: item.bigClassId,
rule: item.rule,
unit: item.unit,
xh: item.xh
})
})
let query = {
id: this.detailForm.id,
faultCause: this.repairDetails.faultReason,
processMethod: this.repairDetails.dealMethods,
isNeedMaterial: this.repairDetails.isMaterial,
status: 4,
materials: data
}
this.$u.api.editData(query).then(res => {
if (res.code == 200) {
uni.showToast({ title: "维修成功", icon: "none" });
setTimeout(() => {
uni.navigateBack({
delta: 1 // 1退
})
}, 500)
}
})
}
})
},
//
statusName(item) {
if (item == -1) {
return '待提报'
}
if (item == 0) {
return '待接单'
}
if (item == 1 || item == 2 || item == 3) {
return '待维修'
}
if (item == 4 || item == 5 || item == 6) {
return '已完成'
}
},
//
goodsSelect() {
this.aterialShow = true
//
//
// this.goodsTableData
this.goodsTableData.forEach(i => {
i.checked = false
})
if (this.selectGoodsList.length > 0) {
this.goodsTableData.forEach(i => {
this.selectGoodsList.forEach(v => {
if (i.id == v.id) {
i.checked = true
}
})
})
}
},
//
goodsSubmit() {
let selected = this.goodsTableData.filter(i => {
return i.checked
})
this.selectGoodsList = JSON.parse(JSON.stringify(selected))
this.aterialShow = false
async captureScreen() {
// DOM
// const element = document.getElementById('capture');\
const element = uni.createSelectorQuery().select('#capture')
// 使html2canvasDOMcanvas
const canvas = await html2canvas(element);
// jspdf
const pdf = new jsPDF('p', 'mm', 'a4');
// canvaspdf
const img = canvas.toDataURL('image/png');
const imgProps = pdf.getImageProperties(img);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
pdf.addImage(img, 'PNG', 0, 0, pdfWidth, pdfHeight);
console.log(1111111, img)
this.imageSrc = img
// PDF
pdf.save('download.pdf');
},
//
getGoodsList() {
let query_ = {
...this.goodspage
}
this.$u.api.getGoodsList(query_).then(res => {
if (res.code == 200) {
var totalPage = res.data.total;
var newOrders = res.data.records;
newOrders.forEach(element => {
// element.rule = element.rule == '' ? 0 : Number(element.rule)
this.$set(element, 'goodsNum', 0)
this.$set(element, 'checked', false)
});
this.goodsTableData = [...this.goodsTableData, ...(res.data.records || [])];
if (newOrders.length == 0) {
// this.loadingState = "nomore";
this.isGoodsNomore = true;
}
if (totalPage == this.goodsTableData.length) {
// this.loadingState = "nomore";
this.isGoodsNomore = true;
}
}
})
},
//
searchGoodsData() {
// selectGoodsList
},
//
goodsLower() {
if (this.isGoodsNomore) {
return;
}
this.goodspage.current++;
this.getGoodsList();
},
//
goPage(path) {
uni.navigateTo({
url: path,
});
}
}
}

@ -0,0 +1,57 @@
## 1.2.3(2024-09-19)
1. 更新示例工程
## 1.2.2(2024-05-08)
1. 修改util.js路径为@/uni_modules/sp-html2canvas-render/utils/index.js
## 1.2.1(2024-05-08)
1. 更新示例工程
## 1.2.0(2024-04-30)
1. 示例工程中示例二已更新解决截图模糊的问题,请参考
## 1.1.9(2024-04-30)
1.更新示例工程
## 1.1.8(2024-04-30)
1. 优化了报错提示
2. domId设为必填
## 1.1.7(2024-04-29)
1. h2c配置项更新
## 1.1.6(2024-03-08)
1. 更新示例工程
2. 更新文档
## 1.1.5(2024-02-29)
1. 更新示例工程示例四:横向长截图
## 1.1.4(2024-02-27)
1. 更新base64ToPath方法,以供需要用临时路径保存文件的小伙伴,见示例一中使用
## 1.1.3(2024-02-24)
1. 更新示例工程:示例3:动态domid,可动态选择单独导出指定dom
## 1.1.2(2024-02-06)
1. 更新文档
## 1.1.1(2024-02-06)
1. 重大更新,详见文档
2. 更新示例工程
## 1.1.0(2024-02-05)
1. 更新文档
## 1.0.9(2024-02-05)
1. 重要更新:插件更新内置了urlToBase64工具方法,图片报错toDataURL on HTMLCanvasElement或其他问题导致图片无法正常渲染的,可以使用该工具方法将图片路径(支持网络路径和本地相对路径)转换为base64格式
## 1.0.8(2024-02-04)
1. 更新示例工程
## 1.0.7(2024-01-24)
1. 更新示例工程延迟渲染示例
## 1.0.6(2024-01-16)
1. 更新示例工程
2. 更新文档
## 1.0.5(2024-01-15)
1. 更新示例工程
## 1.0.4(2023-12-08)
1. 更新html2canvas中部分配置
2. 重新上传示例工程,还请麻烦用hbuilderx一键导入示例,不要使用zip下载的方式
3. 更新文档说明,很多人问toDataURL on HTMLCanvasElement的问题,有的人哪怕直接使用实例工程也会报错,有的人却一切正常,我对此提供了部分解决方式。
## 1.0.3(2023-11-29)
1.更新示例工程,内联使用方式详见inner页面
## 1.0.2(2023-11-27)
1.更新示例工程样例
## 1.0.1(2023-11-27)
1.解决在vue2的ap真机环境下可能出现的报错
2.更新示例代码,兼容vue2/3写法,支持图片生成样例
## 1.0.0(2023-10-20)
1. 组件更新,使用方式详见README
2. 更新示例项目

@ -0,0 +1,110 @@
<template>
<!-- prop是自定义的数据字段名要与chang后的保持一致h2cRender是renderjs的module名称 -->
<view class="sp-html2canvas-render" :prop="domId" :change:prop="h2cRender.watchDomId">
<slot></slot>
<text :prop="expOver" :change:prop="h2cRender.watchExpOver"></text>
</view>
</template>
<script>
export default {
props: {
// domid
domId: {
type: String,
default: '',
require: true
}
},
data() {
return {
expOver: 0,
// #ifdef VUE2
// #ifdef APP
// Vue2APPh2cRender
h2cRender: null
// #endif
// #endif
}
},
methods: {
renderOver(e) {
//
this.$emit('renderOver', e)
},
h2cRenderDom() {
// #ifdef H5
this.renderDom()
// #endif
// #ifndef H5
// expOverRenderJs
this.expOver++
// .....
// #endif
}
}
}
</script>
<!-- renderjs目前仅支持内联使用 -->
<script module="h2cRender" lang="renderjs">
import html2canvas from 'html2canvas';
export default {
data() {
return {
domIdValue: ''
}
},
methods: {
async renderDom() {
// appdomId,scriptdataprops
// ,script,data
try {
const el = document.getElementById(this.domIdValue);
if(!el) {
console.error('dom盒子未加载成功,请先确保dom渲染完成,再检查你的domId是否有误');
return
}
/**
* 配置说明
* 1. allowTaint:true和useCORS:true都是解决跨域问题的方式(不一定完全能解决跨域)不同的是使用allowTaint会对canvas造成污染导致无法使用canvas.toDataURL方法
* 2. 想要完美解决跨域还得需要后端服务器设置access-control-allow-origin: *允许资源跨域访问前端设置useCORS:true
* 2. scale通过放大倍率来调整画质清晰度但是只调整这一个参数可能不是最优解
*/
const canvas = await html2canvas(el, {
width: el.offsetWidth,
height: el.offsetHeight,
x: 0,
y: 0,
logging: true,
useCORS: true,
// allowTaint: true,
// async: false,
scale: 2, // 2
// foreignObjectRendering: true, //
});
const base64 = canvas.toDataURL('image/png', 1);
this.$ownerInstance.callMethod('renderOver', base64);
} catch(err) {
console.log('==== err :', err.message);
}
},
// ,script,data
watchDomId(newValue, oldValue, ownerInstance, instance) {
this.domIdValue = newValue
},
watchExpOver(newValue, oldValue, ownerInstance, instance) {
if(newValue !== 0){
//
this.renderDom()
}
}
}
}
</script>
<style>
.sp-html2canvas-render {
position: relative;
}
</style>

@ -0,0 +1,86 @@
{
"id": "sp-html2canvas-render",
"displayName": "基于html2canvas和renderjs 指定盒子截图 截屏 截长屏",
"version": "1.2.3",
"description": "基于html2canvas和renderjs的页面指定盒子截图,截屏,可截长屏,页面生成图片",
"keywords": [
"截图",
"截屏",
"截长屏",
"生成图片",
"html2canvas"
],
"repository": "",
"engines": {
"HBuilderX": "^3.8.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

@ -0,0 +1,11 @@
# sp-html2canvas-render
### 文档迁移
> 防止文档失效,提供下列五个地址,内容一致
- [地址一](https://sonvee.github.io/sv-app-docs/docs-github/src/plugins/sp-html2canvas-render/sp-html2canvas-render.html)
- [地址二](https://sv-app-docs.pages.dev/src/plugins/sp-html2canvas-render/sp-html2canvas-render.html)
- [地址三](https://sv-app-docs.4everland.app/src/plugins/sp-html2canvas-render/sp-html2canvas-render.html)
- [地址四](https://sv-app-docs.vercel.app/src/plugins/sp-html2canvas-render/sp-html2canvas-render.html) (需要梯子)
- [地址五](https://static-mp-74bfcbac-6ba6-4f39-8513-8831390ff75a.next.bspapp.com/docs-uni/src/plugins/sp-html2canvas-render/sp-html2canvas-render.html) (有IP限制)

@ -0,0 +1,255 @@
function getLocalFilePath(path) {
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf(
'_downloads') === 0) {
return path
}
if (path.indexOf('file://') === 0) {
return path
}
if (path.indexOf('/storage/emulated/0/') === 0) {
return path
}
if (path.indexOf('/') === 0) {
let localFilePath = plus.io.convertAbsoluteFileSystem(path)
if (localFilePath !== path) {
return localFilePath
} else {
path = path.substr(1)
}
}
return '_www/' + path
}
function dataUrlToBase64(str) {
let array = str.split(',')
return array[array.length - 1]
}
let index = 0
function getNewFileId() {
return Date.now() + String(index++)
}
function biggerThan(v1, v2) {
let v1Array = v1.split('.')
let v2Array = v2.split('.')
let update = false
for (let index = 0; index < v2Array.length; index++) {
let diff = v1Array[index] - v2Array[index]
if (diff !== 0) {
update = diff > 0
break
}
}
return update
}
export function pathToBase64(path) {
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
if (typeof FileReader === 'function') {
let xhr = new XMLHttpRequest()
xhr.open('GET', path, true)
xhr.responseType = 'blob'
xhr.onload = function() {
if (this.status === 200) {
let fileReader = new FileReader()
fileReader.onload = function(e) {
resolve(e.target.result)
}
fileReader.onerror = reject
fileReader.readAsDataURL(this.response)
}
}
xhr.onerror = reject
xhr.send()
return
}
let canvas = document.createElement('canvas')
let c2x = canvas.getContext('2d')
let img = new Image
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
c2x.drawImage(img, 0, 0)
resolve(canvas.toDataURL())
canvas.height = canvas.width = 0
}
img.onerror = reject
img.src = path
return
}
if (typeof plus === 'object') {
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) {
entry.file(function(file) {
let fileReader = new plus.io.FileReader()
fileReader.onload = function(data) {
resolve(data.target.result)
}
fileReader.onerror = function(error) {
reject(error)
}
fileReader.readAsDataURL(file)
}, function(error) {
reject(error)
})
}, function(error) {
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
wx.getFileSystemManager().readFile({
filePath: path,
encoding: 'base64',
success: function(res) {
resolve('data:image/png;base64,' + res.data)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
export function base64ToPath(base64) {
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
base64 = base64.split(',')
let type = base64[0].match(/:(.*?);/)[1]
let str = atob(base64[1])
let n = str.length
let array = new Uint8Array(n)
while (n--) {
array[n] = str.charCodeAt(n)
}
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], {
type: type
})))
}
let extName = base64.split(',')[0].match(/data\:\S+\/(\S+);/)
if (extName) {
extName = extName[1]
} else {
reject(new Error('base64 error'))
}
let fileName = getNewFileId() + '.' + extName
if (typeof plus === 'object') {
let basePath = '_doc'
let dirPath = 'uniapp_temp'
let filePath = basePath + '/' + dirPath + '/' + fileName
if (!biggerThan(plus.os.name === 'Android' ? '1.9.9.80627' : '1.9.9.80472', plus.runtime.innerVersion)) {
plus.io.resolveLocalFileSystemURL(basePath, function(entry) {
entry.getDirectory(dirPath, {
create: true,
exclusive: false,
}, function(entry) {
entry.getFile(fileName, {
create: true,
exclusive: false,
}, function(entry) {
entry.createWriter(function(writer) {
writer.onwrite = function() {
resolve(filePath)
}
writer.onerror = reject
writer.seek(0)
writer.writeAsBinary(dataUrlToBase64(base64))
}, reject)
}, reject)
}, reject)
}, reject)
return
}
let bitmap = new plus.nativeObj.Bitmap(fileName)
bitmap.loadBase64Data(base64, function() {
bitmap.save(filePath, {}, function() {
bitmap.clear()
resolve(filePath)
}, function(error) {
bitmap.clear()
reject(error)
})
}, function(error) {
bitmap.clear()
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
let filePath = wx.env.USER_DATA_PATH + '/' + fileName
wx.getFileSystemManager().writeFile({
filePath: filePath,
data: dataUrlToBase64(base64),
encoding: 'base64',
success: function() {
resolve(filePath)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
/**
* 图片地址转换为base64格式图片
* @param {string} url 图片地址 网络地址 本地相对路径
* @param {string} type base64图片类型 默认png
*/
export function urlToBase64(url, type = 'png') {
let promises
// 网络地址 或者h5端本地相对路径 可使用request方式
promises = new Promise((resolve, reject) => {
uni.request({
url: url,
method: 'GET',
responseType: 'arraybuffer',
success: (res) => {
const base64 = `data:image/${type};base64,${uni.arrayBufferToBase64(res.data)}`
resolve(base64);
},
fail: (err) => {
reject(err);
},
})
})
// #ifdef APP
if (!url.startsWith('http')) {
// app真机本地相对路径
promises = new Promise((resolve, reject) => {
// 使用compressImage获取到安卓本地路径file:///...
uni.compressImage({
src: url,
quality: 100,
success: (res) => {
const tempUrl = res.tempFilePath
plus.io.resolveLocalFileSystemURL(tempUrl, (entry) => {
entry.file((e) => {
let fileReader = new plus.io.FileReader();
fileReader.onload = (r) => {
resolve(r.target.result)
}
fileReader.readAsDataURL(e)
})
})
},
fail: (err) => {
reject(err);
},
})
})
}
// #endif
return promises
}

@ -0,0 +1,34 @@
## 1.1.2(2024-06-06)
1. pdf添加页头页尾占位
## 1.1.1(2024-06-05)
1. 修复ios无法导出的问题
## 1.1.0(2024-05-13)
1. 新增openPDF实例事件,以便于关闭autoOpen的之后,方便在任何时机一键重新打开文档
## 1.0.9(2024-05-13)
1. 处理加载loading有延时问题和部分场景下未正常关闭问题
## 1.0.8(2024-05-08)
1. 修改原util.js路径为@/uni_modules/sp-html2pdf-render/utils/index.js
## 1.0.7(2024-05-08)
1. 更新示例工程
2. 修改原renderCanvas事件名为renderOver
3. 移除原afterSavePDF事件
4. 新增autoOpen属性
5. 文档迁移
## 1.0.6(2024-03-18)
1. 新增pdfFileName属性,以自定义导出pdf的文件名,为空则默认以当前时间戳命名
2. 更新文档
## 1.0.5(2024-02-06)
1. 更新文档
## 1.0.4(2024-02-06)
1. 重大更新,详情见文档
2. 示例工程更新
## 1.0.3(2024-02-06)
1. 更新文档
## 1.0.2(2024-02-06)
1. 更新示例工程
2. 插件优化,建议时刻保持最新版插件
## 1.0.1(2024-02-05)
1. 更新示例工程,包括但不限于内联使用示例
## 1.0.0(2023-10-24)
1. 发布插件
2. 使用方式详见下文或示例项目

@ -0,0 +1,277 @@
<template>
<!-- prop是自定义的数据字段名要与chang后的保持一致h2pRender是renderjs的module名称 -->
<view class="sp-html2pdf-render" :prop="domId" :change:prop="h2pRender.watchDomId">
<!-- 导出的PDF会撑满宽度所以请事先规范好要渲染盒子的宽度PDF以一般A4纸大小尺寸会自动分页 -->
<!-- 默认带有插槽但是具体是否要将渲染的dom盒子放入插槽中由开发者自行决定导出结果只与domId有关 -->
<slot></slot>
<text :prop="expOver" :change:prop="h2pRender.watchExpOver"></text>
</view>
</template>
<script>
import { base64ToPath } from '../../utils/index.js'
export default {
props: {
// domid
domId: {
type: String,
default: '',
require: true
},
// loading
showLoading: {
type: Boolean,
default: true
},
// pdf使
pdfFileName: {
type: String,
default: ''
},
//
autoOpen: {
type: Boolean,
default: true
}
},
data() {
return {
expOver: 0,
// #ifdef VUE2
// #ifdef APP
// Vue2APPh2pRender
h2pRender: null
// #endif
// #endif
}
},
methods: {
renderOver(e) {
// canvas
this.$emit('renderOver', e)
},
savePDF(e) {
// pdf
if (this.showLoading) uni.showLoading({ title: '导出中' })
this.$emit('beforeSavePDF', e)
//
if (!this.autoOpen) {
if (this.showLoading) uni.hideLoading()
return
}
this.openPDF(e)
},
/**
* 手动打开pdf文档
* @param {Object} e pdf的base64字符串
*/
openPDF(e) {
/**
* base64ToPath
* @param {Object} e base64字符串
* @param {String} name 导出的文件名未命名则以当前时间戳命名已自动拼接后缀
*/
base64ToPath(e, this.pdfFileName)
.then((path) => {
uni.openDocument({
filePath: path,
success: () => {
this.$emit('successSavePDF', path)
},
fail: (err) => {
console.error('openDocument error', err)
}
})
})
.catch((error) => {
console.error('base64ToPath error', error)
})
.finally(() => {
if (this.showLoading) uni.hideLoading()
})
},
//
async h2pRenderDom() {
uni.showLoading({ title: '加载中' })
// #ifdef H5
await this.renderDom()
// #endif
// #ifndef H5
// expOverRenderJs
this.expOver++
// .....
// #endif
uni.hideLoading()
}
}
}
</script>
<!-- renderjs目前仅支持内联使用 -->
<script module="h2pRender" lang="renderjs">
import html2canvas from 'html2canvas';
import JsPDF from 'jspdf'
export default {
data() {
return {
domIdValue: ''
}
},
methods: {
async renderDom() {
// appdomId,scriptdataprops
// ,script,data
try {
const el = document.getElementById(this.domIdValue);
if(!el) {
console.error('dom盒子未加载成功,请先确保dom渲染完成,再检查你的domId是否有误');
return
}
/**
* 配置说明
* 1. allowTaint:true和useCORS:true都是解决跨域问题的方式(不一定完全能解决跨域)不同的是使用allowTaint会对canvas造成污染导致无法使用canvas.toDataURL方法
* 2. 想要完美解决跨域还得需要后端服务器设置access-control-allow-origin: *允许资源跨域访问前端设置useCORS:true
* 2. scale通过放大倍率来调整画质清晰度但是只调整这一个参数可能不是最优解
*/
const canvas = await html2canvas(el, {
width: el.offsetWidth,
height: el.offsetHeight,
x: 0,
y: 0,
logging: true,
useCORS: true,
// allowTaint: true,
// async: false,
scale: 2, // 2
// foreignObjectRendering: true, //
});
const base64 = canvas.toDataURL('image/png', 1);
this.$ownerInstance.callMethod('renderOver', base64);
// PDF
//
let divNode = document.createElement("div")
divNode.style.width = '100%' // div100%
divNode.style.height = '20px' // div20px
document.body.appendChild(divNode)
const divCanvas = await html2canvas(divNode, {
width: divNode.offsetWidth,
height: divNode.offsetHeight,
x: 0,
y: 0,
scale: 0.5, // 2
backgroundColor: '#ffffff', // pdf
});
this.createPDF(canvas, divCanvas)
} catch(err) {
console.log('==== err :', err);
}
},
// ,script,data
watchDomId(newValue, oldValue, ownerInstance, instance) {
this.domIdValue = newValue
},
watchExpOver(newValue, oldValue, ownerInstance, instance) {
if(newValue !== 0){
//
this.renderDom()
}
},
createPDF(canvas, divCanvas) {
const a4width = 592.28; // A4
const a4height = 841.89; // A4
const divheight = 20; //
// PDF
let contentWidth = canvas.width
let contentHeight = canvas.height
// pdfhtmlcanvas;
let pageHeight = (contentWidth / a4width) * a4height - divheight * 2
// pdfhtml
let leftHeight = contentHeight
//
let position = 0
// a4[a4width,a4height]htmlcanvaspdf
let imgWidth = a4width
let imgHeight = (a4width / contentWidth) * contentHeight
let pageData = canvas.toDataURL('image/png', 1.0)
let divData = divCanvas.toDataURL('image/png', 1.0)
let pdf = new JsPDF('', 'pt', 'a4')
// htmlpdf(a4height)
// pdf
if (leftHeight < pageHeight) {
// pdf.addImage(pageData, 'JPEG', )pdf
pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
//
while (leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
//
pdf.addImage(divData, 'JPEG', 0, 0, imgWidth, divheight)
pdf.addImage(divData, 'JPEG', 0, a4height - divheight, imgWidth, divheight)
leftHeight -= pageHeight
position -= a4height - divheight * 2
//
if (leftHeight > 0) {
pdf.addPage()
}
}
}
// #ifdef H5
//
// pdf.save('PDF')
let base64Str = pdf.output('dataurlstring');
this.$ownerInstance.callMethod('savePDF', base64Str)
// #endif
// #ifdef APP
if(plus.os.name == 'Android') {
plus.android.requestPermissions(['android.permission.WRITE_EXTERNAL_STORAGE'], (e) => {
if (e.deniedAlways.length > 0) { //
//
uni.showModal({
title: '存储权限',
content: '您拒绝了存储权限,请去设置-应用开启存储权限。',
success: function(res) {
if (res.confirm) {
// console.log('');
} else if (res.cancel) {
// console.log('');
}
}
});
}
if (e.deniedPresent.length > 0) { //
// plus.android.requestPermissions
plus.android.requestPermissions(['android.permission.WRITE_EXTERNAL_STORAGE'])
}
if (e.granted.length > 0) { //
//
let base64Str = pdf.output('dataurlstring');
this.$ownerInstance.callMethod('savePDF', base64Str)
}
}, function(e) {
});
} else {
// ios
let base64Str = pdf.output('dataurlstring');
this.$ownerInstance.callMethod('savePDF', base64Str)
}
// #endif
}
}
}
</script>
<style>
.sp-html2pdf-render {
position: relative;
}
</style>

@ -0,0 +1,85 @@
{
"id": "sp-html2pdf-render",
"displayName": "基于html2canvas+jspdf的截图导出成pdf",
"version": "1.1.2",
"description": "html转pdf,html转canvas,pdf文件保存导出分享,指定渲染的盒子导出图片截图或者pdf文件。",
"keywords": [
"html2pdf",
"html2canvas",
"pdf",
"canvas"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

@ -0,0 +1,11 @@
# sp-html2pdf-render
### 文档迁移
> 防止文档失效,提供下列五个地址,内容一致
- [地址一](https://sonvee.github.io/sv-app-docs/docs-github/src/plugins/sp-html2pdf-render/sp-html2pdf-render.html)
- [地址二](https://sv-app-docs.pages.dev/src/plugins/sp-html2pdf-render/sp-html2pdf-render.html)
- [地址三](https://sv-app-docs.4everland.app/src/plugins/sp-html2pdf-render/sp-html2pdf-render.html)
- [地址四](https://sv-app-docs.vercel.app/src/plugins/sp-html2pdf-render/sp-html2pdf-render.html) (需要梯子)
- [地址五](https://static-mp-74bfcbac-6ba6-4f39-8513-8831390ff75a.next.bspapp.com/docs-uni/src/plugins/sp-html2pdf-render/sp-html2pdf-render.html) (有IP限制)

@ -0,0 +1,261 @@
function getLocalFilePath(path) {
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf(
'_downloads') === 0) {
return path
}
if (path.indexOf('file://') === 0) {
return path
}
if (path.indexOf('/storage/emulated/0/') === 0) {
return path
}
if (path.indexOf('/') === 0) {
let localFilePath = plus.io.convertAbsoluteFileSystem(path)
if (localFilePath !== path) {
return localFilePath
} else {
path = path.substr(1)
}
}
return '_www/' + path
}
function dataUrlToBase64(str) {
let array = str.split(',')
return array[array.length - 1]
}
let index = 0
function getNewFileId() {
return Date.now() + String(index++)
}
function biggerThan(v1, v2) {
let v1Array = v1.split('.')
let v2Array = v2.split('.')
let update = false
for (let index = 0; index < v2Array.length; index++) {
let diff = v1Array[index] - v2Array[index]
if (diff !== 0) {
update = diff > 0
break
}
}
return update
}
export function pathToBase64(path) {
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
if (typeof FileReader === 'function') {
let xhr = new XMLHttpRequest()
xhr.open('GET', path, true)
xhr.responseType = 'blob'
xhr.onload = function() {
if (this.status === 200) {
let fileReader = new FileReader()
fileReader.onload = function(e) {
resolve(e.target.result)
}
fileReader.onerror = reject
fileReader.readAsDataURL(this.response)
}
}
xhr.onerror = reject
xhr.send()
return
}
let canvas = document.createElement('canvas')
let c2x = canvas.getContext('2d')
let img = new Image
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
c2x.drawImage(img, 0, 0)
resolve(canvas.toDataURL())
canvas.height = canvas.width = 0
}
img.onerror = reject
img.src = path
return
}
if (typeof plus === 'object') {
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) {
entry.file(function(file) {
let fileReader = new plus.io.FileReader()
fileReader.onload = function(data) {
resolve(data.target.result)
}
fileReader.onerror = function(error) {
reject(error)
}
fileReader.readAsDataURL(file)
}, function(error) {
reject(error)
})
}, function(error) {
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
wx.getFileSystemManager().readFile({
filePath: path,
encoding: 'base64',
success: function(res) {
resolve('data:image/png;base64,' + res.data)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
export function base64ToPath(base64, filename) {
return new Promise(function(resolve, reject) {
// web端
if (typeof window === 'object' && 'document' in window) {
base64 = base64.split(',')
let type = base64[0].match(/:(.*?);/)[1]
let str = atob(base64[1])
let n = str.length
let array = new Uint8Array(n)
while (n--) {
array[n] = str.charCodeAt(n)
}
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], {
type: type
})))
}
// 移动端
let extName = base64.split(',')[0].match(/data\:\S+\/(\S+);/)
// extName 示例为 ["data:application/pdf;filename=generated.pdf;", "pdf;filename=generated.pdf"]
if (extName) {
extName = extName[1].split(';')[0]
} else {
reject(new Error('base64 error'))
}
let fileName = (filename || Date.now()) + '.' + extName
if (typeof plus === 'object') {
let basePath = '_doc'
let dirPath = 'uniapp_temp'
let filePath = basePath + '/' + dirPath + '/' + fileName
if (!biggerThan(plus.os.name === 'Android' ? '1.9.9.80627' : '1.9.9.80472', plus.runtime.innerVersion)) {
plus.io.resolveLocalFileSystemURL(basePath, function(entry) {
entry.getDirectory(dirPath, {
create: true,
exclusive: false,
}, function(entry) {
entry.getFile(fileName, {
create: true,
exclusive: false,
}, function(entry) {
entry.createWriter(function(writer) {
writer.onwrite = function() {
resolve(filePath)
}
writer.onerror = reject
writer.seek(0)
writer.writeAsBinary(dataUrlToBase64(base64))
}, reject)
}, reject)
}, reject)
}, reject)
return
}
let bitmap = new plus.nativeObj.Bitmap(fileName)
bitmap.loadBase64Data(base64, function() {
bitmap.save(filePath, {}, function() {
bitmap.clear()
resolve(filePath)
}, function(error) {
bitmap.clear()
reject(error)
})
}, function(error) {
bitmap.clear()
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
let filePath = wx.env.USER_DATA_PATH + '/' + fileName
wx.getFileSystemManager().writeFile({
filePath: filePath,
data: dataUrlToBase64(base64),
encoding: 'base64',
success: function() {
resolve(filePath)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
/**
* 图片地址转换为base64格式图片
* @param {string} url 图片地址 网络地址 本地相对路径
* @param {string} type base64图片类型 默认png
*/
export function urlToBase64(url, type = 'png') {
let promises
// 网络地址 或者h5端本地相对路径 可使用request方式
promises = new Promise((resolve, reject) => {
uni.request({
url: url,
method: 'GET',
responseType: 'arraybuffer',
success: (res) => {
const base64 = `data:image/${type};base64,${uni.arrayBufferToBase64(res.data)}`
resolve(base64);
},
fail: (err) => {
reject(err);
},
})
})
// #ifdef APP
if (!url.startsWith('http')) {
// app真机本地相对路径
promises = new Promise((resolve, reject) => {
// 使用compressImage获取到安卓本地路径file:///...
uni.compressImage({
src: url,
quality: 100,
success: (res) => {
const tempUrl = res.tempFilePath
plus.io.resolveLocalFileSystemURL(tempUrl, (entry) => {
entry.file((e) => {
let fileReader = new plus.io.FileReader();
fileReader.onload = (r) => {
resolve(r.target.result)
}
fileReader.readAsDataURL(e)
})
})
},
fail: (err) => {
reject(err);
},
})
})
}
// #endif
return promises
}

@ -5,7 +5,7 @@ export default {
// baseUrl: 'http://124.221.142.15:8088/lab',
// baseUrl:'http://192.168.0.111:80', //李庆坤
// baseUrl:'http://192.168.0.109:80', //李涛
baseUrl:'http://192.168.1.12:80', //李涛
baseUrl:'http://192.168.0.113:80', //李涛
indexTitle: "科研医疗建筑运维平台",
clientId: "saber", // 客户端id
clientSecret: "saber_secret", // 客户端密钥

Loading…
Cancel
Save