commit 4190565eb78e9ae473927bd445057d80e0f55fc6 Author: xuchao Date: Tue Jul 21 00:43:59 2026 +0800 feat: 质惠游 H5 一期 —— 门票模块全链路(微信公众号版) 从 xghmall(云闪付版湘工游)迁移民票模块,改造为微信公众号 H5: - 技术栈沿用 uni-app + Vue2 + uView(含定制版日历组件) - 登录/支付独立成层(common/auth.js、common/pay.js): 一期假登录(测试手机号)+ 模拟支付,后端微信接口就绪后只换这两处 - 登录页重写:短信验证码 + 公众号网页授权(code 换用户信息)预留 - 门票列表页为入口页(进页必须登录),加订单悬浮按钮 - 支付成功页接通:查看订单/继续逛逛 + 订单状态轮询 - 详情页定位导航改用 uni.openLocation + 高德网页兜底 - 出行人页:身份证 18 位格式校验,删除死的删除按钮,修正证件类型回显笔误 - 清理云闪付 SDK、支付宝授权、死页面引用等残留 Co-Authored-By: Claude Opus 4.8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a503fa2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/unpackage/ diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..aab2510 --- /dev/null +++ b/App.vue @@ -0,0 +1,18 @@ + + + diff --git a/common/auth.js b/common/auth.js new file mode 100644 index 0000000..5739a1e --- /dev/null +++ b/common/auth.js @@ -0,0 +1,61 @@ +// xuchao: 登录层 —— 登录逻辑全部隔离在这里 +// 一期为假登录(开发模式),后端微信登录接口就绪后只改本文件: +// - loginByMock 开发模式:输入测试手机号直接登录 +// - loginByWechat 正式流程:用微信网页授权凭证(code)换用户信息 +// - bindPhoneBySms 正式流程:短信验证码绑定手机号(微信授权拿不到手机号时用) +import config from '@/common/config.js' + +// 是否已登录(本地缓存里有手机号即视为已登录) +const isLogin = () => { + return !!uni.getStorageSync('phone') +} + +// xuchao: 登录校验 —— 未登录则跳登录页,登录成功后回跳 redirect 指定的页面 +const requireLogin = (redirect) => { + if (isLogin()) return true + uni.navigateTo({ + url: '/pages/index/authorization' + (redirect ? '?redirect=' + encodeURIComponent(redirect) : '') + }) + return false +} + +// xuchao: 开发模式假登录 —— 写入与真实登录相同的缓存数据,后续页面逻辑完全一致 +const loginByMock = (phone) => { + uni.setStorageSync('phone', phone) + uni.setStorageSync('openId', 'mock_openid_' + phone) + uni.setStorageSync('userId', '') +} + +// xuchao: 正式流程占位 —— 后端接口就绪后实现(用 code 换 openId + 用户信息) +const loginByWechat = (code) => { + return new Promise((resolve, reject) => { + // TODO 后端就绪后替换为:POST /auth/oauth2session {code} → {openId, mobile, token} + reject(new Error('微信登录接口未接入')) + }) +} + +// xuchao: 正式流程占位 —— 短信验证码绑定手机号 +const bindPhoneBySms = (phone, smsCode) => { + return new Promise((resolve, reject) => { + // TODO 后端就绪后替换为:短信验证码校验 + 手机号绑定 openId + reject(new Error('短信绑定接口未接入')) + }) +} + +// 退出登录 +const logout = () => { + uni.removeStorageSync('phone') + uni.removeStorageSync('openId') + uni.removeStorageSync('userId') + uni.removeStorageSync('token') +} + +export default { + isLogin, + requireLogin, + loginByMock, + loginByWechat, + bindPhoneBySms, + logout, + devMode: config.devMode +} diff --git a/common/config.js b/common/config.js new file mode 100644 index 0000000..ac76076 --- /dev/null +++ b/common/config.js @@ -0,0 +1,13 @@ +// xuchao: 全局配置 —— 环境地址、开发模式开关 +export default { + // 后端测试环境地址 + apiBaseUrl: 'https://dscp-uat.hn12301.net', + // 开发模式:true = 假登录 + 假支付(本地调试用);后端微信接口就绪后改为 false 接真实流程 + devMode: true, + // 产品名 + appName: '质惠游', + // 测试手机号(测试环境已有数据的账号) + testPhones: ['17763797832', '18684839400', '18710146266', '15367920401'], + // 微信公众号网页授权 appid(公众号信息确定后填入) + wxAppid: '' +} diff --git a/common/http.api.js b/common/http.api.js new file mode 100644 index 0000000..ff975fa --- /dev/null +++ b/common/http.api.js @@ -0,0 +1,54 @@ +// xuchao: 接口定义 —— 一期仅门票模块 + 登录/支付预留 +let queryTicketsPageUrl = 'open-api/tickets/queryTicketsPage' +let getTicketsTypesUrl = 'open-api/tickets/getTicketsTypes' +let getTicketsCitiesUrl = 'open-api/tickets/getTicketsCities' +let getTicketsDetailUrl = 'open-api/tickets/getTicketsDetail' +let getSkuDetailUrl = 'open-api/tickets/getSkuDetail' +let addTicketsMemberTouristUrl = 'open-api/tickets/addTicketsMemberTourist' +let getMemberTouristUrl = 'open-api/tickets/getMemberTourist' +let updateTicketsMemberTouristUrl = 'open-api/tickets/updateTicketsMemberTourist' +let createTicketsOrderUrl = 'open-api/tickets/order/createTicketsOrder' +let getTicketsOrderDetailUrl = 'open-api/tickets/order/getTicketsOrderDetail' +let queryTicketsOrderPageUrl = 'open-api/tickets/order/queryTicketsOrderPage' +let cancelTicketsOrderUrl = 'open-api/tickets/order/cancelTicketsOrder' +let refundTicketsOrderXghUrl = 'open-api/tickets/order/refundTicketsOrderXgh' +// xuchao: 微信预下单接口路径(后端接口就绪后确认,返回微信支付所需参数) +let prepayWxUrl = 'open-api/tickets/order/prepayWx' + +const install = (Vue, vm) => { + let queryTicketsPage = (params = {}) => vm.$u.post(`${queryTicketsPageUrl}`, params); + let getTicketsTypes = (params = {}) => vm.$u.post(`${getTicketsTypesUrl}`, params); + let getTicketsCities = (params = {}) => vm.$u.post(`${getTicketsCitiesUrl}`, params); + let getTicketsDetail = (params = {}) => vm.$u.post(`${getTicketsDetailUrl}`, params); + let getSkuDetail = (params = {}) => vm.$u.post(`${getSkuDetailUrl}`, params); + let addTicketsMemberTourist = (params = {}) => vm.$u.post(`${addTicketsMemberTouristUrl}`, params); + let getMemberTourist = (params = {}) => vm.$u.post(`${getMemberTouristUrl}?mobile=${params.mobile}`, {}); + let updateTicketsMemberTourist = (params = {}) => vm.$u.post(`${updateTicketsMemberTouristUrl}`, params); + let createTicketsOrder = (params = {}) => vm.$u.post(`${createTicketsOrderUrl}`, params); + let getTicketsOrderDetail = (params = {}) => vm.$u.post(`${getTicketsOrderDetailUrl}`, params); + let queryTicketsOrderPage = (params = {}) => vm.$u.post(`${queryTicketsOrderPageUrl}`, params); + let cancelTicketsOrder = (params = {}) => vm.$u.post(`${cancelTicketsOrderUrl}`, params); + let refundTicketsOrderXgh = (params = {}) => vm.$u.post(`${refundTicketsOrderXghUrl}`, params); + let prepayWx = (params = {}) => vm.$u.post(`${prepayWxUrl}`, params); + + Vue.prototype.$u.api = { + queryTicketsPage, + getTicketsTypes, + getTicketsCities, + getTicketsDetail, + getSkuDetail, + addTicketsMemberTourist, + getMemberTourist, + updateTicketsMemberTourist, + createTicketsOrder, + getTicketsOrderDetail, + queryTicketsOrderPage, + cancelTicketsOrder, + refundTicketsOrderXgh, + prepayWx + } +} + +export default { + install +} diff --git a/common/http.interceptor.js b/common/http.interceptor.js new file mode 100644 index 0000000..f343090 --- /dev/null +++ b/common/http.interceptor.js @@ -0,0 +1,34 @@ +// xuchao: 请求/响应拦截器 —— baseUrl 统一走 config.js +import config from '@/common/config.js' + +const install = (Vue, vm) => { + Vue.prototype.$u.http.setConfig({ + baseUrl: config.apiBaseUrl + }); + // 请求拦截:带上登录凭证 + Vue.prototype.$u.http.interceptor.request = (config) => { + config.header.Authorization = uni.getStorageSync('token'); + return config; + } + // 响应拦截:errcode==0 才算成功;status==600 视为登录失效,跳登录页 + Vue.prototype.$u.http.interceptor.response = (res) => { + if (res.errcode == 0) { + return res; + } else if (res.status == 600) { + uni.removeStorageSync('phone') + uni.reLaunch({ + url: '/pages/index/authorization' + }) + } else { + uni.showToast({ + icon: 'none', + title: res.errmsg, + duration: 5000 + }) + }; + } +} + +export default { + install +} diff --git a/common/pay.js b/common/pay.js new file mode 100644 index 0000000..55cad7f --- /dev/null +++ b/common/pay.js @@ -0,0 +1,62 @@ +// xuchao: 支付层 —— 支付逻辑全部隔离在这里 +// 一期为模拟支付(开发模式),后端微信预下单接口就绪后只改本文件: +// - devMode=true 模拟支付成功(订单在测试环境真实生成为"待支付"状态) +// - devMode=false 调后端微信预下单接口,再用微信内置桥接唤起微信支付 +import config from '@/common/config.js' + +// xuchao: 判断是否在微信浏览器内(正式支付只能在微信里唤起) +const isWechatBrowser = () => { + // #ifdef H5 + return /micromessenger/i.test(navigator.userAgent) + // #endif +} + +// xuchao: 发起支付 +// vm: 页面的 this(用于调接口) +// orderId: 订单号(createTicketsOrder 返回值) +// onSuccess / onFail: 支付结果回调 +const pay = (vm, { orderId, onSuccess, onFail }) => { + if (config.devMode) { + // 开发模式:模拟支付成功 + uni.showLoading({ title: '支付中...' }) + setTimeout(() => { + uni.hideLoading() + onSuccess && onSuccess({ mock: true }) + }, 800) + return + } + + // 正式流程:微信 JSAPI 支付 + if (!isWechatBrowser()) { + uni.showToast({ icon: 'none', title: '请在微信中打开完成支付' }) + onFail && onFail({ errMsg: 'not wechat browser' }) + return + } + const openId = uni.getStorageSync('openId') + vm.$u.api.prepayWx({ orderId, openId }).then(res => { + if (!res) return + const p = res.data || {} + // 后端返回微信支付所需参数后,用此桥接唤起支付(微信浏览器内置,无需额外引入) + WeixinJSBridge.invoke('getBrandWCPayRequest', { + appId: p.appId, + timeStamp: String(p.timeStamp), + nonceStr: p.nonceStr, + package: p.pack || p.packageValue, + signType: p.signType || 'RSA', + paySign: p.paySign + }, bridgeRes => { + if (bridgeRes.err_msg === 'get_brand_wcpay_request:ok') { + onSuccess && onSuccess(bridgeRes) + } else { + onFail && onFail(bridgeRes) + } + }) + }).catch(() => { + onFail && onFail({ errMsg: 'prepay error' }) + }) +} + +export default { + pay, + isWechatBrowser +} diff --git a/components/gmy-float-touch/gmy-float-touch.vue b/components/gmy-float-touch/gmy-float-touch.vue new file mode 100644 index 0000000..2ff174d --- /dev/null +++ b/components/gmy-float-touch/gmy-float-touch.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/components/gmy-float-touch/readme.md b/components/gmy-float-touch/readme.md new file mode 100644 index 0000000..fd378a2 --- /dev/null +++ b/components/gmy-float-touch/readme.md @@ -0,0 +1,14 @@ +# 仿苹果悬浮按钮 +## 只需再页面中调用插件,在监听menuClick事件即可,该事件返回五个小按钮的索引从上往下索引一次是1,2,3,4,5。具体事件由用户根据索引自定义 +## 如果需要使用图标请在使用时传入,数据说明 +* mainImg:主图标,不可隐藏,图片单独设置。 +* imgLists:弹出菜单图标,共5个菜单,故需5个图标,以数组形式设置 + +### 使用 + * import gmyFloatTouch from "../../../components/gmy-float-touch/gmy-float-touch.vue" //引用 + * //页面使用 + * floatTouchClick:function(e){ //事件 + console.log(e); + }, + * mainImg:"../../static/main.png", //数据格式 + imgLists:["../../static/home.png","../../static/approve.png","../../static/match.png","../../static/send.png","../../static/return.png"], \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..aa7a146 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + 质惠游 + + + + +
+ + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..4488caf --- /dev/null +++ b/main.js @@ -0,0 +1,19 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +import './uni.promisify.adaptor' +Vue.config.productionTip = false +import uView from "uview-ui" +Vue.use(uView); +App.mpType = 'app' +const app = new Vue({ + ...App +}) +import httpInterceptor from '@/common/http.interceptor.js' +// 这里需要写在最后,是为了等Vue创建对象完成,引入"app"对象(也即页面的"this"实例) +Vue.use(httpInterceptor, app) +import httpApi from '@/common/http.api.js'; +Vue.use(httpApi, app) +app.$mount() +// #endif diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..e63f617 --- /dev/null +++ b/manifest.json @@ -0,0 +1,21 @@ +{ + "name" : "质惠游", + "appid" : "", + "description" : "质惠游 H5(微信公众号网页)", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "2", + "h5" : { + "router" : { + "base" : "/zhy/" + }, + "template" : "index.html", + "devServer" : { + "https" : false + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..666c203 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "zhy-h5", + "version": "1.0.0", + "description": "质惠游 H5(微信公众号网页)—— 门票模块一期", + "main": "main.js" +} diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..0baf8a7 --- /dev/null +++ b/pages.json @@ -0,0 +1,74 @@ +{ + "easycom": { + + "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" + + }, + "pages": [ + // xuchao: 启动页为门票列表页(公众号菜单直链入口,进页需登录) + { + "path": "pages/ticket/scenic", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/ticket/scenicdetail", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/ticket/ordersure", + "style": { + "navigationStyle": "custom", + "app-plus": { + "softinputMode": "adjustResize" + } + } + }, + { + "path": "pages/ticket/person", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/ticket/select", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/ticket/orderdetail", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/ticket/orderlist", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/ticket/paysuccess", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/index/authorization", + "style": { + "navigationBarTitleText": "质惠游", + "navigationBarBackgroundColor": "#ffffff" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "质惠游", + "navigationBarBackgroundColor": "#ffffff", + "backgroundColor": "#ffffff" + } +} diff --git a/pages/index/authorization.vue b/pages/index/authorization.vue new file mode 100644 index 0000000..cf62f34 --- /dev/null +++ b/pages/index/authorization.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/pages/ticket/orderdetail.vue b/pages/ticket/orderdetail.vue new file mode 100644 index 0000000..669ef8d --- /dev/null +++ b/pages/ticket/orderdetail.vue @@ -0,0 +1,509 @@ + + + + + \ No newline at end of file diff --git a/pages/ticket/orderlist.vue b/pages/ticket/orderlist.vue new file mode 100644 index 0000000..645b744 --- /dev/null +++ b/pages/ticket/orderlist.vue @@ -0,0 +1,148 @@ + + + + + + \ No newline at end of file diff --git a/pages/ticket/ordersure.vue b/pages/ticket/ordersure.vue new file mode 100644 index 0000000..9a4d7df --- /dev/null +++ b/pages/ticket/ordersure.vue @@ -0,0 +1,726 @@ + + + + + \ No newline at end of file diff --git a/pages/ticket/paysuccess.vue b/pages/ticket/paysuccess.vue new file mode 100644 index 0000000..05cde25 --- /dev/null +++ b/pages/ticket/paysuccess.vue @@ -0,0 +1,163 @@ + + + + + + diff --git a/pages/ticket/person.vue b/pages/ticket/person.vue new file mode 100644 index 0000000..05aea0d --- /dev/null +++ b/pages/ticket/person.vue @@ -0,0 +1,215 @@ + + + + + \ No newline at end of file diff --git a/pages/ticket/scenic.vue b/pages/ticket/scenic.vue new file mode 100644 index 0000000..2c1819f --- /dev/null +++ b/pages/ticket/scenic.vue @@ -0,0 +1,390 @@ + + + + + + \ No newline at end of file diff --git a/pages/ticket/scenicdetail.vue b/pages/ticket/scenicdetail.vue new file mode 100644 index 0000000..e49cf94 --- /dev/null +++ b/pages/ticket/scenicdetail.vue @@ -0,0 +1,399 @@ + + + + + \ No newline at end of file diff --git a/pages/ticket/select.vue b/pages/ticket/select.vue new file mode 100644 index 0000000..409888b --- /dev/null +++ b/pages/ticket/select.vue @@ -0,0 +1,119 @@ + + + + + \ No newline at end of file diff --git a/static/YouShe.ttf b/static/YouShe.ttf new file mode 100644 index 0000000..3729151 Binary files /dev/null and b/static/YouShe.ttf differ diff --git a/static/card.png b/static/card.png new file mode 100644 index 0000000..7230d92 Binary files /dev/null and b/static/card.png differ diff --git a/static/card_s.png b/static/card_s.png new file mode 100644 index 0000000..6cd6eb0 Binary files /dev/null and b/static/card_s.png differ diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/static/logo.png differ diff --git a/static/logo1.png b/static/logo1.png new file mode 100644 index 0000000..ba84aa6 Binary files /dev/null and b/static/logo1.png differ diff --git a/static/logo2.png b/static/logo2.png new file mode 100644 index 0000000..db2e55f Binary files /dev/null and b/static/logo2.png differ diff --git a/static/logo3.png b/static/logo3.png new file mode 100644 index 0000000..2fa0f93 Binary files /dev/null and b/static/logo3.png differ diff --git a/static/logo4.png b/static/logo4.png new file mode 100644 index 0000000..5221003 Binary files /dev/null and b/static/logo4.png differ diff --git a/static/logo5.png b/static/logo5.png new file mode 100644 index 0000000..21da61c Binary files /dev/null and b/static/logo5.png differ diff --git a/static/main.png b/static/main.png new file mode 100644 index 0000000..9bf34ff Binary files /dev/null and b/static/main.png differ diff --git a/static/main_s.png b/static/main_s.png new file mode 100644 index 0000000..f3724db Binary files /dev/null and b/static/main_s.png differ diff --git a/static/my.png b/static/my.png new file mode 100644 index 0000000..fb27424 Binary files /dev/null and b/static/my.png differ diff --git a/static/my_s.png b/static/my_s.png new file mode 100644 index 0000000..488cca8 Binary files /dev/null and b/static/my_s.png differ diff --git a/uni.promisify.adaptor.js b/uni.promisify.adaptor.js new file mode 100644 index 0000000..47fbce1 --- /dev/null +++ b/uni.promisify.adaptor.js @@ -0,0 +1,10 @@ +uni.addInterceptor({ + returnValue (res) { + if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) { + return res; + } + return new Promise((resolve, reject) => { + res.then((res) => res[0] ? reject(res[0]) : resolve(res[1])); + }); + }, +}); \ No newline at end of file diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..e439bfb --- /dev/null +++ b/uni.scss @@ -0,0 +1,77 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16px; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; +@import "uview-ui/theme.scss"; \ No newline at end of file diff --git a/unit/font/index.css b/unit/font/index.css new file mode 100644 index 0000000..a3655df --- /dev/null +++ b/unit/font/index.css @@ -0,0 +1,5 @@ + +@font-face { + font-family: 'YouShe'; + src: url('./static/YouShe.ttf') format('truetype'); +} diff --git a/unit/utils.js b/unit/utils.js new file mode 100644 index 0000000..7cf5498 --- /dev/null +++ b/unit/utils.js @@ -0,0 +1,82 @@ +// xuchao: 票种类型映射(门票详情、订单详情用) +const skuType = [{ + value: '不限人群', + label: '1' +}, { + value: '成人票', + label: '2' +}, { + value: '双人票', + label: '3' +}, { + value: '亲子票(1大1小)', + label: '4' +}, { + value: '家庭票(2大2小)', + label: '5' +}, { + value: '学生票', + label: '6' +}, { + value: '儿童票', + label: '7' +}, { + value: '老人票', + label: '8' +}, { + value: '单人票', + label: '9' +}, { + value: '三人票', + label: '10' +}, { + value: '家庭票(2大1小)', + label: '11' +}, { + value: '青年票', + label: '12' +}, { + value: '军人票', + label: '13' +}, { + value: '残疾票', + label: '14' +}, { + value: '教师人票', + label: '15' +}, { + value: '大学生票', + label: '16' +}, { + value: '团队票', + label: '17' +}, { + value: '男士票', + label: '18' +}, { + value: '女士票', + label: '19' +}, { + value: '中学生票', + label: '20' +}, { + value: '中/高考考生特惠票', + label: '21' +}, { + value: '情侣票', + label: '22' +}, { + value: '婴幼儿票', + label: '23' +}, { + value: '非中国大陆籍游客专享票', + label: '24' +}] + +// xuchao: 票种编号转名称 +export const getSkuType = (e) => { + let arr = skuType.filter(item => { + return item.label == e + }) + return arr.length ? arr[0].value : '' +} diff --git a/uview-ui/LICENSE b/uview-ui/LICENSE new file mode 100644 index 0000000..8e39ead --- /dev/null +++ b/uview-ui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 www.uviewui.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/uview-ui/README.md b/uview-ui/README.md new file mode 100644 index 0000000..06d5676 --- /dev/null +++ b/uview-ui/README.md @@ -0,0 +1,106 @@ +

+ logo +

+

uView

+

多平台快速开发的UI框架

+ + +## 说明 + +uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水 + +## 特性 + +- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序 +- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用 +- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨 +- 众多的常用页面和布局,让您专注逻辑,事半功倍 +- 详尽的文档支持,现代化的演示效果 +- 按需引入,精简打包体积 + + +## 安装 + +```bash +# npm方式安装 +npm i uview-ui +``` + +## 快速上手 + +1. `main.js`引入uView库 +```js +// main.js +import uView from 'uview-ui'; +Vue.use(uView); +``` + +2. `App.vue`引入基础样式(注意style标签需声明scss属性支持) +```css +/* App.vue */ + +``` + +3. `uni.scss`引入全局scss变量文件 +```css +/* uni.scss */ +@import "uview-ui/theme.scss"; +``` + +4. `pages.json`配置easycom规则(按需引入) + +```js +// pages.json +{ + "easycom": { + // npm安装的方式不需要前面的"@/",下载安装的方式需要"@/" + // npm安装方式 + "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" + // 下载安装方式 + // "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" + }, + // 此为本身已有的内容 + "pages": [ + // ...... + ] +} +``` + +请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 + +## 使用方法 +配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 + +```html + +``` + +请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 + +## 链接 + +- [官方文档](https://uviewui.com/) +- [更新日志](https://uviewui.com/components/changelog.html) +- [升级指南](https://uviewui.com/components/changelog.html) +- [关于我们](https://uviewui.com/cooperation/about.html) + +## 预览 + +您可以通过**微信**扫码,查看最佳的演示效果。 +
+
+ + + +## 版权信息 +uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。 diff --git a/uview-ui/components/u-action-sheet/u-action-sheet.vue b/uview-ui/components/u-action-sheet/u-action-sheet.vue new file mode 100644 index 0000000..722b668 --- /dev/null +++ b/uview-ui/components/u-action-sheet/u-action-sheet.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/uview-ui/components/u-alert-tips/u-alert-tips.vue b/uview-ui/components/u-alert-tips/u-alert-tips.vue new file mode 100644 index 0000000..e81fc37 --- /dev/null +++ b/uview-ui/components/u-alert-tips/u-alert-tips.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue b/uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue new file mode 100644 index 0000000..a48dd54 --- /dev/null +++ b/uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/uview-ui/components/u-avatar-cropper/weCropper.js b/uview-ui/components/u-avatar-cropper/weCropper.js new file mode 100644 index 0000000..df02483 --- /dev/null +++ b/uview-ui/components/u-avatar-cropper/weCropper.js @@ -0,0 +1,1265 @@ +/** + * we-cropper v1.3.9 + * (c) 2020 dlhandsome + * @license MIT + */ +(function(global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.WeCropper = factory()); +}(this, (function() { + 'use strict'; + + var device = void 0; + var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended']; + + function firstLetterUpper(str) { + return str.charAt(0).toUpperCase() + str.slice(1) + } + + function setTouchState(instance) { + var arg = [], + len = arguments.length - 1; + while (len-- > 0) arg[len] = arguments[len + 1]; + + TOUCH_STATE.forEach(function(key, i) { + if (arg[i] !== undefined) { + instance[key] = arg[i]; + } + }); + } + + function validator(instance, o) { + Object.defineProperties(instance, o); + } + + function getDevice() { + if (!device) { + device = uni.getSystemInfoSync(); + } + return device + } + + var tmp = {}; + + var ref = getDevice(); + var pixelRatio = ref.pixelRatio; + + var DEFAULT = { + id: { + default: 'cropper', + get: function get() { + return tmp.id + }, + set: function set(value) { + if (typeof(value) !== 'string') { + console.error(("id:" + value + " is invalid")); + } + tmp.id = value; + } + }, + width: { + default: 750, + get: function get() { + return tmp.width + }, + set: function set(value) { + if (typeof(value) !== 'number') { + console.error(("width:" + value + " is invalid")); + } + tmp.width = value; + } + }, + height: { + default: 750, + get: function get() { + return tmp.height + }, + set: function set(value) { + if (typeof(value) !== 'number') { + console.error(("height:" + value + " is invalid")); + } + tmp.height = value; + } + }, + pixelRatio: { + default: pixelRatio, + get: function get() { + return tmp.pixelRatio + }, + set: function set(value) { + if (typeof(value) !== 'number') { + console.error(("pixelRatio:" + value + " is invalid")); + } + tmp.pixelRatio = value; + } + }, + scale: { + default: 2.5, + get: function get() { + return tmp.scale + }, + set: function set(value) { + if (typeof(value) !== 'number') { + console.error(("scale:" + value + " is invalid")); + } + tmp.scale = value; + } + }, + zoom: { + default: 5, + get: function get() { + return tmp.zoom + }, + set: function set(value) { + if (typeof(value) !== 'number') { + console.error(("zoom:" + value + " is invalid")); + } else if (value < 0 || value > 10) { + console.error("zoom should be ranged in 0 ~ 10"); + } + tmp.zoom = value; + } + }, + src: { + default: '', + get: function get() { + return tmp.src + }, + set: function set(value) { + if (typeof(value) !== 'string') { + console.error(("src:" + value + " is invalid")); + } + tmp.src = value; + } + }, + cut: { + default: {}, + get: function get() { + return tmp.cut + }, + set: function set(value) { + if (typeof(value) !== 'object') { + console.error(("cut:" + value + " is invalid")); + } + tmp.cut = value; + } + }, + boundStyle: { + default: {}, + get: function get() { + return tmp.boundStyle + }, + set: function set(value) { + if (typeof(value) !== 'object') { + console.error(("boundStyle:" + value + " is invalid")); + } + tmp.boundStyle = value; + } + }, + onReady: { + default: null, + get: function get() { + return tmp.ready + }, + set: function set(value) { + tmp.ready = value; + } + }, + onBeforeImageLoad: { + default: null, + get: function get() { + return tmp.beforeImageLoad + }, + set: function set(value) { + tmp.beforeImageLoad = value; + } + }, + onImageLoad: { + default: null, + get: function get() { + return tmp.imageLoad + }, + set: function set(value) { + tmp.imageLoad = value; + } + }, + onBeforeDraw: { + default: null, + get: function get() { + return tmp.beforeDraw + }, + set: function set(value) { + tmp.beforeDraw = value; + } + } + }; + + var ref$1 = getDevice(); + var windowWidth = ref$1.windowWidth; + + function prepare() { + var self = this; + + // v1.4.0 版本中将不再自动绑定we-cropper实例 + self.attachPage = function() { + var pages = getCurrentPages(); + // 获取到当前page上下文 + var pageContext = pages[pages.length - 1]; + // 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问 + Object.defineProperty(pageContext, 'wecropper', { + get: function get() { + console.warn( + 'Instance will not be automatically bound to the page after v1.4.0\n\n' + + 'Please use a custom instance name instead\n\n' + + 'Example: \n' + + 'this.mycropper = new WeCropper(options)\n\n' + + '// ...\n' + + 'this.mycropper.getCropperImage()' + ); + return self + }, + configurable: true + }); + }; + + self.createCtx = function() { + var id = self.id; + var targetId = self.targetId; + + if (id) { + self.ctx = self.ctx || uni.createCanvasContext(id); + self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId); + } else { + console.error("constructor: create canvas context failed, 'id' must be valuable"); + } + }; + + self.deviceRadio = windowWidth / 750; + } + + var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== + 'undefined' ? self : {}; + + + + + + function createCommonjsModule(fn, module) { + return module = { + exports: {} + }, fn(module, module.exports), module.exports; + } + + var tools = createCommonjsModule(function(module, exports) { + /** + * String type check + */ + exports.isStr = function(v) { + return typeof v === 'string'; + }; + /** + * Number type check + */ + exports.isNum = function(v) { + return typeof v === 'number'; + }; + /** + * Array type check + */ + exports.isArr = Array.isArray; + /** + * undefined type check + */ + exports.isUndef = function(v) { + return v === undefined; + }; + + exports.isTrue = function(v) { + return v === true; + }; + + exports.isFalse = function(v) { + return v === false; + }; + /** + * Function type check + */ + exports.isFunc = function(v) { + return typeof v === 'function'; + }; + /** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ + exports.isObj = exports.isObject = function(obj) { + return obj !== null && typeof obj === 'object' + }; + + /** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ + var _toString = Object.prototype.toString; + exports.isPlainObject = function(obj) { + return _toString.call(obj) === '[object Object]' + }; + + /** + * Check whether the object has the property. + */ + var hasOwnProperty = Object.prototype.hasOwnProperty; + exports.hasOwn = function(obj, key) { + return hasOwnProperty.call(obj, key) + }; + + /** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) + */ + exports.noop = function(a, b, c) {}; + + /** + * Check if val is a valid array index. + */ + exports.isValidArrayIndex = function(val) { + var n = parseFloat(String(val)); + return n >= 0 && Math.floor(n) === n && isFinite(val) + }; + }); + + var tools_7 = tools.isFunc; + var tools_10 = tools.isPlainObject; + + var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad']; + + function observer() { + var self = this; + + self.on = function(event, fn) { + if (EVENT_TYPE.indexOf(event) > -1) { + if (tools_7(fn)) { + event === 'ready' ? + fn(self) : + self[("on" + (firstLetterUpper(event)))] = fn; + } + } else { + console.error(("event: " + event + " is invalid")); + } + return self + }; + } + + function wxPromise(fn) { + return function(obj) { + var args = [], + len = arguments.length - 1; + while (len-- > 0) args[len] = arguments[len + 1]; + + if (obj === void 0) obj = {}; + return new Promise(function(resolve, reject) { + obj.success = function(res) { + resolve(res); + }; + obj.fail = function(err) { + reject(err); + }; + fn.apply(void 0, [obj].concat(args)); + }) + } + } + + function draw(ctx, reserve) { + if (reserve === void 0) reserve = false; + + return new Promise(function(resolve) { + ctx.draw(reserve, resolve); + }) + } + + var getImageInfo = wxPromise(uni.getImageInfo); + + var canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath); + + var base64 = createCommonjsModule(function(module, exports) { + /*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */ + (function(root) { + + // Detect free variables `exports`. + var freeExports = 'object' == 'object' && exports; + + // Detect free variable `module`. + var freeModule = 'object' == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, and use + // it as `root`. + var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var InvalidCharacterError = function(message) { + this.message = message; + }; + InvalidCharacterError.prototype = new Error; + InvalidCharacterError.prototype.name = 'InvalidCharacterError'; + + var error = function(message) { + // Note: the error messages used throughout this file match those used by + // the native `atob`/`btoa` implementation in Chromium. + throw new InvalidCharacterError(message); + }; + + var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // http://whatwg.org/html/common-microsyntaxes.html#space-character + var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g; + + // `decode` is designed to be fully compatible with `atob` as described in the + // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob + // The optimized base64-decoding algorithm used is based on @atk’s excellent + // implementation. https://gist.github.com/atk/1020396 + var decode = function(input) { + input = String(input) + .replace(REGEX_SPACE_CHARACTERS, ''); + var length = input.length; + if (length % 4 == 0) { + input = input.replace(/==?$/, ''); + length = input.length; + } + if ( + length % 4 == 1 || + // http://whatwg.org/C#alphanumeric-ascii-characters + /[^+a-zA-Z0-9/]/.test(input) + ) { + error( + 'Invalid character: the string to be decoded is not correctly encoded.' + ); + } + var bitCounter = 0; + var bitStorage; + var buffer; + var output = ''; + var position = -1; + while (++position < length) { + buffer = TABLE.indexOf(input.charAt(position)); + bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer; + // Unless this is the first of a group of 4 characters… + if (bitCounter++ % 4) { + // …convert the first 8 bits to a single ASCII character. + output += String.fromCharCode( + 0xFF & bitStorage >> (-2 * bitCounter & 6) + ); + } + } + return output; + }; + + // `encode` is designed to be fully compatible with `btoa` as described in the + // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa + var encode = function(input) { + input = String(input); + if (/[^\0-\xFF]/.test(input)) { + // Note: no need to special-case astral symbols here, as surrogates are + // matched, and the input is supposed to only contain ASCII anyway. + error( + 'The string to be encoded contains characters outside of the ' + + 'Latin1 range.' + ); + } + var padding = input.length % 3; + var output = ''; + var position = -1; + var a; + var b; + var c; + var buffer; + // Make sure any padding is handled outside of the loop. + var length = input.length - padding; + + while (++position < length) { + // Read three bytes, i.e. 24 bits. + a = input.charCodeAt(position) << 16; + b = input.charCodeAt(++position) << 8; + c = input.charCodeAt(++position); + buffer = a + b + c; + // Turn the 24 bits into four chunks of 6 bits each, and append the + // matching character for each of them to the output. + output += ( + TABLE.charAt(buffer >> 18 & 0x3F) + + TABLE.charAt(buffer >> 12 & 0x3F) + + TABLE.charAt(buffer >> 6 & 0x3F) + + TABLE.charAt(buffer & 0x3F) + ); + } + + if (padding == 2) { + a = input.charCodeAt(position) << 8; + b = input.charCodeAt(++position); + buffer = a + b; + output += ( + TABLE.charAt(buffer >> 10) + + TABLE.charAt((buffer >> 4) & 0x3F) + + TABLE.charAt((buffer << 2) & 0x3F) + + '=' + ); + } else if (padding == 1) { + buffer = input.charCodeAt(position); + output += ( + TABLE.charAt(buffer >> 2) + + TABLE.charAt((buffer << 4) & 0x3F) + + '==' + ); + } + + return output; + }; + + var base64 = { + 'encode': encode, + 'decode': decode, + 'version': '0.1.0' + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof undefined == 'function' && + typeof undefined.amd == 'object' && + undefined.amd + ) { + undefined(function() { + return base64; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = base64; + } else { // in Narwhal or RingoJS v0.7.0- + for (var key in base64) { + base64.hasOwnProperty(key) && (freeExports[key] = base64[key]); + } + } + } else { // in Rhino or a web browser + root.base64 = base64; + } + + }(commonjsGlobal)); + }); + + function makeURI(strData, type) { + return 'data:' + type + ';base64,' + strData + } + + function fixType(type) { + type = type.toLowerCase().replace(/jpg/i, 'jpeg'); + var r = type.match(/png|jpeg|bmp|gif/)[0]; + return 'image/' + r + } + + function encodeData(data) { + var str = ''; + if (typeof data === 'string') { + str = data; + } else { + for (var i = 0; i < data.length; i++) { + str += String.fromCharCode(data[i]); + } + } + return base64.encode(str) + } + + /** + * 获取图像区域隐含的像素数据 + * @param canvasId canvas标识 + * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标 + * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标 + * @param width 将要被提取的图像数据矩形区域的宽度 + * @param height 将要被提取的图像数据矩形区域的高度 + * @param done 完成回调 + */ + function getImageData(canvasId, x, y, width, height, done) { + uni.canvasGetImageData({ + canvasId: canvasId, + x: x, + y: y, + width: width, + height: height, + success: function success(res) { + done(res, null); + }, + fail: function fail(res) { + done(null, res); + } + }); + } + + /** + * 生成bmp格式图片 + * 按照规则生成图片响应头和响应体 + * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData + * @returns {*} base64字符串 + */ + function genBitmapImage(oData) { + // + // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx + // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx + // + var biWidth = oData.width; + var biHeight = oData.height; + var biSizeImage = biWidth * biHeight * 3; + var bfSize = biSizeImage + 54; // total header size = 54 bytes + + // + // typedef struct tagBITMAPFILEHEADER { + // WORD bfType; + // DWORD bfSize; + // WORD bfReserved1; + // WORD bfReserved2; + // DWORD bfOffBits; + // } BITMAPFILEHEADER; + // + var BITMAPFILEHEADER = [ + // WORD bfType -- The file type signature; must be "BM" + 0x42, 0x4D, + // DWORD bfSize -- The size, in bytes, of the bitmap file + bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff, + // WORD bfReserved1 -- Reserved; must be zero + 0, 0, + // WORD bfReserved2 -- Reserved; must be zero + 0, 0, + // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits. + 54, 0, 0, 0 + ]; + + // + // typedef struct tagBITMAPINFOHEADER { + // DWORD biSize; + // LONG biWidth; + // LONG biHeight; + // WORD biPlanes; + // WORD biBitCount; + // DWORD biCompression; + // DWORD biSizeImage; + // LONG biXPelsPerMeter; + // LONG biYPelsPerMeter; + // DWORD biClrUsed; + // DWORD biClrImportant; + // } BITMAPINFOHEADER, *PBITMAPINFOHEADER; + // + var BITMAPINFOHEADER = [ + // DWORD biSize -- The number of bytes required by the structure + 40, 0, 0, 0, + // LONG biWidth -- The width of the bitmap, in pixels + biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff, + // LONG biHeight -- The height of the bitmap, in pixels + biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff, + // WORD biPlanes -- The number of planes for the target device. This value must be set to 1 + 1, 0, + // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap + // has a maximum of 2^24 colors (16777216, Truecolor) + 24, 0, + // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed + 0, 0, 0, 0, + // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps + biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff, + // LONG biXPelsPerMeter, unused + 0, 0, 0, 0, + // LONG biYPelsPerMeter, unused + 0, 0, 0, 0, + // DWORD biClrUsed, the number of color indexes of palette, unused + 0, 0, 0, 0, + // DWORD biClrImportant, unused + 0, 0, 0, 0 + ]; + + var iPadding = (4 - ((biWidth * 3) % 4)) % 4; + + var aImgData = oData.data; + + var strPixelData = ''; + var biWidth4 = biWidth << 2; + var y = biHeight; + var fromCharCode = String.fromCharCode; + + do { + var iOffsetY = biWidth4 * (y - 1); + var strPixelRow = ''; + for (var x = 0; x < biWidth; x++) { + var iOffsetX = x << 2; + strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) + + fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) + + fromCharCode(aImgData[iOffsetY + iOffsetX]); + } + + for (var c = 0; c < iPadding; c++) { + strPixelRow += String.fromCharCode(0); + } + + strPixelData += strPixelRow; + } while (--y) + + var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData); + + return strEncoded + } + + /** + * 转换为图片base64 + * @param canvasId canvas标识 + * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标 + * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标 + * @param width 将要被提取的图像数据矩形区域的宽度 + * @param height 将要被提取的图像数据矩形区域的高度 + * @param type 转换图片类型 + * @param done 完成回调 + */ + function convertToImage(canvasId, x, y, width, height, type, done) { + if (done === void 0) done = function() {}; + + if (type === undefined) { + type = 'png'; + } + type = fixType(type); + if (/bmp/.test(type)) { + getImageData(canvasId, x, y, width, height, function(data, err) { + var strData = genBitmapImage(data); + tools_7(done) && done(makeURI(strData, 'image/' + type), err); + }); + } else { + console.error('暂不支持生成\'' + type + '\'类型的base64图片'); + } + } + + var CanvasToBase64 = { + convertToImage: convertToImage, + // convertToPNG: function (width, height, done) { + // return convertToImage(width, height, 'png', done) + // }, + // convertToJPEG: function (width, height, done) { + // return convertToImage(width, height, 'jpeg', done) + // }, + // convertToGIF: function (width, height, done) { + // return convertToImage(width, height, 'gif', done) + // }, + convertToBMP: function(ref, done) { + if (ref === void 0) ref = {}; + var canvasId = ref.canvasId; + var x = ref.x; + var y = ref.y; + var width = ref.width; + var height = ref.height; + if (done === void 0) done = function() {}; + + return convertToImage(canvasId, x, y, width, height, 'bmp', done) + } + }; + + function methods() { + var self = this; + + var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度 + var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度 + + var id = self.id; + var targetId = self.targetId; + var pixelRatio = self.pixelRatio; + + var ref = self.cut; + var x = ref.x; + if (x === void 0) x = 0; + var y = ref.y; + if (y === void 0) y = 0; + var width = ref.width; + if (width === void 0) width = boundWidth; + var height = ref.height; + if (height === void 0) height = boundHeight; + + self.updateCanvas = function(done) { + if (self.croperTarget) { + // 画布绘制图片 + self.ctx.drawImage( + self.croperTarget, + self.imgLeft, + self.imgTop, + self.scaleWidth, + self.scaleHeight + ); + } + tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self); + + self.setBoundStyle(self.boundStyle); // 设置边界样式 + + self.ctx.draw(false, done); + return self + }; + + self.pushOrigin = self.pushOrign = function(src) { + self.src = src; + + tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self); + + return getImageInfo({ + src: src + }) + .then(function(res) { + var innerAspectRadio = res.width / res.height; + var customAspectRadio = width / height; + + self.croperTarget = res.path; + + if (innerAspectRadio < customAspectRadio) { + self.rectX = x; + self.baseWidth = width; + self.baseHeight = width / innerAspectRadio; + self.rectY = y - Math.abs((height - self.baseHeight) / 2); + } else { + self.rectY = y; + self.baseWidth = height * innerAspectRadio; + self.baseHeight = height; + self.rectX = x - Math.abs((width - self.baseWidth) / 2); + } + + self.imgLeft = self.rectX; + self.imgTop = self.rectY; + self.scaleWidth = self.baseWidth; + self.scaleHeight = self.baseHeight; + + self.update(); + + return new Promise(function(resolve) { + self.updateCanvas(resolve); + }) + }) + .then(function() { + tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self); + }) + }; + + self.removeImage = function() { + self.src = ''; + self.croperTarget = ''; + return draw(self.ctx) + }; + + self.getCropperBase64 = function(done) { + if (done === void 0) done = function() {}; + + CanvasToBase64.convertToBMP({ + canvasId: id, + x: x, + y: y, + width: width, + height: height + }, done); + }; + + self.getCropperImage = function(opt, fn) { + var customOptions = opt; + + var canvasOptions = { + canvasId: id, + x: x, + y: y, + width: width, + height: height + }; + + var task = function() { + return Promise.resolve(); + }; + + if ( + tools_10(customOptions) && + customOptions.original + ) { + // original mode + task = function() { + self.targetCtx.drawImage( + self.croperTarget, + self.imgLeft * pixelRatio, + self.imgTop * pixelRatio, + self.scaleWidth * pixelRatio, + self.scaleHeight * pixelRatio + ); + + canvasOptions = { + canvasId: targetId, + x: x * pixelRatio, + y: y * pixelRatio, + width: width * pixelRatio, + height: height * pixelRatio + }; + + return draw(self.targetCtx) + }; + } + + return task() + .then(function() { + if (tools_10(customOptions)) { + canvasOptions = Object.assign({}, canvasOptions, customOptions); + } + + if (tools_7(customOptions)) { + fn = customOptions; + } + + var arg = canvasOptions.componentContext ? + [canvasOptions, canvasOptions.componentContext] : + [canvasOptions]; + + return canvasToTempFilePath.apply(null, arg) + }) + .then(function(res) { + var tempFilePath = res.tempFilePath; + + return tools_7(fn) ? + fn.call(self, tempFilePath, null) : + tempFilePath + }) + .catch(function(err) { + if (tools_7(fn)) { + fn.call(self, null, err); + } else { + throw err + } + }) + }; + } + + /** + * 获取最新缩放值 + * @param oldScale 上一次触摸结束后的缩放值 + * @param oldDistance 上一次触摸结束后的双指距离 + * @param zoom 缩放系数 + * @param touch0 第一指touch对象 + * @param touch1 第二指touch对象 + * @returns {*} + */ + var getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1) { + var xMove, yMove, newDistance; + // 计算二指最新距离 + xMove = Math.round(touch1.x - touch0.x); + yMove = Math.round(touch1.y - touch0.y); + newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove)); + + return oldScale + 0.001 * zoom * (newDistance - oldDistance) + }; + + function update() { + var self = this; + + if (!self.src) { + return + } + + self.__oneTouchStart = function(touch) { + self.touchX0 = Math.round(touch.x); + self.touchY0 = Math.round(touch.y); + }; + + self.__oneTouchMove = function(touch) { + var xMove, yMove; + // 计算单指移动的距离 + if (self.touchended) { + return self.updateCanvas() + } + xMove = Math.round(touch.x - self.touchX0); + yMove = Math.round(touch.y - self.touchY0); + + var imgLeft = Math.round(self.rectX + xMove); + var imgTop = Math.round(self.rectY + yMove); + + self.outsideBound(imgLeft, imgTop); + + self.updateCanvas(); + }; + + self.__twoTouchStart = function(touch0, touch1) { + var xMove, yMove, oldDistance; + + self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2); + self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2); + + // 计算两指距离 + xMove = Math.round(touch1.x - touch0.x); + yMove = Math.round(touch1.y - touch0.y); + oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove)); + + self.oldDistance = oldDistance; + }; + + self.__twoTouchMove = function(touch0, touch1) { + var oldScale = self.oldScale; + var oldDistance = self.oldDistance; + var scale = self.scale; + var zoom = self.zoom; + + self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1); + + // 设定缩放范围 + self.newScale <= 1 && (self.newScale = 1); + self.newScale >= scale && (self.newScale = scale); + + self.scaleWidth = Math.round(self.newScale * self.baseWidth); + self.scaleHeight = Math.round(self.newScale * self.baseHeight); + var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2); + var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2); + + self.outsideBound(imgLeft, imgTop); + + self.updateCanvas(); + }; + + self.__xtouchEnd = function() { + self.oldScale = self.newScale; + self.rectX = self.imgLeft; + self.rectY = self.imgTop; + }; + } + + var handle = { + // 图片手势初始监测 + touchStart: function touchStart(e) { + var self = this; + var ref = e.touches; + var touch0 = ref[0]; + var touch1 = ref[1]; + + if (!self.src) { + return + } + + setTouchState(self, true, null, null); + + // 计算第一个触摸点的位置,并参照改点进行缩放 + self.__oneTouchStart(touch0); + + // 两指手势触发 + if (e.touches.length >= 2) { + self.__twoTouchStart(touch0, touch1); + } + }, + + // 图片手势动态缩放 + touchMove: function touchMove(e) { + var self = this; + var ref = e.touches; + var touch0 = ref[0]; + var touch1 = ref[1]; + + if (!self.src) { + return + } + + setTouchState(self, null, true); + + // 单指手势时触发 + if (e.touches.length === 1) { + self.__oneTouchMove(touch0); + } + // 两指手势触发 + if (e.touches.length >= 2) { + self.__twoTouchMove(touch0, touch1); + } + }, + + touchEnd: function touchEnd(e) { + var self = this; + + if (!self.src) { + return + } + + setTouchState(self, false, false, true); + self.__xtouchEnd(); + } + }; + + function cut() { + var self = this; + var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度 + var boundHeight = self.height; + // 裁剪框默认高度,即整个画布高度 + var ref = self.cut; + var x = ref.x; + if (x === void 0) x = 0; + var y = ref.y; + if (y === void 0) y = 0; + var width = ref.width; + if (width === void 0) width = boundWidth; + var height = ref.height; + if (height === void 0) height = boundHeight; + + /** + * 设置边界 + * @param imgLeft 图片左上角横坐标值 + * @param imgTop 图片左上角纵坐标值 + */ + self.outsideBound = function(imgLeft, imgTop) { + self.imgLeft = imgLeft >= x ? + x : + self.scaleWidth + imgLeft - x <= width ? + x + width - self.scaleWidth : + imgLeft; + + self.imgTop = imgTop >= y ? + y : + self.scaleHeight + imgTop - y <= height ? + y + height - self.scaleHeight : + imgTop; + }; + + /** + * 设置边界样式 + * @param color 边界颜色 + */ + self.setBoundStyle = function(ref) { + if (ref === void 0) ref = {}; + var color = ref.color; + if (color === void 0) color = '#04b00f'; + var mask = ref.mask; + if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)'; + var lineWidth = ref.lineWidth; + if (lineWidth === void 0) lineWidth = 1; + + var half = lineWidth / 2; + var boundOption = [{ + start: { + x: x - half, + y: y + 10 - half + }, + step1: { + x: x - half, + y: y - half + }, + step2: { + x: x + 10 - half, + y: y - half + } + }, + { + start: { + x: x - half, + y: y + height - 10 + half + }, + step1: { + x: x - half, + y: y + height + half + }, + step2: { + x: x + 10 - half, + y: y + height + half + } + }, + { + start: { + x: x + width - 10 + half, + y: y - half + }, + step1: { + x: x + width + half, + y: y - half + }, + step2: { + x: x + width + half, + y: y + 10 - half + } + }, + { + start: { + x: x + width + half, + y: y + height - 10 + half + }, + step1: { + x: x + width + half, + y: y + height + half + }, + step2: { + x: x + width - 10 + half, + y: y + height + half + } + } + ]; + + // 绘制半透明层 + self.ctx.beginPath(); + self.ctx.setFillStyle(mask); + self.ctx.fillRect(0, 0, x, boundHeight); + self.ctx.fillRect(x, 0, width, y); + self.ctx.fillRect(x, y + height, width, boundHeight - y - height); + self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight); + self.ctx.fill(); + + boundOption.forEach(function(op) { + self.ctx.beginPath(); + self.ctx.setStrokeStyle(color); + self.ctx.setLineWidth(lineWidth); + self.ctx.moveTo(op.start.x, op.start.y); + self.ctx.lineTo(op.step1.x, op.step1.y); + self.ctx.lineTo(op.step2.x, op.step2.y); + self.ctx.stroke(); + }); + }; + } + + var version = "1.3.9"; + + var WeCropper = function WeCropper(params) { + var self = this; + var _default = {}; + + validator(self, DEFAULT); + + Object.keys(DEFAULT).forEach(function(key) { + _default[key] = DEFAULT[key].default; + }); + Object.assign(self, _default, params); + + self.prepare(); + self.attachPage(); + self.createCtx(); + self.observer(); + self.cutt(); + self.methods(); + self.init(); + self.update(); + + return self + }; + + WeCropper.prototype.init = function init() { + var self = this; + var src = self.src; + + self.version = version; + + typeof self.onReady === 'function' && self.onReady(self.ctx, self); + + if (src) { + self.pushOrign(src); + } else { + self.updateCanvas(); + } + setTouchState(self, false, false, false); + + self.oldScale = 1; + self.newScale = 1; + + return self + }; + + Object.assign(WeCropper.prototype, handle); + + WeCropper.prototype.prepare = prepare; + WeCropper.prototype.observer = observer; + WeCropper.prototype.methods = methods; + WeCropper.prototype.cutt = cut; + WeCropper.prototype.update = update; + + return WeCropper; + +}))); diff --git a/uview-ui/components/u-avatar/u-avatar.vue b/uview-ui/components/u-avatar/u-avatar.vue new file mode 100644 index 0000000..289b9b0 --- /dev/null +++ b/uview-ui/components/u-avatar/u-avatar.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/uview-ui/components/u-back-top/u-back-top.vue b/uview-ui/components/u-back-top/u-back-top.vue new file mode 100644 index 0000000..7970fc7 --- /dev/null +++ b/uview-ui/components/u-back-top/u-back-top.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/uview-ui/components/u-badge/u-badge.vue b/uview-ui/components/u-badge/u-badge.vue new file mode 100644 index 0000000..e85b133 --- /dev/null +++ b/uview-ui/components/u-badge/u-badge.vue @@ -0,0 +1,216 @@ + + + + + \ No newline at end of file diff --git a/uview-ui/components/u-button/u-button.vue b/uview-ui/components/u-button/u-button.vue new file mode 100644 index 0000000..82c3a6f --- /dev/null +++ b/uview-ui/components/u-button/u-button.vue @@ -0,0 +1,596 @@ + + + + + diff --git a/uview-ui/components/u-calendar/u-calendar.vue b/uview-ui/components/u-calendar/u-calendar.vue new file mode 100644 index 0000000..e544f56 --- /dev/null +++ b/uview-ui/components/u-calendar/u-calendar.vue @@ -0,0 +1,715 @@ + + + + \ No newline at end of file diff --git a/uview-ui/components/u-car-keyboard/u-car-keyboard.vue b/uview-ui/components/u-car-keyboard/u-car-keyboard.vue new file mode 100644 index 0000000..84b1467 --- /dev/null +++ b/uview-ui/components/u-car-keyboard/u-car-keyboard.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/uview-ui/components/u-card/u-card.vue b/uview-ui/components/u-card/u-card.vue new file mode 100644 index 0000000..a3cb2aa --- /dev/null +++ b/uview-ui/components/u-card/u-card.vue @@ -0,0 +1,299 @@ + + + + + diff --git a/uview-ui/components/u-cell-group/u-cell-group.vue b/uview-ui/components/u-cell-group/u-cell-group.vue new file mode 100644 index 0000000..3fbca72 --- /dev/null +++ b/uview-ui/components/u-cell-group/u-cell-group.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/uview-ui/components/u-cell-item/u-cell-item.vue b/uview-ui/components/u-cell-item/u-cell-item.vue new file mode 100644 index 0000000..055af3a --- /dev/null +++ b/uview-ui/components/u-cell-item/u-cell-item.vue @@ -0,0 +1,316 @@ + + + + + diff --git a/uview-ui/components/u-chain/u-chain.vue b/uview-ui/components/u-chain/u-chain.vue new file mode 100644 index 0000000..426dbce --- /dev/null +++ b/uview-ui/components/u-chain/u-chain.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/uview-ui/components/u-checkbox-group/u-checkbox-group.vue b/uview-ui/components/u-checkbox-group/u-checkbox-group.vue new file mode 100644 index 0000000..6a149b3 --- /dev/null +++ b/uview-ui/components/u-checkbox-group/u-checkbox-group.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/uview-ui/components/u-checkbox/u-checkbox.vue b/uview-ui/components/u-checkbox/u-checkbox.vue new file mode 100644 index 0000000..bcbdc20 --- /dev/null +++ b/uview-ui/components/u-checkbox/u-checkbox.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/uview-ui/components/u-circle-progress/u-circle-progress.vue b/uview-ui/components/u-circle-progress/u-circle-progress.vue new file mode 100644 index 0000000..4bca3bf --- /dev/null +++ b/uview-ui/components/u-circle-progress/u-circle-progress.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/uview-ui/components/u-col/u-col.vue b/uview-ui/components/u-col/u-col.vue new file mode 100644 index 0000000..3b6cc64 --- /dev/null +++ b/uview-ui/components/u-col/u-col.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/uview-ui/components/u-collapse-item/u-collapse-item.vue b/uview-ui/components/u-collapse-item/u-collapse-item.vue new file mode 100644 index 0000000..3b66bfa --- /dev/null +++ b/uview-ui/components/u-collapse-item/u-collapse-item.vue @@ -0,0 +1,204 @@ + + + + + diff --git a/uview-ui/components/u-collapse/u-collapse.vue b/uview-ui/components/u-collapse/u-collapse.vue new file mode 100644 index 0000000..8572957 --- /dev/null +++ b/uview-ui/components/u-collapse/u-collapse.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/uview-ui/components/u-column-notice/u-column-notice.vue b/uview-ui/components/u-column-notice/u-column-notice.vue new file mode 100644 index 0000000..da944e3 --- /dev/null +++ b/uview-ui/components/u-column-notice/u-column-notice.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/uview-ui/components/u-count-down/u-count-down.vue b/uview-ui/components/u-count-down/u-count-down.vue new file mode 100644 index 0000000..7285d67 --- /dev/null +++ b/uview-ui/components/u-count-down/u-count-down.vue @@ -0,0 +1,318 @@ + + + + + diff --git a/uview-ui/components/u-count-to/u-count-to.vue b/uview-ui/components/u-count-to/u-count-to.vue new file mode 100644 index 0000000..053dc5f --- /dev/null +++ b/uview-ui/components/u-count-to/u-count-to.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/uview-ui/components/u-divider-other/u-divider-other.vue b/uview-ui/components/u-divider-other/u-divider-other.vue new file mode 100644 index 0000000..6f8d7e6 --- /dev/null +++ b/uview-ui/components/u-divider-other/u-divider-other.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/uview-ui/components/u-divider/u-divider.vue b/uview-ui/components/u-divider/u-divider.vue new file mode 100644 index 0000000..03c3bfc --- /dev/null +++ b/uview-ui/components/u-divider/u-divider.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/uview-ui/components/u-dropdown-item/u-dropdown-item.vue b/uview-ui/components/u-dropdown-item/u-dropdown-item.vue new file mode 100644 index 0000000..ae7cbb9 --- /dev/null +++ b/uview-ui/components/u-dropdown-item/u-dropdown-item.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/uview-ui/components/u-dropdown/u-dropdown.vue b/uview-ui/components/u-dropdown/u-dropdown.vue new file mode 100644 index 0000000..6da919c --- /dev/null +++ b/uview-ui/components/u-dropdown/u-dropdown.vue @@ -0,0 +1,298 @@ + + + + + diff --git a/uview-ui/components/u-empty/u-empty.vue b/uview-ui/components/u-empty/u-empty.vue new file mode 100644 index 0000000..2c77b24 --- /dev/null +++ b/uview-ui/components/u-empty/u-empty.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/uview-ui/components/u-field/u-field.vue b/uview-ui/components/u-field/u-field.vue new file mode 100644 index 0000000..b562798 --- /dev/null +++ b/uview-ui/components/u-field/u-field.vue @@ -0,0 +1,384 @@ +