diff --git a/README.md b/README.md index 5947b1f..da5e336 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,16 @@ src -- 源码目录 - 微信开发者工具开启服务端口:`设置->安全设置>服务端口开启` - 在`HBuilder X`中使用`运行->运行到小程序模拟器->微信开发者工具`运行项目,运行成功后会自动打开微信开发者工具并编译项目 +## 开发须知 + +- 登录相关 +* 接口地址:39.103.180.196:9012 +* 接口文档:[luoo-user API](http://39.103.180.196:9012/doc.html?open_in_browser=true) + +- 商城相关 +* 接口地址:43.248.137.154:8085 +* 接口文档:[mall前台系统](http://43.248.137.154:8085/swagger-ui/index.html) + ## 许可证 diff --git a/api/member.js b/api/member.js index 149b24d..dbaeac6 100644 --- a/api/member.js +++ b/api/member.js @@ -17,3 +17,93 @@ export function memberInfo() { url: '/sso/info' }) } + +/* +* 获取支持的手机号国家码 +* */ +export function supportedCountryCode() { + return request({ + requestBase:'USER', // 携带 USER 模块独有的配置 + method: 'GET', + url: '/user/user/supportedCountryCode' + }) +} + +/* +* 发送短信验证码 +* @data mobile 手机号 (必填) +* @data deviceId 设备id (必填) +* @data countryCode 国家码,默认为‘+86’ +* @data imageCheckCode 图形验证码 +**/ +export function sendsms(data) { + return request({ + requestBase:'USER', // 携带 USER 模块独有的配置 + header: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' + }, + method: 'POST', + url: '/user/user/sendsms', + data: data + }) +} + +/* +* 登录/注册 +* @data mobile 手机号 (必填) +* @data deviceId 设备id (必填) +* @data mobileCheckCode 6位验证码 (必填) +* @data deviceBrand 设备品牌 +**/ +export function appLogin(data) { + return request({ + requestBase:'USER', // 携带 USER 模块独有的配置 + header: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' + }, + method: 'POST', + url: '/user/user/appLogin', + data: data + }) +} + +/* +* 微信wxId登录/注册 +* @data code code码 (必填) +* @data deviceId 设备id (必填) +* @data mobile 手机号 +* @data mobileCheckCode 6位验证码 +* @data deviceBrand 设备品牌 +**/ +export function wxIdLogin(data) { + return request({ + requestBase:'USER', // 携带 USER 模块独有的配置 + method: 'POST', + url: '/user/user/wxIdLogin', + data: data + }) +} + +/* +* 退出登录 +* @hearder Authorization +**/ +export function logout() { + return request({ + requestBase:'USER', // 携带 USER 模块独有的配置 + method: 'POST', + url: '/user/user/logout' + }) +} + +/* +* 获取个人信息 +* */ +export function getUserInfo() { + return request({ + requestBase:'USER', // 携带 USER 模块独有的配置 + method: 'GET', + url: '/user/my/userInfo' + }) +} + diff --git a/components/uni-code-input/props.js b/components/uni-code-input/props.js new file mode 100644 index 0000000..0066f32 --- /dev/null +++ b/components/uni-code-input/props.js @@ -0,0 +1,96 @@ +const codeInput = { + adjustPosition: true, + maxlength: 6, + dot: false, + mode: 'box', + hairline: false, + space: 10, + value: '', + focus: false, + bold: false, + color: '#606266', + fontSize: 18, + size: 35, + disabledKeyboard: false, + borderColor: '#c9cacc', + disabledDot: true + } +export default { + props: { + // 键盘弹起时,是否自动上推页面 + adjustPosition: { + type: Boolean, + default: codeInput.adjustPosition + }, + // 最大输入长度 + maxlength: { + type: [String, Number], + default: codeInput.maxlength + }, + // 是否用圆点填充 + dot: { + type: Boolean, + default: codeInput.dot + }, + // 显示模式,box-盒子模式,line-底部横线模式 + mode: { + type: String, + default: codeInput.mode + }, + // 是否细边框 + hairline: { + type: Boolean, + default: codeInput.hairline + }, + // 字符间的距离 + space: { + type: [String, Number], + default: codeInput.space + }, + // 预置值 + value: { + type: [String, Number], + default: codeInput.value + }, + // 是否自动获取焦点 + focus: { + type: Boolean, + default: codeInput.focus + }, + // 字体是否加粗 + bold: { + type: Boolean, + default: codeInput.bold + }, + // 字体颜色 + color: { + type: String, + default: codeInput.color + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: codeInput.fontSize + }, + // 输入框的大小,宽等于高 + size: { + type: [String, Number], + default: codeInput.size + }, + // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true + disabledKeyboard: { + type: Boolean, + default: codeInput.disabledKeyboard + }, + // 边框和线条颜色 + borderColor: { + type: String, + default: codeInput.borderColor + }, + // 是否禁止输入"."符号 + disabledDot: { + type: Boolean, + default: codeInput.disabledDot + } + } +} diff --git a/components/uni-code-input/uni-code-input.vue b/components/uni-code-input/uni-code-input.vue new file mode 100644 index 0000000..2c55ec7 --- /dev/null +++ b/components/uni-code-input/uni-code-input.vue @@ -0,0 +1,264 @@ + + + + + \ No newline at end of file diff --git a/manifest.json b/manifest.json index 60596d1..695c928 100644 --- a/manifest.json +++ b/manifest.json @@ -56,10 +56,11 @@ "mp-weixin" : { /* 小程序特有相关 */ "usingComponents" : true, - "appid" : "", + "appid" : "wx69b3bdd669084b09", "setting" : { "urlCheck" : true - } + }, + "darkmode" : true }, "h5" : { "devServer" : { diff --git a/pages.json b/pages.json index 56113e5..d27983e 100644 --- a/pages.json +++ b/pages.json @@ -77,17 +77,7 @@ "animationType": "slide-in-bottom" } } - }, { - "path": "pages/public/register", - "style": { - "navigationBarTitleText": "", - "navigationStyle": "custom", - "app-plus": { - "titleNView": false, - "animationType": "slide-in-bottom" - } - } - }, { + },{ "path": "pages/user/user", "style": { "navigationBarTitleText": "我的", @@ -260,6 +250,13 @@ } } } + }, + { + "path" : "pages/webview/webview", + "style" : + { + "navigationBarTitleText" : "" + } } ], "globalStyle": { diff --git a/pages/address/addressManage.vue b/pages/address/addressManage.vue index 67624a3..78a0a5c 100644 --- a/pages/address/addressManage.vue +++ b/pages/address/addressManage.vue @@ -42,6 +42,7 @@ updateAddress, fetchAddressDetail } from '@/api/address.js'; + import rule from '@/utils/rule.js' export default { data() { return { @@ -108,7 +109,7 @@ this.$api.msg('请填写收货人姓名'); return; } - if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(data.phoneNumber)) { + if (!rule.mobile(data.phoneNumber)) { this.$api.msg('请输入正确的手机号码'); return; } diff --git a/pages/product/product.vue b/pages/product/product.vue index 3831e25..6387760 100644 --- a/pages/product/product.vue +++ b/pages/product/product.vue @@ -216,7 +216,7 @@ + + \ No newline at end of file diff --git a/pages/public/components/checkCode.vue b/pages/public/components/checkCode.vue new file mode 100644 index 0000000..05a55db --- /dev/null +++ b/pages/public/components/checkCode.vue @@ -0,0 +1,140 @@ + + + + + \ No newline at end of file diff --git a/pages/public/login.vue b/pages/public/login.vue index f1726a6..1526fbc 100644 --- a/pages/public/login.vue +++ b/pages/public/login.vue @@ -1,256 +1,424 @@ - + \ No newline at end of file diff --git a/pages/public/login_old.vue b/pages/public/login_old.vue new file mode 100644 index 0000000..d0831fa --- /dev/null +++ b/pages/public/login_old.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/pages/public/register.vue b/pages/public/register.vue deleted file mode 100644 index cec8b7e..0000000 --- a/pages/public/register.vue +++ /dev/null @@ -1,137 +0,0 @@ - - - - - diff --git a/pages/webview/webview.vue b/pages/webview/webview.vue new file mode 100644 index 0000000..305f0c4 --- /dev/null +++ b/pages/webview/webview.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/static/icons/icon_apple.png b/static/icons/icon_apple.png new file mode 100644 index 0000000..ecfeb8b Binary files /dev/null and b/static/icons/icon_apple.png differ diff --git a/static/icons/icon_clear.png b/static/icons/icon_clear.png new file mode 100644 index 0000000..8c82e85 Binary files /dev/null and b/static/icons/icon_clear.png differ diff --git a/static/icons/icon_down.png b/static/icons/icon_down.png new file mode 100644 index 0000000..fa98a13 Binary files /dev/null and b/static/icons/icon_down.png differ diff --git a/static/icons/icon_wechat.png b/static/icons/icon_wechat.png new file mode 100644 index 0000000..a3feaf9 Binary files /dev/null and b/static/icons/icon_wechat.png differ diff --git a/static/icons/left_back.png b/static/icons/left_back.png new file mode 100644 index 0000000..71260f2 Binary files /dev/null and b/static/icons/left_back.png differ diff --git a/static/icons/left_back@2x.png b/static/icons/left_back@2x.png new file mode 100644 index 0000000..f56612b Binary files /dev/null and b/static/icons/left_back@2x.png differ diff --git a/static/icons/left_back@3x.png b/static/icons/left_back@3x.png new file mode 100644 index 0000000..1e7f04d Binary files /dev/null and b/static/icons/left_back@3x.png differ diff --git a/static/public/login_bg_dark.png b/static/public/login_bg_dark.png new file mode 100644 index 0000000..95106dd Binary files /dev/null and b/static/public/login_bg_dark.png differ diff --git a/static/public/login_bg_light.png b/static/public/login_bg_light.png new file mode 100644 index 0000000..a9c4eaf Binary files /dev/null and b/static/public/login_bg_light.png differ diff --git a/store/index.js b/store/index.js index 3d2a508..200c1a1 100644 --- a/store/index.js +++ b/store/index.js @@ -7,6 +7,9 @@ const store = new Vuex.Store({ state: { hasLogin: false, userInfo: {}, + theme: uni.getSystemInfoSync().theme, + deviceId: uni.getDeviceInfo().deviceId, + deviceBrand: uni.getDeviceInfo().deviceBrand, }, mutations: { login(state, provider) { @@ -28,6 +31,9 @@ const store = new Vuex.Store({ uni.removeStorage({ key: 'token' }) + }, + changeTheme(state, theme) { + state.theme = theme } }, actions: { diff --git a/theme.json b/theme.json new file mode 100644 index 0000000..bbc1fd5 --- /dev/null +++ b/theme.json @@ -0,0 +1,24 @@ +{ + "light": { + "navigationBarTextStyle": "black", + "navigationBarBackgroundColor": "#F7F7F7", + "backgroundColor": "#F7F7F7", + + "tabBarColor": "#7A7E83", + "tabBarSelectedColor": "#3cc51f", + "tabBarBorderStyle": "black", + "tabBarBackgroundColor": "#F7F7F7" + + }, + "dark": { + "navigationBarTextStyle": "white", + "navigationBarBackgroundColor": "#1F1F1F", + "backgroundColor": "#1F1F1F", + + "tabBarColor": "#cacaca", + "tabBarSelectedColor": "#51A937", + "tabBarBorderStyle": "white", + "tabBarBackgroundColor": "#1F1F1F" + + } +} diff --git a/utils/appConfig.js b/utils/appConfig.js index c69c25d..d917d18 100644 --- a/utils/appConfig.js +++ b/utils/appConfig.js @@ -1,7 +1,15 @@ // appConfig.js //配置API请求的基础路径 -// export const API_BASE_URL = 'http://localhost:8085'; -export const API_BASE_URL = 'https://portal-api.macrozheng.com'; +export const API_BASE_URL = 'http://43.248.137.154:8085'; +export const API_USER_URL = 'http://39.103.180.196:9012'; + +// export const API_BASE_URL = 'https://portal-api.macrozheng.com'; //是否启用支付宝支付 -export const USE_ALIPAY = false; \ No newline at end of file +export const USE_ALIPAY = true; + +// 是否启用微信登录 +export const USE_WECHAT_LOGIN = false; + +// 是否启用Apple登录 +export const USE_APPLE_LOGIN = false; \ No newline at end of file diff --git a/utils/index.js b/utils/index.js new file mode 100644 index 0000000..e5682e7 --- /dev/null +++ b/utils/index.js @@ -0,0 +1,29 @@ + +import rule from './rule.js' +/** + * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾 + * @param {string|number} value 需要添加单位的值 + * @param {string} unit 添加的单位名 比如px + */ +export function addUnit(value = 'auto', unit = uni?.$u?.config?.unit ?? 'px') { + value = String(value) + // 用uView内置验证规则中的number判断是否为数值 + return rule.number(value) ? `${value}${unit}` : value +} + +/** + * @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换 + * @param {number|string} value 用户传递值的px值 + * @param {boolean} unit + * @returns {number|string} + */ +export function getPx(value, unit = false) { + if (rule.number(value)) { + return unit ? `${value}px` : Number(value) + } + // 如果带有rpx,先取出其数值部分,再转为px值 + if (/(rpx|upx)$/.test(value)) { + return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value))) + } + return unit ? `${parseInt(value)}px` : parseInt(value) +} \ No newline at end of file diff --git a/utils/requestUtil.js b/utils/requestUtil.js index 77f1ecb..da45226 100644 --- a/utils/requestUtil.js +++ b/utils/requestUtil.js @@ -1,9 +1,13 @@ import Request from '@/js_sdk/luch-request/request.js' -import { API_BASE_URL} from '@/utils/appConfig.js'; +import { + API_BASE_URL, + API_USER_URL, +} from '@/utils/appConfig.js'; const http = new Request() -http.setConfig((config) => { /* 设置全局配置 */ +http.setConfig((config) => { + /* 设置全局配置 */ config.baseUrl = API_BASE_URL /* 根域名不同 */ config.header = { ...config.header @@ -20,14 +24,20 @@ http.validateStatus = (statusCode) => { return statusCode === 200 } -http.interceptor.request((config, cancel) => { /* 请求之前拦截器 */ +http.interceptor.request((config, cancel) => { + /* 请求之前拦截器 */ + if (config.requestBase === 'USER') { + config.baseUrl = API_USER_URL + } else { + config.baseUrl = API_BASE_URL + } const token = uni.getStorageSync('token'); - if(token){ + if (token) { config.header = { - 'Authorization':token, + 'Authorization': token, ...config.header } - }else{ + } else { config.header = { ...config.header } @@ -40,21 +50,23 @@ http.interceptor.request((config, cancel) => { /* 请求之前拦截器 */ return config }) -http.interceptor.response((response) => { /* 请求之后拦截器 */ +http.interceptor.response((response) => { + /* 请求之后拦截器 */ const res = response.data; if (res.code !== 200) { //提示错误信息 uni.showToast({ - title:res.message, - duration:1500 + title: res.message, + duration: 1500, + icon: 'error' }) //401未登录处理 if (res.code === 401) { uni.showModal({ title: '提示', content: '你已被登出,可以取消继续留在该页面,或者重新登录', - confirmText:'重新登录', - cancelText:'取消', + confirmText: '重新登录', + cancelText: '取消', success: function(res) { if (res.confirm) { uni.navigateTo({ @@ -74,13 +86,13 @@ http.interceptor.response((response) => { /* 请求之后拦截器 */ //提示错误信息 console.log('response error', response); uni.showToast({ - title:response.errMsg, - duration:1500 + title: response.errMsg, + duration: 1500 }) return Promise.reject(response); }) -export function request (options = {}) { +export function request(options = {}) { return http.request(options); } diff --git a/utils/rule.js b/utils/rule.js new file mode 100644 index 0000000..7c5f1ae --- /dev/null +++ b/utils/rule.js @@ -0,0 +1,289 @@ +/** + * 验证电子邮箱格式 + */ +function email(value) { + return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value) +} + +/** + * 验证手机格式 + * source: https://m.jihaoba.com/tools/haoduan/ + */ +function mobile(value) { + return /^((13[0-9])|(14[0|5|6|7|9])|(15[0|1|2|3|5|6|7|8|9])|(16[2|5|6|7])|(17[0|1|2|3|5|6|7|8])|(18[0-9])|(19[0|1|2|3|5|6|7|8|9]))\d{8}$/.test(value) +} + +/** + * 验证URL格式 + */ +function url(value) { + return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/ + .test(value) +} + +/** + * 验证日期格式 + */ +function date(value) { + if (!value) return false + // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳 + if (number(value)) value = +value + return !/Invalid|NaN/.test(new Date(value).toString()) +} + +/** + * 验证ISO类型的日期格式 + */ +function dateISO(value) { + return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value) +} + +/** + * 验证十进制数字 + */ +function number(value) { + return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value) +} + +/** + * 验证字符串 + */ +function string(value) { + return typeof value === 'string' +} + +/** + * 验证整数 + */ +function digits(value) { + return /^\d+$/.test(value) +} + +/** + * 验证身份证号码 + */ +function idCard(value) { + return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test( + value + ) +} + +/** + * 是否车牌号 + */ +function carNo(value) { + // 新能源车牌 + const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/ + // 旧车牌 + const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/ + if (value.length === 7) { + return creg.test(value) + } if (value.length === 8) { + return xreg.test(value) + } + return false +} + +/** + * 金额,只允许2位小数 + */ +function amount(value) { + // 金额,只允许保留两位小数 + return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value) +} + +/** + * 中文 + */ +function chinese(value) { + const reg = /^[\u4e00-\u9fa5]+$/gi + return reg.test(value) +} + +/** + * 只能输入字母 + */ +function letter(value) { + return /^[a-zA-Z]*$/.test(value) +} + +/** + * 只能是字母或者数字 + */ +function enOrNum(value) { + // 英文或者数字 + const reg = /^[0-9a-zA-Z]*$/g + return reg.test(value) +} + +/** + * 验证是否包含某个值 + */ +function contains(value, param) { + return value.indexOf(param) >= 0 +} + +/** + * 验证一个值范围[min, max] + */ +function range(value, param) { + return value >= param[0] && value <= param[1] +} + +/** + * 验证一个长度范围[min, max] + */ +function rangeLength(value, param) { + return value.length >= param[0] && value.length <= param[1] +} + +/** + * 是否固定电话 + */ +function landline(value) { + const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/ + return reg.test(value) +} + +/** + * 判断是否为空 + */ +function empty(value) { + switch (typeof value) { + case 'undefined': + return true + case 'string': + if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true + break + case 'boolean': + if (!value) return true + break + case 'number': + if (value === 0 || isNaN(value)) return true + break + case 'object': + if (value === null || value.length === 0) return true + for (const i in value) { + return false + } + return true + } + return false +} + +/** + * 是否json字符串 + */ +function jsonString(value) { + if (typeof value === 'string') { + try { + const obj = JSON.parse(value) + if (typeof obj === 'object' && obj) { + return true + } + return false + } catch (e) { + return false + } + } + return false +} + +/** + * 是否数组 + */ +function array(value) { + if (typeof Array.isArray === 'function') { + return Array.isArray(value) + } + return Object.prototype.toString.call(value) === '[object Array]' +} + +/** + * 是否对象 + */ +function object(value) { + return Object.prototype.toString.call(value) === '[object Object]' +} + +/** + * 是否短信验证码 + */ +function code(value, len = 6) { + return new RegExp(`^\\d{${len}}$`).test(value) +} + +/** + * 是否函数方法 + * @param {Object} value + */ +function func(value) { + return typeof value === 'function' +} + +/** + * 是否promise对象 + * @param {Object} value + */ +function promise(value) { + return object(value) && func(value.then) && func(value.catch) +} + +/** 是否图片格式 + * @param {Object} value + */ +function image(value) { + const newValue = value.split('?')[0] + const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i + return IMAGE_REGEXP.test(newValue) +} + +/** + * 是否视频格式 + * @param {Object} value + */ +function video(value) { + const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i + return VIDEO_REGEXP.test(value) +} + +/** + * 是否为正则对象 + * @param {Object} + * @return {Boolean} + */ +function regExp(o) { + return o && Object.prototype.toString.call(o) === '[object RegExp]' +} + +export default { + email, + mobile, + url, + date, + dateISO, + number, + digits, + idCard, + carNo, + amount, + chinese, + letter, + enOrNum, + contains, + range, + rangeLength, + empty, + isEmpty: empty, + jsonString, + landline, + object, + array, + code, + func, + promise, + video, + image, + regExp, + string +} \ No newline at end of file