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/
|
114
App.vue
114
App.vue
@ -1,32 +1,106 @@
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import store from './store/index.js'
|
||||
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
|
||||
console.log('App Launch')
|
||||
uni.getSystemInfo({
|
||||
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() {
|
||||
console.log('App Show')
|
||||
// console.log('App Show')
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
/*每个页面公共css */
|
||||
@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标签加入lang="scss"属性 */
|
||||
</style>
|
17
main.js
17
main.js
@ -1,8 +1,7 @@
|
||||
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
|
||||
import mixin from '@/utils/mixin.js'
|
||||
Vue.mixin(mixin);
|
||||
Vue.config.productionTip = false
|
||||
|
||||
App.mpType = 'app'
|
||||
@ -11,15 +10,3 @@ const app = new Vue({
|
||||
...App
|
||||
})
|
||||
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
|
||||
},
|
||||
"vueVersion" : "2"
|
||||
"vueVersion" : "2",
|
||||
"h5" : {
|
||||
"router" : {
|
||||
"base" : "/h5/"
|
||||
},
|
||||
"devServer" : {
|
||||
"https" : false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,27 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="!showSignature">
|
||||
<view v-if="!url">
|
||||
<view v-if="!showSignature" style="padding-bottom: 100rpx;">
|
||||
<view v-if="!zhen_url">
|
||||
<view @click="openImg()">
|
||||
<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"
|
||||
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>
|
||||
</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 v-if="url">
|
||||
<image :src="url" style="width: 100%;" mode="widthFix"></image>
|
||||
<view v-if="zhen_url">
|
||||
<image @click="openImg()" :src="zhen_url" style="width: 100%;" mode="widthFix"></image>
|
||||
<view style="text-align: center;margin-top: 10rpx;font-size: 20px;font-weight: 600;">↑↑↑长按图片保存↑↑↑</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 class="wrapper" v-if="showSignature">
|
||||
@ -40,31 +42,101 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getInfo,getUpdate
|
||||
} from '@/utils/api.js';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imgUrl: "http://qz.hschool.com.cn",
|
||||
signatureBase64: '',
|
||||
showSignature: false,
|
||||
url: '',
|
||||
info: {},
|
||||
token: '',
|
||||
time: '',
|
||||
zhen_url: '',
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
uni.setNavigationBarTitle({
|
||||
title: '新的标题'
|
||||
});
|
||||
onLoad(t) {
|
||||
console.log(t);
|
||||
this.token = t.token;
|
||||
this.getContentInfo();
|
||||
},
|
||||
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() {
|
||||
|
||||
this.$refs.painter.canvasToTempFilePathSync({
|
||||
// 在nvue里是jpeg
|
||||
fileType: "jpg",
|
||||
fileType: "png",
|
||||
quality: 1,
|
||||
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
|
||||
},
|
||||
success: (uploadFileRes) => {
|
||||
console.log(uploadFileRes);
|
||||
var data = JSON.parse(uploadFileRes.data);
|
||||
this.zhen_url = data.data.fullurl;
|
||||
setTimeout(()=>{
|
||||
uni.hideLoading();
|
||||
}, 3000);
|
||||
},
|
||||
fail(re) {
|
||||
console.log(re);
|
||||
}
|
||||
});
|
||||
},
|
||||
complete: () => {
|
||||
uni.hideLoading();
|
||||
fail: (re) => {
|
||||
console.log(re);
|
||||
},
|
||||
});
|
||||
},
|
||||
@ -72,7 +144,7 @@
|
||||
this.$refs.signatureRef.canvasToTempFilePath({
|
||||
success: (res) => {
|
||||
this.signatureBase64 = res.tempFilePath;
|
||||
console.log(res);
|
||||
//console.log(res);
|
||||
this.showSignature = false;
|
||||
uni.showLoading({
|
||||
title: '签名生成中...',
|
||||
@ -92,11 +164,36 @@
|
||||
this.$refs.signatureRef.clear();
|
||||
},
|
||||
openImg() {
|
||||
uni.previewImage({
|
||||
urls: ['/static/img/1.jpg'],
|
||||
current: 0
|
||||
});
|
||||
if (this.zhen_url != '') {
|
||||
uni.previewImage({
|
||||
urls: [this.zhen_url],
|
||||
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>
|
||||
|
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