# 山科小站 > 山东科技大学校园小程序,如果觉得不错,点个star吧 :-) > GitHub:https://github.com/WindRunnerMax/SHST ## 一、微信小程序转UNIAPP > 最近转微信小程序项目到UNIAPP项目遇到的的一些注意事项和坑 > 整体来说迁移项目并不是很复杂,更多的是一些重复的工作 ### 1. 文件对应关系 ``` 对应.wxml文件 对应.js文件 对应.wxss文件 在使用HBuildX创建.vue文件时会自动创建模板 ``` ### 2. App.vue文件 ``` globalData、onPageNotFound()、onLaunch()定义于此 在 onLaunch() 中直接绑定于app的方法需要操作this.$scope ``` ### 3. 自定义组件 ``` created()方法定义为组件创建执行的方法 作为组件xml插入点,可设置name属性设置多个插入点 组件单独挂载,在.vue文件引入后组件,在export default{components: {}}声明引入组件名 全局挂载组件,在main.js引入并挂载import example from "example.vue"; Vue.component('example',example); ``` ### 4. 自定义文件夹 ``` 自定义文件夹在打包时会自动编译到/common/下 静态资源放入static中,打包时目录不会变动,wxml引入目录不变 ``` ### 5. 数据绑定 ``` 微信小程序中使用 setData({}) 方法将数据发送到视图层 Vue中数据双向绑定,使用 this.param = value 重新渲染数据, 当然也可以重写 setData({}) 方法,官网给予示例 setData:function(obj){ let that = this; let keys = []; let val,data; Object.keys(obj).forEach(function(key){ keys = key.split('.'); val = obj[key]; data = that.$data; keys.forEach(function(key2,index){ if(index+1 == keys.length){ that.$set(data,key2,val); }else{ if(!data[key2]){ that.$set(data,key2,{}); } } data = data[key2]; }) }); } ``` ### 6. template数据渲染 ``` 要使用的数据必须首先在export default {data() {return { param : value}}}中声明 在xml节点属性中使用:attr引用变量值,在xml节点的值使用{{param}} 循环wx:for="{{list}}" wx:key="{{index}}"> 改为 v-for="(item,index) in list" :key="index" ``` ### 7. 动态绑定class与style ``` 在ES6中可直接在:attr中使用新特性`string${param}string`来拼接字符串,但是微信小程序还不支持 手动拼接字符串的方式:attr="'string' + param" Vue中提供了动态绑定的方式 ``` ### 8. page.json ``` 在微信小程序的app.json路由在Unaipp中同样由page.json文件统一管理 微信小程序时在app.json中写入路由则创建文件,HbuildX中在创建页面时可选自动在page.json创建路由 在page.json中style对应微信小程序某一页面的json文件 ``` ### 9. 条件编译功能 ``` // #ifdef %PLATFORM% 平台特有的API实现,UNIAPP提供的条件编译功能,非常适用于跨端开发 // #endif ``` ### 10. 阿里矢量图标库Iconfont ``` 将图标添加到项目,以代码的方式下载到本地 复制iconfont.css到项目,移除所有类似 url('iconfont.eot?t=1577846073653#iefix') format('embedded-opentype') 部分 在引用即可 ``` ## 二、山科小站实例 ### 1. 目录结构 ``` SHST-UNI // 山科小站总目录 ├── components // 组件封装 │ ├── headslot.vue // 带solt的标题布局 │ ├── layout.vue // 卡片式布局 │ ├── list.vue // 展示用list布局 │ ├── sentence.vue // 每日一句封装 │ └── weather.vue // 天气封装 ├── modules // 模块化封装 │ ├── cookies.js // Cookies操作 │ ├── copy.js // 深浅拷贝 │ ├── datetime.js // 时间日期操作 │ ├── event-bus.js // 事件总线 │ ├── global-data.js // 全局变量 │ ├── loading.js // 加载提示 │ ├── operate-limit.js // 防抖与节流 │ ├── regex.js // 正则匹配 │ ├── request.js // 网络请求 │ ├── toast.js // 消息提示 │ └── update.js // 自动更新 ├── pages // 页面 │ ├── Ext // 拓展组 │ ├── Home // Tabbar、辅助组 │ ├── Lib // 图书馆功能组 │ ├── Sdust // 科大组 │ ├── Study // 学习组 │ └── User // 用户组 ├── static // 静态资源 │ ├── camptour // 校园导览静态资源 │ └── img // 图标等静态资源 ├── unpackage // 打包文件 ├── utils // 辅助功能 │ ├── amap-wx.js // 高德地图SDK │ └── md5.js // MD5引入 ├── vector // 部署封装 │ ├── resources // 资源文件 │ │ ├── camptour // 校园导览配置文件 │ │ ├── asse.mini.wxss // 公共样式库 │ │ └── iconfont.wxss // 字体图标 │ ├── dispose.js // 部署小程序 │ └── pubFct.js // 公有方法 ├── App.vue // App全局样式以及监听 ├── main.js // 挂载App,Vue初始化入口文件 ├── manifest.json // 配置Uniapp打包等信息 ├── pages.json // 路由 └── uni.scss // 内置的常用样式变量 ``` ## 三、模块化 ### 1. Cookies操作 ```javascript /** * GetCookie */ function getCookies(res) { var cookies = ""; if (res && res.header && res.header['Set-Cookie']) { // #ifdef MP-ALIPAY var cookies = res.header['Set-Cookie'][0].split(";")[0] + ";"; // #endif // #ifndef MP-ALIPAY var cookies = res.header['Set-Cookie'].split(";")[0] + ";"; // #endif console.log("SetCookie:" + cookies); uni.setStorage({ key: "cookies", data: cookies }); } else { console.log("Get Cookie From Cache"); cookies = uni.getStorageSync("cookies") || ""; } return cookies; } export { getCookies } export default { getCookies } ``` ### 2. 深浅拷贝 ```javascript function shallowCopy(target, ...origin) { return Object.assign(target, ...origin); } function extend(target, ...origin) { return shallowCopy(target, ...origin); } function deepCopy(target, origin) { for (let item in origin) { if (origin[item] && typeof(origin[item]) === "object") { // Object Array Date RegExp 深拷贝 if (Object.prototype.toString.call(origin[item]) === "[object Object]") { target[item] = deepCopy({}, origin[item]); } else if (origin[item] instanceof Array) { target[item] = deepCopy([], origin[item]); } else if (origin[item] instanceof Date) { target[item] = new Date(origin[item]); } else if (origin[item] instanceof RegExp) { target[item] = new RegExp(origin[item].source, origin[item].flags); } else { target[item] = origin[item]; } } else { target[item] = origin[item]; } } return target; } export { extend, shallowCopy, deepCopy } export default { extend, shallowCopy, deepCopy } ``` ### 3. 时间日期操作 ```javascript /** * yyyy年 MM月 dd日 hh1~12小时制(1-12) HH24小时制(0-23) mm分 ss秒 S毫秒 K周 */ const formatDate = (fmt = "yyyy-MM-dd", date = new Date()) => { var week = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"]; var o = { "M+": date.getMonth() + 1, //月份 "d+": date.getDate(), //日 "h+": date.getHours(), //小时 "m+": date.getMinutes(), //分 "s+": date.getSeconds(), //秒 "q+": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds(), //毫秒 "K": week[date.getDay()] }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) { if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (( "00" + o[k]).substr(("" + o[k]).length))); } return fmt; } const extDate = () => { // console.log("拓展Date原型"); Date.prototype.addDate = function(years = 0, months = 0, days = 0) { if (days !== 0) this.setDate(this.getDate() + days); if (months !== 0) this.setMonth(this.getMonth() + months); if (years !== 0) this.setFullYear(this.getFullYear() + years); } } /** * 日期相差天数 */ const dateDiff = (startDateString, endDateString) => { var separator = "-"; //日期分隔符 var startDates = startDateString.split(separator); var endDates = endDateString.split(separator); var startDate = new Date(startDates[0], startDates[1] - 1, startDates[2]); var endDate = new Date(endDates[0], endDates[1] - 1, endDates[2]); var diff = parseInt((endDate - startDate) / 1000 / 60 / 60 / 24); //把相差的毫秒数转换为天数 return diff; } export { formatDate, extDate, dateDiff } export default { formatDate, extDate, dateDiff } ``` ### 4. 事件总线 ```javascript var PubSub = function() { this.handlers = {}; } PubSub.prototype = { on: function(key, handler) { // 订阅 if (!(key in this.handlers)) this.handlers[key] = []; this.handlers[key].push(handler); }, off: function(key, handler) { // 卸载 const index = this.handlers[key].findIndex(item => item === handler); if (index < 0) return false; if (this.handlers[key].length === 1) delete this.handlers[key]; else this.handlers[key].splice(index, 1); return true; }, commit: function(key, ...args) { // 触发 if (!this.handlers[key]) return false; this.handlers[key].forEach(handler => handler.apply(this, args)); return true; }, } export { PubSub } export default { PubSub } ``` ### 5. 全局变量 ```javascript /** * 颜色方案 */ // var colorList = ["#EAA78C", "#F9CD82", "#9ADEAD", "#9CB6E9", "#E49D9B", "#97D7D7", "#ABA0CA", "#9F8BEC", // "#ACA4D5", "#6495ED", "#7BCDA5", "#76B4EF","#E1C38F","#F6C46A","#B19ED1","#F09B98","#87CECB","#D1A495","#89D196" // ]; var colorList = ["#FE9E9F", "#93BAFF", "#D999F9", "#81C784", "#FFCA62", "#FFA477"]; export { colorList } export default { colorList } ``` ### 6. 加载提示 ```javascript /** * startLoading */ function startLoading(option) { switch (option.load) { case 1: uni.showNavigationBarLoading(); break; case 2: uni.showNavigationBarLoading(); uni.setNavigationBarTitle({ title: option.title || "加载中..." }) break; case 3: uni.showLoading({ title: option.title || "请求中", mask: true }) break; } } /** * endLoading */ function endLoading(option) { switch (option.load) { case 1: uni.hideNavigationBarLoading(); break; case 2: uni.hideNavigationBarLoading(); uni.setNavigationBarTitle({ title: option.title || "山科小站" }) break; case 3: uni.hideLoading(); break; } } export { startLoading, endLoading } export default { startLoading, endLoading } ``` ### 7. 防抖与节流 ```javascript /** * 防抖 * 定时器实现 */ function debounceGenerater(){ var timer = null; return (wait, funct, ...args) => { clearTimeout(timer); timer = setTimeout(() => funct(...args), wait); } } /** * 节流 * 时间戳实现 */ function throttleGenerater(){ var previous = 0; return (wait, funct, ...args) => { var now = +new Date(); if(now - previous > wait){ funct(...args); previous = now; } } } /* // 节流 // 定时器实现 function throttleGenerater(){ var timer = null; return (wait, funct, ...args) => { if(!timer){ funct(...args); timer = setTimeout(() => timer = null, wait); } } } */ export { debounceGenerater, throttleGenerater } export default { debounceGenerater, throttleGenerater } ``` ### 8. 正则匹配 ```javascript /** * 正则匹配 */ const regMatch = (regex, s) => { var result = []; var temp = null; var flags = `${regex.flags}${regex.flags.includes("g") ? "" : "g"}`; regex = new RegExp(regex, flags); while (temp = regex.exec(s)) result.push(temp[1] ? temp[1] : temp[0]); return result; } export { regMatch } export default { regMatch } ``` ### 9. 网络请求 ```javascript import {startLoading, endLoading} from "./loading"; import {getCookies} from "./cookies"; import {extend} from "./copy"; import {toast} from "./toast"; var headers = {'content-type': 'application/x-www-form-urlencoded'}; /** * HTTP请求 */ function ajax(requestInfo) { var option = { load: 1, url: "", method: "GET", data: {}, headers: headers, success: () => {}, resolve: () => {}, fail: function() { this.completeLoad = () => { toast("External Error");}}, reject: () => {}, complete: () => {}, completeLoad: () => {} }; extend(option, requestInfo); startLoading(option); uni.request({ url: option.url, data: option.data, method: option.method, header: headers, success: function(res) { if (!headers.cookie) headers.cookie = getCookies(res); if(res.statusCode === 200){ try { option.success(res); option.resolve(res); } catch (e) { option.completeLoad = () => { toast("External Error");} console.log(e); } }else{ option.fail(res); option.reject(res); } }, fail: function(res) { option.fail(res); }, complete: function(res) { endLoading(option); try { option.complete(res); } catch (e) { console.log(e); } option.completeLoad(res); } }) } /** * request promise封装 */ function request(option) { return new Promise((resolve,reject) => { option.resolve = resolve; option.reject = reject; ajax(option); }) } export { ajax, request } export default { ajax, request } ``` ### 10. 消息提示 ```javascript /** * 弹窗提示 */ function toast(e, time = 2000, icon = 'none') { uni.showToast({ title: e, icon: icon, duration: time }) } export { toast } export default { toast } ``` ### 11. 自动更新 ```javascript /** * 小程序更新 */ function checkUpdate() { if (!uni.getUpdateManager) return false; uni.getUpdateManager().onCheckForUpdate((res) => { console.log("Update:" + res.hasUpdate); if (res.hasUpdate) { //如果有新版本 uni.getUpdateManager().onUpdateReady(() => { //当新版本下载完成 uni.showModal({ title: '更新提示', content: '新版本已经准备好,单击确定重启应用', success: (res) => { if (res.confirm) uni.getUpdateManager().applyUpdate(); //applyUpdate 应用新版本并重启 } }) }) uni.getUpdateManager().onUpdateFailed(() => { //当新版本下载失败 uni.showModal({ title: '提示', content: '检查到有新版本,但下载失败,请检查网络设置', showCancel: false }) }) } }) } export { checkUpdate } export default { checkUpdate } ``` ### 12. 启动事件 ```javascript "use strict"; import globalData from "@/modules/global-data"; import request from "@/modules/request"; import {toast} from "@/modules/toast"; import {extend} from "@/modules/copy"; import {PubSub} from "@/modules/event-bus"; import {extDate} from "@/modules/datetime"; import {checkUpdate} from "@/modules/update"; import {getCurWeek} from "@/vector/pubFct"; function disposeApp(app){ extDate(); //拓展Date原型 checkUpdate(); // 检查更新 app.$scope.toast = toast; app.$scope.extend = extend; app.$scope.eventBus = new PubSub(); app.$scope.extend(app.$scope, request); app.$scope.extend(app.globalData, globalData); app.globalData.colorN = app.globalData.colorList.length; app.globalData.curWeek = getCurWeek(app.globalData.curTermStart); } /** * APP启动事件 */ function onLaunch() { var app = this; disposeApp(this); var userInfo = uni.getStorageSync("user") || {}; uni.login({ scopes: 'auth_base' }).then((data) => { var [err,res] = data; if(err) return Promise.reject(err); return app.$scope.request({ load: 3, // #ifdef MP-WEIXIN url: app.globalData.url + 'auth/wx', // #endif // #ifdef MP-QQ url: app.globalData.url + 'auth/QQ', // #endif method: 'POST', data: { "code": res.code, user: JSON.stringify(userInfo) } }) }).then((res) => { app.globalData.curTerm = res.data.initData.curTerm; app.globalData.curTermStart = res.data.initData.termStart; app.globalData.curWeek = res.data.initData.curWeek; app.globalData.loginStatus = res.data.Message; app.globalData.initData = res.data.initData; if(app.globalData.initData.custom){ let custom = app.globalData.initData.custom; if(custom.color_list) { app.globalData.colorList = JSON.parse(custom.color_list); app.globalData.colorN = app.globalData.colorList.length; } } if (res.data.Message === "Ex") app.globalData.userFlag = 1; else app.globalData.userFlag = 0; console.log("Status:" + (app.globalData.userFlag === 1 ? "User Login" : "New User")); if (res.data.openid) { var notify = res.data.initData.tips; app.globalData.tips = notify; var point = uni.getStorageSync("point") || ""; if (point !== notify) uni.showTabBarRedDot({ index: 2 }); console.log("SetOpenid:" + res.data.openid); app.globalData.openid = res.data.openid; uni.setStorageSync('openid', res.data.openid); } else { console.log("Get Openid From Cache"); app.globalData.openid = uni.getStorageSync("openid") || ""; } return Promise.resolve(res); }).then((res) => { if (res.statusCode !== 200 || !res.data.initData || !res.data.initData.curTerm) return Promise.reject("DATA INIT FAIL"); else app.$scope.eventBus.commit('LoginEvent', res); }).catch((err) => { console.log(err); uni.showModal({ title: '警告', content: '数据初始化失败,点击确定重新初始化数据', showCancel: false, success: (res) => { if (res.confirm) onLaunch.apply(app); } }) }) } export default {onLaunch, toast} ``` ## 四、组件化 ### 1. 标题组件 ```html ``` ### 2. 卡片组件 ```html ``` ### 3. 列表组件 ```html S ``` ### 4. 每日一句组件 ```html ``` ### 5. 天气组件 ```html ``` ## 五、小程序部署 ### 1. 初始化小程序 ```javascript import dispose from "@/vector/dispose"; export default { globalData: { tips: "0", openid: "", userFlag: 0, // 0 未登录 1 已登陆 initData: {}, version: "3.3.0", curTerm: "2019-2020-1", curTermStart: "2019-08-26", url: 'https://www.touchczy.top/', // url: 'http://dev.touchczy.top/', }, onPageNotFound: (res) => { //处理404 uni.reLaunch({ url: 'pages/Home/auxiliary/notFound' }) }, onLaunch: function() { console.log("APP INIT"); dispose.onLaunch.apply(this); //启动加载事件 }, onError: (err) => { console.log(err); dispose.toast("Internal Error"); } } ``` ### 2. 全局样式 ```css /*全局样式*/ @import "@/vector/resources/asse.mini.wxss"; @import "@/vector/resources/iconfont.wxss"; button:after { border: none; } button { background: #fff; border: none; box-sizing: unset; padding: 0; margin: 0; font-size: 13px; line-height: unset; height: auto; } .adapt{ box-sizing: border-box; } .tipsCon view{ padding: 5px; } ```