feat: 添加签名功能及图片上传逻辑
实现签名确认功能,支持生成签名图片并上传至服务器 添加图片预览和保存功能,优化用户交互体验 重构页面布局和样式,增加时间戳显示 集成API调用处理签名数据的获取和提交
This commit is contained in:
parent
8de96d1fb0
commit
6c0af29edc
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
unpackage/
|
112
App.vue
112
App.vue
@ -1,32 +1,106 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import Vue from 'vue'
|
||||||
|
import store from './store/index.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
onLaunch: function() {
|
onLaunch: function() {
|
||||||
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
|
uni.getSystemInfo({
|
||||||
console.log('App Launch')
|
success: function(e) {
|
||||||
|
// #ifndef H5
|
||||||
|
// 获取手机系统版本
|
||||||
|
const system = e.system.toLowerCase()
|
||||||
|
const platform = e.platform.toLowerCase()
|
||||||
|
// 判断是否为ios设备
|
||||||
|
if (platform.indexOf('ios') != -1 && (system.indexOf('ios') != -1 || system.indexOf(
|
||||||
|
'macos') != -1)) {
|
||||||
|
Vue.prototype.SystemPlatform = 'apple'
|
||||||
|
} else if (platform.indexOf('android') != -1 && (system.indexOf('android') != -1)) {
|
||||||
|
Vue.prototype.SystemPlatform = 'android'
|
||||||
|
} else {
|
||||||
|
Vue.prototype.SystemPlatform = 'devtools'
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取设备的状态栏信息和自定义顶栏信息
|
||||||
|
// store.dispatch('updateCustomBarInfo')
|
||||||
|
// updateCustomBarInfo().then((res) => {
|
||||||
|
// store.commit('$tStore', {
|
||||||
|
// name: 'vuex_status_bar_height',
|
||||||
|
// value: res.statusBarHeight
|
||||||
|
// })
|
||||||
|
// store.commit('$tStore', {
|
||||||
|
// name: 'vuex_custom_bar_height',
|
||||||
|
// value: res.customBarHeight
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
//更新检测
|
||||||
|
if (wx.canIUse('getUpdateManager')) {
|
||||||
|
const updateManager = wx.getUpdateManager();
|
||||||
|
updateManager && updateManager.onCheckForUpdate((res) => {
|
||||||
|
if (res.hasUpdate) {
|
||||||
|
updateManager.onUpdateReady(() => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '更新提示',
|
||||||
|
content: '新版本已经准备就绪,是否需要重新启动应用?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.clearStorageSync() // 更新完成后刷新storage的数据
|
||||||
|
updateManager.applyUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
updateManager.onUpdateFailed(() => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '已有新版本上线',
|
||||||
|
content: '小程序自动更新失败,请删除该小程序后重新搜索打开哟~~~',
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
//没有更新
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '当前微信版本过低,无法使用该功能,请更新到最新的微信后再重试。',
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
},
|
},
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
console.log('App Show')
|
// console.log('App Show')
|
||||||
},
|
},
|
||||||
onHide: function() {
|
onHide: function() {
|
||||||
console.log('App Hide')
|
// console.log('App Hide')
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addWidthToImages(html) {
|
||||||
|
// 匹配并更新已有 style 属性的 img 标签
|
||||||
|
html = html.replace(/(<img\b[^>]*\bstyle\s*=\s*['"])([^'"]*)(['"][^>]*>)/g,
|
||||||
|
function(match, p1, p2, p3) {
|
||||||
|
return p1 + ';width: 100%;margin:0 auto;' + p3;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 匹配并添加 style 属性到没有 style 属性的 img 标签
|
||||||
|
html = html.replace(/(<img\b(?![^>]*\bstyle\s*=)[^>]*>)/g,
|
||||||
|
function(match, p1) {
|
||||||
|
return p1.replace(/\/?>$/, ' style="width: 100%;margin:0 auto;" />');
|
||||||
|
});
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
/*每个页面公共css */
|
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
|
||||||
@import '@/uni_modules/uni-scss/index.scss';
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
@import '@/static/customicons.css';
|
|
||||||
// 设置整个项目的背景色
|
|
||||||
page {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #endif */
|
|
||||||
.example-info {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
17
main.js
17
main.js
@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
// #ifndef VUE3
|
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import mixin from '@/utils/mixin.js'
|
||||||
|
Vue.mixin(mixin);
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
App.mpType = 'app'
|
App.mpType = 'app'
|
||||||
@ -11,15 +10,3 @@ const app = new Vue({
|
|||||||
...App
|
...App
|
||||||
})
|
})
|
||||||
app.$mount()
|
app.$mount()
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef VUE3
|
|
||||||
import { createSSRApp } from 'vue'
|
|
||||||
import App from './App.vue'
|
|
||||||
export function createApp() {
|
|
||||||
const app = createSSRApp(App)
|
|
||||||
return {
|
|
||||||
app
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
@ -56,5 +56,13 @@
|
|||||||
},
|
},
|
||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"vueVersion" : "2"
|
"vueVersion" : "2",
|
||||||
|
"h5" : {
|
||||||
|
"router" : {
|
||||||
|
"base" : "/h5/"
|
||||||
|
},
|
||||||
|
"devServer" : {
|
||||||
|
"https" : false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<view>
|
<view>
|
||||||
<view v-if="!showSignature">
|
<view v-if="!showSignature" style="padding-bottom: 100rpx;">
|
||||||
<view v-if="!url">
|
<view v-if="!zhen_url">
|
||||||
<view @click="openImg()">
|
<view @click="openImg()">
|
||||||
<l-painter ref="painter">
|
<l-painter ref="painter">
|
||||||
<l-painter-image src="/static/img/1.jpg" css="width: 100%; height: 100%" />
|
<l-painter-image :src="imgUrl+info.yuan_image" css="width: 100%; height: 100%" />
|
||||||
<l-painter-image :src="signatureBase64"
|
<l-painter-image :src="signatureBase64"
|
||||||
css="width: 20%;position:absolute;bottom:20rpx;left:100rpx" />
|
css="width: 18%;position:absolute;bottom:20rpx;left:110rpx" />
|
||||||
|
<l-painter-text
|
||||||
|
css="font-size:10px;width: 100%;position:absolute;bottom:24rpx;left:300rpx">{{getFormattedTime()}}</l-painter-text>
|
||||||
</l-painter>
|
</l-painter>
|
||||||
</view>
|
</view>
|
||||||
<view>
|
|
||||||
<button @click="showSignature = true"
|
|
||||||
style="margin-top: 20px;background-color:#0066FF;border: #0066FF;color: #fff;width: 80%;">签名确认</button>
|
|
||||||
<!-- <button @click="insImg()"
|
|
||||||
style="margin-top: 20px;background-color:#0066FF;border: #0066FF;color: #fff;width: 80%;">图片上传</button> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view v-if="zhen_url">
|
||||||
<view v-if="url">
|
<image @click="openImg()" :src="zhen_url" style="width: 100%;" mode="widthFix"></image>
|
||||||
<image :src="url" style="width: 100%;" mode="widthFix"></image>
|
|
||||||
<view style="text-align: center;margin-top: 10rpx;font-size: 20px;font-weight: 600;">↑↑↑长按图片保存↑↑↑</view>
|
<view style="text-align: center;margin-top: 10rpx;font-size: 20px;font-weight: 600;">↑↑↑长按图片保存↑↑↑</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="info.nlinesignature_image=='' || info.nlinesignature_image==null">
|
||||||
|
<button @click="showSignature = true"
|
||||||
|
style="margin-top: 20px;background-color:#0066FF;border: #0066FF;color: #fff;width: 80%;">{{zhen_url?'重签':'签名确认'}}</button>
|
||||||
|
<button v-if="zhen_url" @click="submit()"
|
||||||
|
style="margin-top: 20px;background-color:#00CC33;border: #00CC33;color: #fff;width: 80%;">确认提交</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="wrapper" v-if="showSignature">
|
<view class="wrapper" v-if="showSignature">
|
||||||
@ -40,31 +42,101 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {
|
||||||
|
getInfo,getUpdate
|
||||||
|
} from '@/utils/api.js';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
imgUrl: "http://qz.hschool.com.cn",
|
||||||
signatureBase64: '',
|
signatureBase64: '',
|
||||||
showSignature: false,
|
showSignature: false,
|
||||||
url: '',
|
url: '',
|
||||||
|
info: {},
|
||||||
|
token: '',
|
||||||
|
time: '',
|
||||||
|
zhen_url: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad(t) {
|
||||||
uni.setNavigationBarTitle({
|
console.log(t);
|
||||||
title: '新的标题'
|
this.token = t.token;
|
||||||
});
|
this.getContentInfo();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
submit() {
|
||||||
|
getUpdate({
|
||||||
|
token: this.token,
|
||||||
|
nlinesignature_image: this.zhen_url
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
console.log(res);
|
||||||
|
if(res.code==1){
|
||||||
|
uni.showToast({
|
||||||
|
icon:'none',
|
||||||
|
title: '提交成功!',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.getContentInfo();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
uni.showToast({
|
||||||
|
title: error,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getContentInfo() {
|
||||||
|
|
||||||
|
getInfo({
|
||||||
|
token: this.token,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.info = res.data;
|
||||||
|
this.zhen_url=res.data.nlinesignature_image;
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: res.data.name
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
uni.showToast({
|
||||||
|
title: error,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
insImg() {
|
insImg() {
|
||||||
|
|
||||||
this.$refs.painter.canvasToTempFilePathSync({
|
this.$refs.painter.canvasToTempFilePathSync({
|
||||||
// 在nvue里是jpeg
|
// 在nvue里是jpeg
|
||||||
fileType: "jpg",
|
fileType: "png",
|
||||||
quality: 1,
|
quality: 1,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
console.log(res.tempFilePath);
|
//this.url = res.tempFilePath;
|
||||||
this.url = res.tempFilePath;
|
uni.uploadFile({
|
||||||
|
url: 'http://qz.hschool.com.cn/api/common/upload', //仅为示例,非真实的接口地址
|
||||||
|
filePath: res.tempFilePath,
|
||||||
|
name: 'file',
|
||||||
|
formData: {
|
||||||
|
'token': this.token
|
||||||
},
|
},
|
||||||
complete: () => {
|
success: (uploadFileRes) => {
|
||||||
|
console.log(uploadFileRes);
|
||||||
|
var data = JSON.parse(uploadFileRes.data);
|
||||||
|
this.zhen_url = data.data.fullurl;
|
||||||
|
setTimeout(()=>{
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
fail(re) {
|
||||||
|
console.log(re);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fail: (re) => {
|
||||||
|
console.log(re);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -72,7 +144,7 @@
|
|||||||
this.$refs.signatureRef.canvasToTempFilePath({
|
this.$refs.signatureRef.canvasToTempFilePath({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
this.signatureBase64 = res.tempFilePath;
|
this.signatureBase64 = res.tempFilePath;
|
||||||
console.log(res);
|
//console.log(res);
|
||||||
this.showSignature = false;
|
this.showSignature = false;
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '签名生成中...',
|
title: '签名生成中...',
|
||||||
@ -92,11 +164,36 @@
|
|||||||
this.$refs.signatureRef.clear();
|
this.$refs.signatureRef.clear();
|
||||||
},
|
},
|
||||||
openImg() {
|
openImg() {
|
||||||
|
if (this.zhen_url != '') {
|
||||||
uni.previewImage({
|
uni.previewImage({
|
||||||
urls: ['/static/img/1.jpg'],
|
urls: [this.zhen_url],
|
||||||
current: 0
|
current: 0
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (this.info.nlinesignature_image != '') {
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [this.info.nlinesignature_image],
|
||||||
|
current: 0
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [this.imgUrl + this.info.yuan_image],
|
||||||
|
current: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
getFormattedTime() {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份补零
|
||||||
|
const day = String(now.getDate()).padStart(2, '0'); // 日期补零
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0'); // 小时补零
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0'); // 分钟补零
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
BIN
static/img/1.jpg
BIN
static/img/1.jpg
Binary file not shown.
Before Width: | Height: | Size: 205 KiB |
28
store/$tn.mixin.js
Normal file
28
store/$tn.mixin.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { mapState } from 'vuex'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
|
// 尝试将用户在根目录中的store/index.js的vuex的state变量加载到全局变量中
|
||||||
|
let $tStoreKey = []
|
||||||
|
try {
|
||||||
|
$tStoreKey = store.state ? Object.keys(store.state) : []
|
||||||
|
} catch(e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
beforeCreate() {
|
||||||
|
// 将vuex方法挂在在$t中
|
||||||
|
// 使用方法:
|
||||||
|
// 修改vuex的state中的user.name变量为图鸟小菜 => this.$tn.vuex('user.name', '图鸟小菜')
|
||||||
|
// 修改vuexde state中的version变量为1.0.1 => this.$tn.vuex('version', 1.0.1)
|
||||||
|
this.$tn.vuex = (name, value) => {
|
||||||
|
this.$store.commit('$tStore', {
|
||||||
|
name, value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 将vuex的state中的变量结构到全局混入mixin中
|
||||||
|
...mapState($tStoreKey)
|
||||||
|
}
|
||||||
|
}
|
76
store/index.js
Normal file
76
store/index.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
let lifeData = {}
|
||||||
|
|
||||||
|
// 尝试获取本地是否存在lifeData变量,第一次启动时不存在
|
||||||
|
try {
|
||||||
|
lifeData = uni.getStorageSync('lifeData')
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记需要永久存储的变量,在每次启动时取出,在state中的变量名
|
||||||
|
let saveStateKeys = ['vuex_user']
|
||||||
|
|
||||||
|
// 保存变量到本地存储
|
||||||
|
const saveLifeData = function(key, value) {
|
||||||
|
// 判断变量是否在存储数组中
|
||||||
|
if (saveStateKeys.indexOf(key) != -1) {
|
||||||
|
// 获取本地存储的lifeData对象,将变量添加到对象中
|
||||||
|
let tmpLifeData = uni.getStorageSync('lifeData')
|
||||||
|
// 第一次启动时不存在,则放一个空对象
|
||||||
|
tmpLifeData = tmpLifeData ? tmpLifeData : {},
|
||||||
|
tmpLifeData[key] = value
|
||||||
|
// 将变量再次放回本地存储中
|
||||||
|
uni.setStorageSync('lifeData', tmpLifeData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
// 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量
|
||||||
|
// 加上vuex_前缀,是防止变量名冲突,也让人一目了然
|
||||||
|
vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {
|
||||||
|
name: '灵睿'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 如果vuex_version无需保存到本地永久存储,无需lifeData.vuex_version方式
|
||||||
|
// app版本
|
||||||
|
vuex_version: "1.0.3",
|
||||||
|
// 是否使用自定义导航栏
|
||||||
|
vuex_custom_nav_bar: true,
|
||||||
|
// 状态栏高度
|
||||||
|
vuex_status_bar_height: 0,
|
||||||
|
// 自定义导航栏的高度
|
||||||
|
vuex_custom_bar_height: 0
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
$tStore(state, payload) {
|
||||||
|
// 判断是否多层调用,state中为对象存在的情况,例如user.info.score = 1
|
||||||
|
let nameArr = payload.name.split('.')
|
||||||
|
let saveKey = ''
|
||||||
|
let len = nameArr.length
|
||||||
|
if (len >= 2) {
|
||||||
|
let obj = state[nameArr[0]]
|
||||||
|
for (let i = 1; i < len - 1; i++) {
|
||||||
|
obj = obj[nameArr[i]]
|
||||||
|
}
|
||||||
|
obj[nameArr[len - 1]] = payload.value
|
||||||
|
saveKey = nameArr[0]
|
||||||
|
} else {
|
||||||
|
// 单层级变量
|
||||||
|
state[payload.name] = payload.value
|
||||||
|
saveKey = payload.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存变量到本地中
|
||||||
|
saveLifeData(saveKey, state[saveKey])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store
|
3
utils/api.js
Normal file
3
utils/api.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
export const getInfo = data => request.post('/api/signature/getTkenFind', data, false);
|
||||||
|
export const getUpdate = data => request.post('/api/signature/update', data, false);
|
59
utils/mixin.js
Normal file
59
utils/mixin.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
function v(a, b) {
|
||||||
|
return +((1000 * a - 1000 * b) / 1000).toFixed(1)
|
||||||
|
}
|
||||||
|
module.exports = {
|
||||||
|
created() {
|
||||||
|
if (this._setTransform) {
|
||||||
|
this._setTransform = (x, y, scale, source = '', r, o) => {
|
||||||
|
if (!(x !== null && x.toString() !== 'NaN' && typeof x === 'number')) {
|
||||||
|
x = this._translateX || 0
|
||||||
|
}
|
||||||
|
if (!(y !== null && y.toString() !== 'NaN' && typeof y === 'number')) {
|
||||||
|
y = this._translateY || 0
|
||||||
|
}
|
||||||
|
x = Number(x.toFixed(1))
|
||||||
|
y = Number(y.toFixed(1))
|
||||||
|
scale = Number(scale.toFixed(1))
|
||||||
|
if (!(this._translateX === x && this._translateY === y)) {
|
||||||
|
if (!r) {
|
||||||
|
this.$trigger('change', {}, {
|
||||||
|
x: v(x, this._scaleOffset.x),
|
||||||
|
y: v(y, this._scaleOffset.y),
|
||||||
|
source: source
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.scale) {
|
||||||
|
scale = this._scale
|
||||||
|
}
|
||||||
|
scale = this._adjustScale(scale)
|
||||||
|
scale = +scale.toFixed(3)
|
||||||
|
if (o && scale !== this._scale) {
|
||||||
|
this.$trigger('scale', {}, {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
scale: scale
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var transform = 'translateX(' + x + 'px) translateY(' + y + 'px) scale(' + scale + ')'
|
||||||
|
this.$el.style.transform = transform
|
||||||
|
this.$el.style.webkitTransform = transform
|
||||||
|
this._translateX = x
|
||||||
|
this._translateY = y
|
||||||
|
this._scale = scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
//解决预览模式关闭后,和重复开关预览模式this._setTransform函数无限次执行导致手机卡顿的问题
|
||||||
|
if (this._FA) {
|
||||||
|
this._FA.cancel()
|
||||||
|
}
|
||||||
|
if (this._SFA) {
|
||||||
|
this._SFA.cancel()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
57
utils/request.js
Normal file
57
utils/request.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
toast,
|
||||||
|
clearStorageSync,
|
||||||
|
getStorageSync,
|
||||||
|
useRouter
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
import RequestManager from '@/utils/requestManager.js'
|
||||||
|
|
||||||
|
let BASE_URL = 'http://qz.hschool.com.cn';
|
||||||
|
|
||||||
|
const baseRequest = async (url, method, data = {}, loading = true) => {
|
||||||
|
//const u = getStorageSync('u');
|
||||||
|
// let requestId = manager.generateId(method, url, data)
|
||||||
|
// if (!requestId) {
|
||||||
|
// console.log('重复请求')
|
||||||
|
// }
|
||||||
|
// if (!requestId) return false;
|
||||||
|
return new Promise((reslove, reject) => {
|
||||||
|
loading && uni.showLoading({
|
||||||
|
title: '加载中...'
|
||||||
|
})
|
||||||
|
uni.request({
|
||||||
|
url: BASE_URL + url,
|
||||||
|
method: method || 'GET',
|
||||||
|
header: {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
},
|
||||||
|
timeout: 10000,
|
||||||
|
data: data || {},
|
||||||
|
complete: () => {
|
||||||
|
uni.hideLoading()
|
||||||
|
},
|
||||||
|
success: (successData) => {
|
||||||
|
const res = successData.data;
|
||||||
|
if (successData.statusCode == 200) {
|
||||||
|
reslove(res)
|
||||||
|
} else {
|
||||||
|
//toast('网络连接失败,请稍后重试')
|
||||||
|
reject(res)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (msg) => {
|
||||||
|
toast('网络连接失败,请稍后重试')
|
||||||
|
reject(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = {};
|
||||||
|
|
||||||
|
['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
|
||||||
|
request[method] = (api, data, loading) => baseRequest(api, method, data, loading)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default request
|
66
utils/requestManager.js
Normal file
66
utils/requestManager.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
class RequestManager {
|
||||||
|
constructor() {
|
||||||
|
this.idMap = new Map()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 生成唯一ID,并将ID和请求信息存储到map对象中
|
||||||
|
* @param {string} method - 请求方法
|
||||||
|
* @param {string} url - 请求URL
|
||||||
|
* @param {object} params - 请求参数
|
||||||
|
* @returns {string|boolean} - 生成的唯一ID,如果存在相同请求则返回false
|
||||||
|
*/
|
||||||
|
generateId(method, url, params) {
|
||||||
|
const id = this.generateUniqueId(method, url, params)
|
||||||
|
if (this.idMap.has(id)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
this.idMap.set(id, { method, url, params })
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID删除map对象中的请求信息
|
||||||
|
* @param {string} id - 要删除的唯一ID
|
||||||
|
*/
|
||||||
|
deleteById(id) {
|
||||||
|
this.idMap.delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成唯一ID的方法
|
||||||
|
* @param {string} method - 请求方法
|
||||||
|
* @param {string} url - 请求URL
|
||||||
|
* @param {object} params - 请求参数
|
||||||
|
* @returns {string} - 生成的唯一ID
|
||||||
|
*/
|
||||||
|
generateUniqueId(method, url, params) {
|
||||||
|
const idString = `${method}-${url}-${this.serializeObject(params)}`
|
||||||
|
let id = 0;
|
||||||
|
for (let i = 0; i < idString.length; i++) {
|
||||||
|
id = ((id << 5) - id) + idString.charCodeAt(i)
|
||||||
|
id |= 0;
|
||||||
|
}
|
||||||
|
return id.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化对象为字符串
|
||||||
|
* @param {object} obj - 要序列化的对象
|
||||||
|
* @returns {string} - 序列化后的字符串
|
||||||
|
*/
|
||||||
|
serializeObject(obj) {
|
||||||
|
const keys = Object.keys(obj).sort()
|
||||||
|
const serializedObj = {}
|
||||||
|
for (let key of keys) {
|
||||||
|
const value = obj[key]
|
||||||
|
if (value !== null && typeof value === 'object') {
|
||||||
|
serializedObj[key] = this.serializeObject(value)
|
||||||
|
} else {
|
||||||
|
serializedObj[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JSON.stringify(serializedObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RequestManager
|
136
utils/utils.js
Normal file
136
utils/utils.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* 提示方法
|
||||||
|
* @param {String} title 提示文字
|
||||||
|
* @param {String} icon icon图片
|
||||||
|
* @param {Number} duration 提示时间
|
||||||
|
*/
|
||||||
|
export function toast(title, icon = 'none', duration = 1500) {
|
||||||
|
if(title) {
|
||||||
|
uni.showToast({
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
duration
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置缓存
|
||||||
|
* @param {String} key 键名
|
||||||
|
* @param {String} data 值
|
||||||
|
*/
|
||||||
|
export function setStorageSync(key, data) {
|
||||||
|
uni.setStorageSync(key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存
|
||||||
|
* @param {String} key 键名
|
||||||
|
*/
|
||||||
|
export function getStorageSync(key) {
|
||||||
|
return uni.getStorageSync(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除缓存
|
||||||
|
* @param {String} key 键名
|
||||||
|
*/
|
||||||
|
export function removeStorageSync(key) {
|
||||||
|
return uni.removeStorageSync(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空缓存
|
||||||
|
* @param {String} key 键名
|
||||||
|
*/
|
||||||
|
export function clearStorageSync() {
|
||||||
|
return uni.clearStorageSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面跳转
|
||||||
|
* @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url 转跳路径
|
||||||
|
* @param {String} params 跳转时携带的参数
|
||||||
|
* @param {String} type 转跳方式
|
||||||
|
**/
|
||||||
|
export function useRouter(url, params = {}, type = 'navigateTo') {
|
||||||
|
try {
|
||||||
|
if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}`
|
||||||
|
if (type === 'navigateBack') {
|
||||||
|
uni[type]({ delta: url })
|
||||||
|
} else {
|
||||||
|
uni[type]({ url })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览图片
|
||||||
|
* @param {Array} urls 图片链接
|
||||||
|
*/
|
||||||
|
export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) {
|
||||||
|
uni.previewImage({
|
||||||
|
urls,
|
||||||
|
longPressActions: {
|
||||||
|
itemList,
|
||||||
|
fail: function (error) {
|
||||||
|
console.error(error,'===previewImage')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存图片到本地
|
||||||
|
* @param {String} filePath 图片临时路径
|
||||||
|
**/
|
||||||
|
export function saveImage(filePath) {
|
||||||
|
if (!filePath) return false
|
||||||
|
uni.saveImageToPhotosAlbum({
|
||||||
|
filePath,
|
||||||
|
success: (res) => {
|
||||||
|
toast('图片保存成功', 'success')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '需要您授权保存相册',
|
||||||
|
showCancel: false,
|
||||||
|
success: (modalSuccess) => {
|
||||||
|
uni.openSetting({
|
||||||
|
success(settingdata) {
|
||||||
|
if (settingdata.authSetting['scope.writePhotosAlbum']) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '获取权限成功,再次点击图片即可保存',
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '获取权限失败,将无法保存到相册哦~',
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail(failData) {
|
||||||
|
console.log('failData', failData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深拷贝
|
||||||
|
* @param {Object} data
|
||||||
|
**/
|
||||||
|
export const clone = (data) => JSON.parse(JSON.stringify(data))
|
||||||
|
|
77
utils/weapp-jwt.js
Normal file
77
utils/weapp-jwt.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||||
|
var b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
|
||||||
|
exports.weBtoa = function (string) {
|
||||||
|
string = String(string);
|
||||||
|
var bitmap, a, b, c, result = "", i = 0, rest = string.length % 3;
|
||||||
|
for (; i < string.length;) {
|
||||||
|
if ((a = string.charCodeAt(i++)) > 255 ||
|
||||||
|
(b = string.charCodeAt(i++)) > 255 ||
|
||||||
|
(c = string.charCodeAt(i++)) > 255)
|
||||||
|
throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
|
||||||
|
bitmap = (a << 16) | (b << 8) | c;
|
||||||
|
result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) +
|
||||||
|
b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63);
|
||||||
|
}
|
||||||
|
return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
|
||||||
|
};
|
||||||
|
exports.weAtob = function (string) {
|
||||||
|
string = String(string).replace(/[\t\n\f\r ]+/g, "");
|
||||||
|
if (!b64re.test(string))
|
||||||
|
throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
|
||||||
|
string += "==".slice(2 - (string.length & 3));
|
||||||
|
var bitmap, result = "", r1, r2, i = 0;
|
||||||
|
for (; i < string.length;) {
|
||||||
|
bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 |
|
||||||
|
(r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));
|
||||||
|
result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) :
|
||||||
|
r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) :
|
||||||
|
String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
function b64DecodeUnicode(str) {
|
||||||
|
return decodeURIComponent(exports.weAtob(str).replace(/(.)/g, function (p) {
|
||||||
|
var code = p.charCodeAt(0).toString(16).toUpperCase();
|
||||||
|
if (code.length < 2) {
|
||||||
|
code = "0" + code;
|
||||||
|
}
|
||||||
|
return "%" + code;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
function base64_url_decode(str) {
|
||||||
|
var output = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
switch (output.length % 4) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
output += "==";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
output += "=";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw "Illegal base64url string!";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return b64DecodeUnicode(output);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return exports.weAtob(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function weappJwtDecode(token, options) {
|
||||||
|
if (typeof token !== "string") {
|
||||||
|
throw ("Invalid token specified");
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
var pos = options.header === true ? 0 : 1;
|
||||||
|
try {
|
||||||
|
return JSON.parse(base64_url_decode(token.split(".")[pos]));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw ("Invalid token specified: " + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = weappJwtDecode;
|
13
vue.config.js
Normal file
13
vue.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://qz.hschool.com.cn/',
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: {
|
||||||
|
'^/api': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user