Jelajahi Sumber

使用简历附件生成在线简历

lifanagju_citu 5 bulan lalu
induk
melakukan
31afb5c780

+ 6 - 0
pages.json

@@ -50,6 +50,12 @@
 		{
 			"root": "pagesA",
 			"pages": [
+				{
+					"path": "resumeAnalysis/index",
+					"style": {
+						"navigationBarTitleText": "简历解析"
+					}
+				},
 				{
 					"path": "resumeOnline/index",
 					"style": {

+ 31 - 5
pagesA/resume/index.vue

@@ -1,23 +1,27 @@
 <template>
   <view class="ss-p-b-100" style="height: 100vh; background-color: #f2f4f7;">
     <uni-notice-bar text="温馨提示:最多可以上传5份附件简历。请在手机上打开此小程序进行文件上传,暂不支持在桌面版小程序中上传文件。" />
-    <view v-if="bioList.length > 0">
-			<uni-card v-for="(item, index) in bioList" :key="index" :is-shadow="true" :border='false' shadow="0px 0px 3px 1px rgba(0,0,0,0.1)">
-				<view class="d-flex align-center">
+    <view v-if="bioList?.length > 0">
+      <uni-card v-for="(item, index) in bioList" :key="index" :is-shadow="true" :border='false' shadow="0px 0px 3px 1px rgba(0,0,0,0.1)">
+        <view class="d-flex align-center">
+          <view v-if="props.resumeAnalysis" class="ss-m-r-15">
+            <radio :value="index" :checked="index === checkedIndex" @tap="radioChange(index)" />
+          </view>
           <view @click="preview(item.url)"  style="flex: 1;">
             <view class="font-size-14" style="font-weight: bold;">{{ item.title }}</view>
-				    <view>上传时间:{{ timesTampChange(item.createTime, 'Y-M-D') }}</view>
+            <view>上传时间:{{ timesTampChange(item.createTime, 'Y-M-D') }}</view>
           </view>
           <view class="ss-m-l-30" style="width: 60rpx;">
             <uni-icons @click="handleOpenPopup(item)" type="more-filled" size="20"></uni-icons>
           </view>
         </view>
-			</uni-card>
+      </uni-card>
 		</view>
 		<view v-else class="nodata-img-parent">
 			<image src="https://minio.citupro.com/dev/static/nodata.png" mode="widthFix" style="width: 100vw;height: 100vh;"></image>
 		</view>
     <view class="bottom-sticky flex-column">
+      <button class="recomm-button" style="margin-bottom: 0;" @click="handleResumeAnalysis">开始解析</button>
       <button class="recomm-button" style="margin-bottom: 0;" @click="handleUpload">微信聊天文件上传</button>
       <view class="color-primary font-size-14 ss-m-b-25 ss-m-t-10" style="text-align: center;">上传文件大小不能超过20MB</view>
     </view>
@@ -35,6 +39,10 @@ import { getPersonResumeCv, saveResume, deleteResume } from '@/api/user'
 import { uploadFile } from '@/api/file'
 import { timesTampChange } from '@/utils/date'
 import { preview } from '@/utils/preview'
+const emit = defineEmits(['submit'])
+const props = defineProps({
+  resumeAnalysis: { type: Boolean, default: false }
+})
 
 // 获取附件
 const bioList = ref([])
@@ -116,6 +124,24 @@ const handleUpload = () => {
     }
   })
 }
+
+const fileUrl = ref('')
+const checkedIndex = ref()
+const radioChange = (index) => {
+  if (!props.resumeAnalysis) return
+  checkedIndex.value = index
+  if (bioList.value[index]?.url) fileUrl.value = encodeURIComponent(bioList.value[index].url)
+}
+
+const handleResumeAnalysis = () => {
+  if (!fileUrl.value) return uni.showToast({ icon: 'none', title: '请选择要解析的简历' })
+  emit('submit', fileUrl.value)
+	// uni.navigateTo({
+	// 	url: `/pagesA/resumeAnalysis/index?fileUrl=${fileUrl.value}`
+	// })
+}
+
+
 </script>
 
 <style scoped lang="scss">

+ 108 - 0
pagesA/resumeAnalysis/components/avatarEdit.vue

@@ -0,0 +1,108 @@
+<template>
+	<view class="f-straight wrapper">
+		<uni-forms ref="form" :modelValue="formData" :rules="rules" validateTrigger="bind" label-width="105px" label-align="right">
+			<uni-forms-item label="头像" name="avatar" class="f-straight" required>
+        <view style="display: flex;flex-wrap: wrap;">
+          <view class="upload-img" v-if="formData?.avatar">
+            <uni-icons size="35" type="clear" color="#fe574a" style="position: absolute;right: -15px; top: -15px; z-index: 9" @click="formData.avatar = ''"></uni-icons>
+            <image :src="formData?.avatar" mode="contain" style="width: 200rpx;height: 200rpx;" @click="handlePreviewImage"></image>
+          </view>
+          <view v-else class="upload-file" @click="uploadPhotos">
+            <uni-icons type="plusempty" size="50" color="#f1f1f1"></uni-icons>
+          </view>
+        </view>
+			</uni-forms-item>
+		</uni-forms>
+	</view>
+</template>
+
+<script setup>
+defineOptions({name: 'resumeAnalysis-avatarEdit'})
+import { pathToBase64 } from '@/utils/image-tools.js'
+import { ref, watch } from 'vue'
+const props = defineProps({
+  data: {
+    type: String,
+    default: ''
+  }
+})
+
+const form = ref()
+const formData = ref({ avatar:'' })
+
+watch(
+  () => props.data,
+  (newVal) => {
+    formData.value.avatar = newVal
+  },
+  { immediate: true },
+)
+
+// 图片预览
+const handlePreviewImage = () => {
+  uni.previewImage({
+    current: 0,
+    urls: [formData.value.avatar]
+  })
+}
+
+// 选择头像
+const uploadPhotos = () => {
+  wx.chooseImage({
+    count: 1,
+    sizeType: ['original', 'compressed'],
+    sourceType: ['album', 'camera'],
+    async success(res) {
+      console.log('res:', res)
+      const size = res.tempFiles[0]?.size || 0
+      if (size >= 31457280) {
+        uni.showToast({
+          icon: 'none',
+          title: '头像上传大小不得超过 20MB !',
+          duration: 2000
+        })
+        return
+      }
+      // 选取图片并转为base64
+      formData.value.avatar = await pathToBase64(res.tempFilePaths[0])
+    }
+  })
+}
+
+const rules = {
+	avatar:{
+		rules: [{required: true, errorMessage: '请上传头像' }]
+	}
+}
+
+// const submit = async () => {
+//   const valid = await unref(form).validate()
+//   if (!valid) return
+// }
+
+</script>
+
+<style lang="less" scoped>
+
+.wrapper{
+	padding: 15px;
+  padding-top: 30px;
+}
+.upload-img{
+  position: relative;
+  width: 200rpx;
+  height: 200rpx;
+  border: 1px solid #f1f1f1;
+  margin: 10rpx;
+}
+.upload-file{
+  width: 200rpx;
+  height: 200rpx;
+  border: 1px solid #f1f1f1;
+  margin: 10rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 10rpx;
+}
+</style>

+ 159 - 0
pagesA/resumeAnalysis/components/baseInfoEdit.vue

@@ -0,0 +1,159 @@
+<template>
+	<view class="f-straight wrapper">
+		<uni-forms ref="form" :modelValue="formData" :rules="rules" validateTrigger="bind" label-width="105px" label-align="right">
+			<uni-forms-item required label="中文名" name="name">
+        <uni-easyinput v-model="formData.name" placeholder="请输入中文名" />
+			</uni-forms-item>
+      <uni-forms-item label="性别" name="sex" required>
+				<uni-data-checkbox v-model="formData.sex" :localdata="sexData" />
+			</uni-forms-item>
+      <uni-forms-item label="联系电话" name="phone" clearable>
+        <uni-easyinput v-model="formData.phone" placeholder="请输入电话号码" />
+			</uni-forms-item>
+      <uni-forms-item label="常用邮箱" name="email" clearable required>
+        <uni-easyinput v-model="formData.email" placeholder="请输入常用邮箱" />
+			</uni-forms-item>
+      <uni-forms-item required label="出生日期" name="birthday">
+        <uni-datetime-picker type="date" return-type="timestamp" v-model="formData.birthday" />
+			</uni-forms-item>
+      <uni-forms-item label="首次工作时间" name="firstWorkTime">
+        <uni-datetime-picker type="date" return-type="timestamp" v-model="formData.firstWorkTime" />
+			</uni-forms-item>
+			<uni-forms-item required label="工作年限" name="expType" >
+				<uni-data-picker v-model="formData.expType" :localdata="dictObj.exp" :clear-icon="false" popup-title="请选择工作年限" :clear="false" :map="map"></uni-data-picker>
+			</uni-forms-item>
+      <uni-forms-item required label="最高学历" name="eduType" >
+				<uni-data-picker v-model="formData.eduType" :localdata="dictObj.edu" :clear-icon="false" popup-title="请选择最高学历" :clear="false" :map="map"></uni-data-picker>
+			</uni-forms-item>
+			<uni-forms-item required label="求职类型" name="jobType" >
+				<uni-data-picker v-model="formData.jobType" :localdata="dictObj.jobType" :clear-icon="false" popup-title="请选择求职类型" :map="map"></uni-data-picker>
+			</uni-forms-item>
+      <uni-forms-item required label="求职状态" name="jobStatus" >
+				<uni-data-picker v-model="formData.jobStatus" :localdata="dictObj.jobStatus" :clear-icon="false" popup-title="请选择求职状态" :map="map"></uni-data-picker>
+			</uni-forms-item>
+      <uni-forms-item label="婚姻状况" name="maritalStatus" >
+				<uni-data-picker v-model="formData.maritalStatus" :localdata="dictObj.marital" :clear-icon="true" popup-title="请选择婚姻状况" :map="map"></uni-data-picker>
+			</uni-forms-item>
+      <uni-forms-item label="所在城市" name="areaId" >
+				<uni-data-picker v-model="formData.areaId" :localdata="dictObj.areaTreeData" :clear-icon="true" popup-title="请选择所在城市" :map="{ text: 'name', value: 'id'}"></uni-data-picker>
+			</uni-forms-item>
+      <uni-forms-item label="户籍地" name="regId" >
+				<uni-data-picker v-model="formData.regId" :localdata="dictObj.areaTreeData" :clear-icon="true" popup-title="请选择户籍所在地" :map="{ text: 'name', value: 'id'}"></uni-data-picker>
+			</uni-forms-item>
+		</uni-forms>
+	</view>
+</template>
+
+<script setup>
+defineOptions({name: 'resumeAnalysis-baseInfoEdit'})
+import { cloneDeep } from 'lodash-es'
+import { emailRequired } from '@/utils/validate'
+import { dictObj } from '@/utils/position.js'
+import { userStore } from '@/store/user'
+// import { saveBaseInfo, updatePersonAvatar } from '@/api/user'
+// import { uploadFile } from '@/api/file'
+import { ref, watch } from 'vue'
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const form = ref()
+const sexData = ref([])
+const map = { text: 'label', value: 'value' }
+const useUserStore = userStore()
+
+const formData = ref({})
+const getInfo = (data) => {
+  data.phone = data.phone || useUserStore?.baseInfo?.phone
+  formData.value = cloneDeep(data) || {
+    name: '',
+    sex: '',
+    regId: '',
+    birthday: '1985-05-01',
+    phone: '',
+    email: '',
+    eduType: '',
+    firstWorkTime: '',
+    expType: '',
+    jobType: '',
+    jobStatus: '',
+    maritalStatus: '',
+    areaId: ''
+  }
+}
+
+watch(
+  () => props.data,
+  (newVal) => {
+    if (newVal && Object.keys(newVal)) {
+      getInfo(newVal)
+    }
+  },
+  { immediate: true },
+)
+
+if (dictObj && dictObj?.sex) {
+  sexData.value = dictObj.sex.map(e => {
+    return { text: e.label, value: e.value, ...e }
+  })
+}
+
+const rules = {
+	name:{
+		rules: [{required: true, errorMessage: '请输入姓名' }]
+	},
+	sex : {
+		rules: [{required: true, errorMessage: '请选择您的性别' }]
+	},
+	birthday: {
+		rules: [{required: true, errorMessage: '请选择您的出生日期' }]
+	},
+  email: emailRequired,
+  expType: {
+		rules: [{required: true, errorMessage: '请选择您的工作年限' }]
+	},
+  eduType: {
+		rules: [{required: true, errorMessage: '请选择您的最高学历' }]
+	},
+  jobType: {
+		rules: [{required: true, errorMessage: '请选择您的求职类型' }]
+	},
+  jobStatus: {
+		rules: [{required: true, errorMessage: '请选择您的求职状态' }]
+	}
+}
+
+// const submit = async () => {
+//   const valid = await unref(form).validate()
+//   if (!valid) return
+// }
+
+</script>
+
+<style lang="less" scoped>
+
+.wrapper{
+	padding: 15px;
+  // padding-top: 30px;
+}
+.upload-img{
+  position: relative;
+  width: 200rpx;
+  height: 200rpx;
+  border: 1px solid #f1f1f1;
+  margin: 10rpx;
+}
+.upload-file{
+  width: 200rpx;
+  height: 200rpx;
+  border: 1px solid #f1f1f1;
+  margin: 10rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 10rpx;
+}
+</style>

+ 115 - 0
pagesA/resumeAnalysis/index.vue

@@ -0,0 +1,115 @@
+<!--  -->
+<template>
+  <view>
+    <resume v-if="showResumeList" resumeAnalysis @submit="handleResumeAnalysis"></resume>
+    <view v-if="formLIst?.length">
+      <uni-card v-for="item of formLIst" :key="item.id" :id="item.id">
+		    <uni-section :title="item.text" type="line">
+          <template v-slot:right>
+            <view style="color: #e64340;" @click="null">删除</view>
+          </template>
+          <avatarEdit v-if="item.path === 'avatarEdit'" ref="componentRef" :id="item.id" :data="item.data" />
+          <baseInfoEdit v-if="item.path === 'baseInfoEdit'" ref="componentRef" :id="item.id" :data="item.data" />
+        </uni-section>
+      </uni-card>
+    </view>
+    <view v-else>
+      <view v-if="loading">加载中...</view>
+      <view v-else>加载失败</view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+defineOptions({name: 'resumeAnalysis-index'})
+// import { saveResumeInfo, resumeParser2 } from '@/api/recruit/personal/resume'
+// import { resumeParser2 } from '@/api/recruit/personal/resume'
+// import { envObj, baseUrl } from '@/utils/config'
+import resume from '../resume/index.vue'
+import { ref, shallowRef } from 'vue'
+import baseInfoEdit from './components/baseInfoEdit.vue'
+import avatarEdit from './components/avatarEdit.vue'
+import { data } from './testData.js'
+
+const showResumeList = ref(true)
+
+const handleResumeAnalysis = (url) => {
+  if (!url) {
+    return uni.showToast({ icon: 'none', title: '请选择要解析的简历' })
+  }
+  showResumeList.value = false
+  // handleAnalysis(url)
+}
+
+const exampleList = {
+  avatar: { text: '头像', id: 'avatar', path: 'avatarEdit' },
+  person: { text: '基础信息', id: 'person', path: 'baseInfoEdit' },
+  advantage: { text: '个人优势', id: 'advantage', path: 'advantage' },
+  eduList: { text: '教育经历', id: 'eduList', path: '' },
+  workList: { text: '工作经历', id: 'workList', path: '' },
+  trainList: { text: '培训经历', id: 'trainList', path: '' },
+}
+
+// const del = (item) => {
+//   Confirm('系统提示', `是否确认删除${item.text}?`).then(async () => {
+//     formLIst.value = formLIst.value.filter(e => e.id !== item.id)
+//   })
+// }
+
+const resumeTxt = ref([]) // 查看文本信息
+const formLIst = shallowRef([])
+const transformToLIst = async (result) => {
+  formLIst.value = []
+  if (result && Object.keys(result)) {
+    if (result.resume?.rawText) resumeTxt.value = result.resume.rawText.split('\n') || []
+    if (result.person?.advantage) result.advantage = result.person.advantage
+    if (result.person?.avatar) result.avatar = result.person.avatar
+    // obj
+    const dealObjKeys = ['avatar', 'person', 'advantage']
+    dealObjKeys.forEach(key => {
+      if (result[key]) {
+        const obj = {...exampleList[key]}
+        obj.data = result[key]
+        formLIst.value.push(obj)
+      }
+    })
+    // arr
+    const dealArrKeys = ['eduList', 'workList', 'trainList']
+    dealArrKeys.forEach(key => {
+      if (result[key]?.length) {
+        for (let index = 0; index < result[key].length; index++) {
+          const obj = {...exampleList[key]}
+          obj.id = obj.id + '_' + index
+          obj.text = result[key].length > 1 ? obj.text + (index+1) : obj.text
+          obj.data = result[key][index]
+          formLIst.value.push(obj)
+        }
+      }
+    })
+  }
+  console.log('formLIst:', formLIst.value)
+}
+
+const result = ref(JSON.parse(JSON.stringify(data)))
+const loading = ref(false)
+transformToLIst(result.value)
+// const handleAnalysis = async (url) => {
+//   url = decodeURIComponent(url)
+//   if (!url) return
+//   showSelect.value = false
+//   loading.value = true
+//   // const baseUrl = envObj.previewUrl
+//   // fileUrl.value = !url.includes('.pdf') ?  `${baseUrl}/onlinePreview?url=${encodeURIComponent(Base64.encode(url))}` : url
+//   try {
+//     const data = await resumeParser2({ fileUrl: url })
+//     result.value = data || {}
+//     await transformToLIst(result.value)
+//   } catch (error) {
+//     console.log(error)
+//   } finally {
+//     loading.value = false
+//   }
+// }
+</script>
+<style lang="scss" scoped>
+</style>

File diff ditekan karena terlalu besar
+ 9 - 0
pagesA/resumeAnalysis/testData.js


+ 19 - 0
pagesA/resumeOnline/index.vue

@@ -11,6 +11,10 @@
         stroke-width="8"
       />
     </view>
+    <!-- <view class="topTip">
+      使用简历附件生成在线简历:
+      <text class="resumeAnalysisBtn" @tap="handleToResumeAnalysis">立即生成</text>
+    </view> -->
     <view class="baseInfo borderLine" @tap="handleTo('baseInfoEdit')">
       <view>
         <view class="baseInfo-name">
@@ -276,6 +280,10 @@ function handleTo (str, id) {
   uni.navigateTo({ url: id ? `/pagesA/resumeOnline/${str}?id=${id}` : `/pagesA/resumeOnline/${str}` })
 }
 
+// function handleToResumeAnalysis () {
+//   uni.navigateTo({ url: '/pagesA/resumeAnalysis/index' })
+// }
+
 // 获取基础信息
 function getBaseInfo () {
   const { name, phone, ...obj } = useUserStore.baseInfo
@@ -608,4 +616,15 @@ $px: 30rpx;
     padding-bottom: 100rpx;
   }
 }
+.topTip {
+  background-color: #f7f8fa;
+  color: #2f3640;
+  padding: 12px 20px;
+  margin: 20px 20rpx;
+  font-size: 14px;
+}
+.resumeAnalysisBtn {
+  text-decoration: underline;
+  color: #008978;
+}
 </style>

+ 196 - 0
utils/image-tools.js

@@ -0,0 +1,196 @@
+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) {
+        var localFilePath = plus.io.convertAbsoluteFileSystem(path)
+        if (localFilePath !== path) {
+            return localFilePath
+        } else {
+            path = path.substr(1)
+        }
+    }
+    return '_www/' + path
+}
+
+function dataUrlToBase64(str) {
+    var array = str.split(',')
+    return array[array.length - 1]
+}
+
+var index = 0
+function getNewFileId() {
+    return Date.now() + String(index++)
+}
+
+function biggerThan(v1, v2) {
+    var v1Array = v1.split('.')
+    var v2Array = v2.split('.')
+    var update = false
+    for (var index = 0; index < v2Array.length; index++) {
+        var 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') {
+                var 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
+            }
+            var canvas = document.createElement('canvas')
+            var c2x = canvas.getContext('2d')
+            var 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) {
+                    var 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(',')
+            var type = base64[0].match(/:(.*?);/)[1]
+            var str = atob(base64[1])
+            var n = str.length
+            var array = new Uint8Array(n)
+            while (n--) {
+                array[n] = str.charCodeAt(n)
+            }
+            return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], { type: type })))
+        }
+        var extName = base64.split(',')[0].match(/data\:\S+\/(\S+);/)
+        if (extName) {
+            extName = extName[1]
+        } else {
+            reject(new Error('base64 error'))
+        }
+        var fileName = getNewFileId() + '.' + extName
+        if (typeof plus === 'object') {
+            var basePath = '_doc'
+            var dirPath = 'uniapp_temp'
+            var 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
+            }
+            var 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')) {
+            var 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'))
+    })
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini