diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99057ba --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +unpackage/ \ No newline at end of file diff --git a/App.vue b/App.vue index ee288b1..5524441 100644 --- a/App.vue +++ b/App.vue @@ -1,32 +1,106 @@ + /* 注意要写在第一行,同时给style标签加入lang="scss"属性 */ + \ No newline at end of file diff --git a/main.js b/main.js index cb25a2f..ca0a380 100644 --- a/main.js +++ b/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 \ No newline at end of file diff --git a/manifest.json b/manifest.json index cac8ba6..b1fca20 100644 --- a/manifest.json +++ b/manifest.json @@ -56,5 +56,13 @@ }, "usingComponents" : true }, - "vueVersion" : "2" + "vueVersion" : "2", + "h5" : { + "router" : { + "base" : "/h5/" + }, + "devServer" : { + "https" : false + } + } } diff --git a/pages/index/index.vue b/pages/index/index.vue index bf8e0c1..becbae7 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -1,25 +1,27 @@ diff --git a/static/img/1.jpg b/static/img/1.jpg deleted file mode 100644 index b39f46e..0000000 Binary files a/static/img/1.jpg and /dev/null differ diff --git a/store/$tn.mixin.js b/store/$tn.mixin.js new file mode 100644 index 0000000..ab7689b --- /dev/null +++ b/store/$tn.mixin.js @@ -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) + } +} \ No newline at end of file diff --git a/store/index.js b/store/index.js new file mode 100644 index 0000000..9fe6205 --- /dev/null +++ b/store/index.js @@ -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 \ No newline at end of file diff --git a/utils/api.js b/utils/api.js new file mode 100644 index 0000000..79debc0 --- /dev/null +++ b/utils/api.js @@ -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); \ No newline at end of file diff --git a/utils/mixin.js b/utils/mixin.js new file mode 100644 index 0000000..05a1b1c --- /dev/null +++ b/utils/mixin.js @@ -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: { + } +} \ No newline at end of file diff --git a/utils/request.js b/utils/request.js new file mode 100644 index 0000000..86be710 --- /dev/null +++ b/utils/request.js @@ -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 \ No newline at end of file diff --git a/utils/requestManager.js b/utils/requestManager.js new file mode 100644 index 0000000..56dff20 --- /dev/null +++ b/utils/requestManager.js @@ -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 \ No newline at end of file diff --git a/utils/utils.js b/utils/utils.js new file mode 100644 index 0000000..7ca8ccb --- /dev/null +++ b/utils/utils.js @@ -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)) + diff --git a/utils/weapp-jwt.js b/utils/weapp-jwt.js new file mode 100644 index 0000000..cea62a1 --- /dev/null +++ b/utils/weapp-jwt.js @@ -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; \ No newline at end of file diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..154b78a --- /dev/null +++ b/vue.config.js @@ -0,0 +1,13 @@ +module.exports = { + devServer: { + proxy: { + '/api': { + target: 'http://qz.hschool.com.cn/', + changeOrigin: true, + pathRewrite: { + '^/api': '' + } + } + } + } +} \ No newline at end of file