diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b163ca8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/unpackage diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..60525dd --- /dev/null +++ b/App.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c40d577 --- /dev/null +++ b/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "{}" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright 2018 萤火科技 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index e69de29..387d8e8 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,3 @@ +# 专网注册系统 +# +国家文化专网网络注册登录系统 \ No newline at end of file diff --git a/androidPrivacy.json b/androidPrivacy.json new file mode 100644 index 0000000..4896361 --- /dev/null +++ b/androidPrivacy.json @@ -0,0 +1,14 @@ +{ + "version" : "1", + "prompt" : "template", + "title" : "服务协议和隐私政策", + "message" : "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。
  你可阅读《服务协议》《隐私政策》了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。", + "buttonAccept" : "同意并接受", + "buttonRefuse" : "暂不同意", + "second" : { + "title" : "确认提示", + "message" : "  进入应用前,你需先同意《服务协议》《隐私政策》,否则将退出应用。", + "buttonAccept" : "同意并继续", + "buttonRefuse" : "退出应用" + } +} diff --git a/api/address.js b/api/address.js new file mode 100644 index 0000000..43faa4d --- /dev/null +++ b/api/address.js @@ -0,0 +1,47 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'address/list', + defaultId: 'address/defaultId', + detail: 'address/detail', + add: 'address/add', + edit: 'address/edit', + setDefault: 'address/setDefault', + remove: 'address/remove' +} + +// 收货地址列表 +export const list = (param) => { + return request.get(api.list, param) +} + +// 默认收货地址ID +export const defaultId = (param) => { + return request.get(api.defaultId, param) +} + +// 收货地址详情 +export const detail = (addressId) => { + return request.get(api.detail, { addressId }) +} + +// 新增收货地址 +export const add = (data) => { + return request.post(api.add, { form: data }) +} + +// 编辑收货地址 +export const edit = (addressId, data) => { + return request.post(api.edit, { addressId, form: data }) +} + +// 设置默认收货地址 +export const setDefault = (addressId) => { + return request.post(api.setDefault, { addressId }) +} + +// 删除收货地址 +export const remove = (addressId) => { + return request.post(api.remove, { addressId }) +} diff --git a/api/article/category.js b/api/article/category.js new file mode 100644 index 0000000..62e0aeb --- /dev/null +++ b/api/article/category.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'article.category/list' +} + +// 页面数据 +export function list() { + return request.get(api.list) +} diff --git a/api/article/index.js b/api/article/index.js new file mode 100644 index 0000000..a5411f7 --- /dev/null +++ b/api/article/index.js @@ -0,0 +1,17 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'article/list', + detail: 'article/detail' +} + +// 文章列表 +export function list(param, option) { + return request.get(api.list, param, option) +} + +// 文章详情 +export function detail(articleId) { + return request.get(api.detail, { articleId }) +} diff --git a/api/balance.js b/api/balance.js new file mode 100644 index 0000000..62b0ef1 --- /dev/null +++ b/api/balance.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'balance.log/list' +} + +// 余额账单明细列表 +export const list = (param) => { + return request.get(api.list, param) +} diff --git a/api/balance/log.js b/api/balance/log.js new file mode 100644 index 0000000..153fb2a --- /dev/null +++ b/api/balance/log.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'balance.log/list' +} + +// 余额账单明细 +export const list = (param) => { + return request.get(api.list, param) +} diff --git a/api/captcha.js b/api/captcha.js new file mode 100644 index 0000000..6fff247 --- /dev/null +++ b/api/captcha.js @@ -0,0 +1,17 @@ +import request from '@/utils/request' + +// api地址 +const api = { + image: 'captcha/image', + sendSmsCaptcha: 'captcha/sendSmsCaptcha' +} + +// 图形验证码 +export function image() { + return request.get(api.image, {}, { load: false }) +} + +// 发送短信验证码 +export function sendSmsCaptcha(data) { + return request.post(api.sendSmsCaptcha, data, { load: false }) +} diff --git a/api/cart.js b/api/cart.js new file mode 100644 index 0000000..cba717e --- /dev/null +++ b/api/cart.js @@ -0,0 +1,35 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'cart/list', + total: 'cart/total', + add: 'cart/add', + update: 'cart/update', + clear: 'cart/clear' +} + +// 购物车列表 +export const list = () => { + return request.get(api.list, {}, { load: false }) +} + +// 购物车商品总数量 +export const total = () => { + return request.get(api.total, {}, { load: false }) +} + +// 加入购物车 +export const add = (goodsId, goodsSkuId, goodsNum) => { + return request.post(api.add, { goodsId, goodsSkuId, goodsNum }) +} + +// 更新购物车商品数量 +export const update = (goodsId, goodsSkuId, goodsNum) => { + return request.post(api.update, { goodsId, goodsSkuId, goodsNum }, { isPrompt: false }) +} + +// 删除购物车中指定记录 +export const clear = (cartIds = []) => { + return request.post(api.clear, { cartIds }) +} diff --git a/api/category/index.js b/api/category/index.js new file mode 100644 index 0000000..7198be0 --- /dev/null +++ b/api/category/index.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'category/list' +} + +// 页面数据 +export function list() { + return request.get(api.list) +} diff --git a/api/checkout.js b/api/checkout.js new file mode 100644 index 0000000..d64d3d4 --- /dev/null +++ b/api/checkout.js @@ -0,0 +1,19 @@ +import request from '@/utils/request' + +// api地址 +const api = { + order: 'checkout/order', + submit: 'checkout/submit', +} + +// mode: 结算模式 (buyNow立即购买 cart购物车) + +// 结算台订单信息 +export const order = (mode, param) => { + return request.get(api.order, { mode, ...param }) +} + +// 结算台订单提交 +export const submit = (mode, data) => { + return request.post(api.submit, { mode, ...data }) +} diff --git a/api/comment.js b/api/comment.js new file mode 100644 index 0000000..c751c01 --- /dev/null +++ b/api/comment.js @@ -0,0 +1,23 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'comment/list', + listRows: 'comment/listRows', + total: 'comment/total' +} + +// 商品评价列表 +export const list = (goodsId, param, option) => { + return request.get(api.list, { ...param, goodsId }, option) +} + +// 商品评价列表 (限制数量, 用于商品详情页展示) +export const listRows = (goodsId, limit = 5) => { + return request.get(api.listRows, { goodsId, limit }) +} + +// 商品评分总数 +export const total = (goodsId) => { + return request.get(api.total, { goodsId }) +} diff --git a/api/coupon.js b/api/coupon.js new file mode 100644 index 0000000..706149c --- /dev/null +++ b/api/coupon.js @@ -0,0 +1,16 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'coupon/list' +} + +// 优惠券列表 +export const list = (param, option) => { + const options = { + isPrompt: true, //(默认 true 说明:本接口抛出的错误是否提示) + load: true, //(默认 true 说明:本接口是否提示加载动画) + ...option + } + return request.get(api.list, param, options) +} diff --git a/api/express.js b/api/express.js new file mode 100644 index 0000000..b0db530 --- /dev/null +++ b/api/express.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'express/list' +} + +// 物流公司列表 +export const list = (param) => { + return request.get(api.list, param) +} diff --git a/api/goods.js b/api/goods.js new file mode 100644 index 0000000..452a319 --- /dev/null +++ b/api/goods.js @@ -0,0 +1,23 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'goods/list', + detail: 'goods/detail', + specData: 'goods/specData' +} + +// 商品列表 +export const list = param => { + return request.get(api.list, param) +} + +// 商品详情 +export const detail = goodsId => { + return request.get(api.detail, { goodsId }) +} + +// 获取商品规格数据 +export const specData = (goodsId) => { + return request.get(api.specData, { goodsId }) +} diff --git a/api/goods/service.js b/api/goods/service.js new file mode 100644 index 0000000..4bc2e43 --- /dev/null +++ b/api/goods/service.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'goods.service/list' +} + +// 商品评价列表 +export function list(goodsId) { + return request.get(api.list, { goodsId }) +} diff --git a/api/help.js b/api/help.js new file mode 100644 index 0000000..150fa07 --- /dev/null +++ b/api/help.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'help/list' +} + +// 帮助中心列表 +export const list = (param) => { + return request.get(api.list, param) +} diff --git a/api/login/index.js b/api/login/index.js new file mode 100644 index 0000000..c0a1988 --- /dev/null +++ b/api/login/index.js @@ -0,0 +1,23 @@ +import request from '@/utils/request' + +// api地址 +const api = { + login: 'passport/login', + loginMpWx: 'passport/loginMpWx', + loginMpWxMobile: 'passport/loginMpWxMobile', +} + +// 用户登录(手机号+验证码) +export function login(data) { + return request.post(api.login, data) +} + +// 微信小程序快捷登录(获取微信用户基本信息) +export function loginMpWx(data, option) { + return request.post(api.loginMpWx, data, option) +} + +// 微信小程序快捷登录(授权手机号) +export function loginMpWxMobile(data, option) { + return request.post(api.loginMpWxMobile, data, option) +} diff --git a/api/myCoupon.js b/api/myCoupon.js new file mode 100644 index 0000000..69480e8 --- /dev/null +++ b/api/myCoupon.js @@ -0,0 +1,22 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'myCoupon/list', + receive: 'myCoupon/receive' +} + +// 我的优惠券列表 +export const list = (param, option) => { + const options = { + isPrompt: true, //(默认 true 说明:本接口抛出的错误是否提示) + load: true, //(默认 true 说明:本接口是否提示加载动画) + ...option + } + return request.get(api.list, param, options) +} + +// 领取优惠券 +export const receive = (couponId, data) => { + return request.post(api.receive, { couponId, ...couponId, data }) +} diff --git a/api/order.js b/api/order.js new file mode 100644 index 0000000..8a9c16c --- /dev/null +++ b/api/order.js @@ -0,0 +1,47 @@ +import request from '@/utils/request' + +// api地址 +const api = { + todoCounts: 'order/todoCounts', + list: 'order/list', + detail: 'order/detail', + express: 'order/express', + cancel: 'order/cancel', + receipt: 'order/receipt', + pay: 'order/pay' +} + +// 当前用户待处理的订单数量 +export function todoCounts(param, option) { + return request.get(api.todoCounts, param, option) +} + +// 我的订单列表 +export function list(param, option) { + return request.get(api.list, param, option) +} + +// 订单详情 +export function detail(orderId, param) { + return request.get(api.detail, { orderId, ...param }) +} + +// 获取物流信息 +export function express(orderId, param) { + return request.get(api.express, { orderId, ...param }) +} + +// 取消订单 +export function cancel(orderId, data) { + return request.post(api.cancel, { orderId, ...data }) +} + +// 确认收货 +export function receipt(orderId, data) { + return request.post(api.receipt, { orderId, ...data }) +} + +// 立即支付 +export function pay(orderId, payType, param) { + return request.get(api.pay, { orderId, payType, ...param }) +} diff --git a/api/order/comment.js b/api/order/comment.js new file mode 100644 index 0000000..f0d57b8 --- /dev/null +++ b/api/order/comment.js @@ -0,0 +1,17 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'order.comment/list', + submit: 'order.comment/submit' +} + +// 待评价订单商品列表 +export const list = (orderId, param) => { + return request.get(api.list, { orderId, ...param }) +} + +// 创建商品评价 +export const submit = (orderId, data) => { + return request.post(api.submit, { orderId, form: data }) +} diff --git a/api/page.js b/api/page.js new file mode 100644 index 0000000..bc0a519 --- /dev/null +++ b/api/page.js @@ -0,0 +1,13 @@ +import request from '@/utils/request' + +// api地址 +const apiUri = { + detail: 'page/detail' +} + +// 页面数据 +export function detail(pageId) { + return request.get(apiUri.detail, { + pageId + }) +} diff --git a/api/points/log.js b/api/points/log.js new file mode 100644 index 0000000..bb7a6e7 --- /dev/null +++ b/api/points/log.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'points.log/list' +} + +// 积分明细列表 +export const list = (param) => { + return request.get(api.list, param) +} diff --git a/api/recharge.js b/api/recharge.js new file mode 100644 index 0000000..dc8f046 --- /dev/null +++ b/api/recharge.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + submit: 'recharge/submit' +} + +// 积分明细列表 +export const submit = (data) => { + return request.post(api.submit, data) +} diff --git a/api/recharge/order.js b/api/recharge/order.js new file mode 100644 index 0000000..4cab383 --- /dev/null +++ b/api/recharge/order.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'recharge.order/list' +} + +// 我的充值记录列表 +export const list = (param) => { + return request.get(api.list, param) +} diff --git a/api/recharge/plan.js b/api/recharge/plan.js new file mode 100644 index 0000000..56f4550 --- /dev/null +++ b/api/recharge/plan.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'recharge.plan/list' +} + +// 充值套餐列表 +export const list = (param) => { + return request.get(api.list, param) +} diff --git a/api/refund.js b/api/refund.js new file mode 100644 index 0000000..d92af6e --- /dev/null +++ b/api/refund.js @@ -0,0 +1,35 @@ +import request from '@/utils/request' + +// api地址 +const api = { + list: 'refund/list', + goods: 'refund/goods', + apply: 'refund/apply', + detail: 'refund/detail', + delivery: 'refund/delivery' +} + +// 售后单列表 +export const list = (param, option) => { + return request.get(api.list, param, option) +} + +// 订单商品详情 +export const goods = (orderGoodsId, param) => { + return request.get(api.goods, { orderGoodsId, ...param }) +} + +// 申请售后 +export const apply = (orderGoodsId, data) => { + return request.post(api.apply, { orderGoodsId, form: data }) +} + +// 售后单详情 +export const detail = (orderRefundId, param) => { + return request.get(api.detail, { orderRefundId, ...param }) +} + +// 用户发货 +export const delivery = (orderRefundId, data) => { + return request.post(api.delivery, { orderRefundId, form: data }) +} diff --git a/api/region.js b/api/region.js new file mode 100644 index 0000000..d8ba090 --- /dev/null +++ b/api/region.js @@ -0,0 +1,17 @@ +import request from '@/utils/request' + +// api地址 +const api = { + all: 'region/all', + tree: 'region/tree' +} + +// 获取所有地区 +export const all = (param) => { + return request.get(api.all, param) +} + +// 获取所有地区(树状) +export const tree = (param) => { + return request.get(api.tree, param) +} diff --git a/api/setting.js b/api/setting.js new file mode 100644 index 0000000..5d3327e --- /dev/null +++ b/api/setting.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + data: 'setting/data' +} + +// 设置项详情 +export function data() { + return request.get(api.data) +} diff --git a/api/upload.js b/api/upload.js new file mode 100644 index 0000000..4e28551 --- /dev/null +++ b/api/upload.js @@ -0,0 +1,18 @@ +import request from '@/utils/request' + +// api地址 +const api = { + image: 'upload/image' +} + +// 图片上传 +export const image = files => { + // 文件上传大小, 2M + const maxSize = 1024 * 1024 * 2 + // 执行上传 + return new Promise((resolve, reject) => { + request.urlFileUpload({ files, maxSize }) + .then(result => resolve(result.map(item => item.data.fileInfo.file_id), result)) + .catch(reject) + }) +} diff --git a/api/user.js b/api/user.js new file mode 100644 index 0000000..6e3c30c --- /dev/null +++ b/api/user.js @@ -0,0 +1,34 @@ +import request from '@/utils/request' + +// api地址 +const api = { + userInfo: 'user/info', + assets: 'user/assets', + bindMobile: 'user/bindMobile', + personal: 'user/personal' +} + +// 当前登录的用户信息 +export const info = (param, option) => { + const options = { + isPrompt: true, //(默认 true 说明:本接口抛出的错误是否提示) + load: true, //(默认 true 说明:本接口是否提示加载动画) + ...option + } + return request.get(api.userInfo, param, options) +} + +// 账户资产 +export const assets = (param, option) => { + return request.get(api.assets, param, option) +} + +// 绑定手机号 +export const bindMobile = (data, option) => { + return request.post(api.bindMobile, data, option) +} + +// 修改个人信息(头像昵称) +export const personal = (data, option) => { + return request.post(api.personal, data, option) +} diff --git a/api/user/coupon.js b/api/user/coupon.js new file mode 100644 index 0000000..3412566 --- /dev/null +++ b/api/user/coupon.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// api地址 +const api = { + receive: 'user.coupon/receive' +} + +// 优惠券列表 +export const receive = (data) => { + return request.post(api.receive, data) +} diff --git a/app.scss b/app.scss new file mode 100644 index 0000000..c42140f --- /dev/null +++ b/app.scss @@ -0,0 +1,44 @@ +/* utils.scss */ +@import "/utils/utils.scss"; + +page { + background: #fafafa; +} + +@-webkit-keyframes rotate { + 0% { + transform: rotate(0deg) scale(1); + } + + 100% { + transform: rotate(360deg) scale(1); + } +} + +@keyframes rotate { + 0% { + transform: rotate(0deg) scale(1); + } + + 100% { + transform: rotate(360deg) scale(1); + } +} + +/* #ifdef H5*/ + +uni-page { + box-shadow: 0 1px 22px rgb(169, 169, 169, .3); +} + +.footer-fixed { + left: var(--window-left) !important; + right: var(--window-right) !important; +} + +.u-mask,.u-drawer { + left: var(--window-left) !important; + right: var(--window-right) !important; +} + +/* #endif */ \ No newline at end of file diff --git a/common/constant/index.js b/common/constant/index.js new file mode 100644 index 0000000..4c1c240 --- /dev/null +++ b/common/constant/index.js @@ -0,0 +1,3 @@ +import paginate from './paginate' + +export { paginate } diff --git a/common/constant/paginate.js b/common/constant/paginate.js new file mode 100644 index 0000000..0617b7e --- /dev/null +++ b/common/constant/paginate.js @@ -0,0 +1,7 @@ +export default { + data: [], // 列表数据 + current_page: 1, // 当前页码 + last_page: 1, // 最大页码 + per_page: 15, // 每页记录数 + total: 0, // 总记录数 +} diff --git a/common/enum/coupon/ApplyRange.js b/common/enum/coupon/ApplyRange.js new file mode 100644 index 0000000..7422e62 --- /dev/null +++ b/common/enum/coupon/ApplyRange.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:优惠券适用范围 + * ApplyRangeEnum + */ +export default new Enum([ + { key: 'ALL', name: '全部商品', value: 10 }, + { key: 'SOME_GOODS', name: '指定商品', value: 20 } +]) diff --git a/common/enum/coupon/CouponType.js b/common/enum/coupon/CouponType.js new file mode 100644 index 0000000..5f56fda --- /dev/null +++ b/common/enum/coupon/CouponType.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:优惠券类型 + * CouponTypeEnum + */ +export default new Enum([ + { key: 'FULL_DISCOUNT', name: '满减券', value: 10 }, + { key: 'DISCOUNT', name: '折扣券', value: 20 } +]) diff --git a/common/enum/coupon/ExpireType.js b/common/enum/coupon/ExpireType.js new file mode 100644 index 0000000..269b36c --- /dev/null +++ b/common/enum/coupon/ExpireType.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:优惠券到期类型 + * ExpireTypeEnum + */ +export default new Enum([ + { key: 'RECEIVE', name: '领取后', value: 10 }, + { key: 'FIXED_TIME', name: '固定时间', value: 20 } +]) diff --git a/common/enum/coupon/index.js b/common/enum/coupon/index.js new file mode 100644 index 0000000..be1bdc8 --- /dev/null +++ b/common/enum/coupon/index.js @@ -0,0 +1,5 @@ +import ApplyRangeEnum from './ApplyRange' +import ExpireTypeEnum from './ExpireType' +import CouponTypeEnum from './CouponType' + +export { ApplyRangeEnum, CouponTypeEnum, ExpireTypeEnum } diff --git a/common/enum/enum.js b/common/enum/enum.js new file mode 100644 index 0000000..0894708 --- /dev/null +++ b/common/enum/enum.js @@ -0,0 +1,85 @@ +/** + * 枚举类 + * Enum.IMAGE.name => "图片" + * Enum.getNameByKey('IMAGE') => "图片" + * Enum.getValueByKey('IMAGE') => 10 + * Enum.getNameByValue(10) => "图片" + * Enum.getData() => [{key: "IMAGE", name: "图片", value: 10}] + */ +class Enum { + constructor (param) { + const keyArr = [] + const valueArr = [] + + if (!Array.isArray(param)) { + throw new Error('param is not an array!') + } + + param.map(element => { + if (!element.key || !element.name) { + return + } + // 保存key值组成的数组,方便A.getName(name)类型的调用 + keyArr.push(element.key) + valueArr.push(element.value) + // 根据key生成不同属性值,以便A.B.name类型的调用 + this[element.key] = element + if (element.key !== element.value) { + this[element.value] = element + } + }) + + + // 保存源数组 + this.data = param + this.keyArr = keyArr + this.valueArr = valueArr + + // 防止被修改 + // Object.freeze(this) + } + + // 根据key得到对象 + keyOf (key) { + return this.data[this.keyArr.indexOf(key)] + } + + // 根据key得到对象 + valueOf (key) { + return this.data[this.valueArr.indexOf(key)] + } + + // 根据key获取name值 + getNameByKey (key) { + const prop = this.keyOf(key) + if (!prop) { + throw new Error('No enum constant' + key) + } + return prop.name + } + + // 根据value获取name值 + getNameByValue (value) { + const prop = this.valueOf(value) + if (!prop) { + throw new Error('No enum constant' + value) + } + return prop.name + } + + // 根据key获取value值 + getValueByKey (key) { + const prop = this.keyOf(key) + if (!prop) { + throw new Error('No enum constant' + key) + } + return prop.key + } + + // 返回源数组 + getData () { + return this.data + } +} + +export default Enum diff --git a/common/enum/goods/SpecType.js b/common/enum/goods/SpecType.js new file mode 100644 index 0000000..ec7832e --- /dev/null +++ b/common/enum/goods/SpecType.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:商品规格类型 + * SpecTypeEnum + */ +export default new Enum([ + { key: 'SINGLE', name: '单规格', value: 10 }, + { key: 'MULTI', name: '多规格', value: 20 } +]) diff --git a/common/enum/goods/index.js b/common/enum/goods/index.js new file mode 100644 index 0000000..481f203 --- /dev/null +++ b/common/enum/goods/index.js @@ -0,0 +1,3 @@ +import SpecTypeEnum from './SpecType' + +export { SpecTypeEnum } diff --git a/common/enum/order/DeliveryStatus.js b/common/enum/order/DeliveryStatus.js new file mode 100644 index 0000000..ed55f95 --- /dev/null +++ b/common/enum/order/DeliveryStatus.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:订单发货状态 + * DeliveryStatusEnum + */ +export default new Enum([ + { key: 'NOT_DELIVERED', name: '未发货', value: 10 }, + { key: 'DELIVERED', name: '已发货', value: 20 } +]) diff --git a/common/enum/order/DeliveryType.js b/common/enum/order/DeliveryType.js new file mode 100644 index 0000000..fe6623c --- /dev/null +++ b/common/enum/order/DeliveryType.js @@ -0,0 +1,9 @@ +import Enum from '../enum' + +/** + * 枚举类:配送方式 + * DeliveryTypeEnum + */ +export default new Enum([ + { key: 'EXPRESS', name: '快递配送', value: 10 } +]) diff --git a/common/enum/order/OrderSource.js b/common/enum/order/OrderSource.js new file mode 100644 index 0000000..e46062c --- /dev/null +++ b/common/enum/order/OrderSource.js @@ -0,0 +1,11 @@ +import Enum from '../enum' + +/** + * 枚举类:订单来源 + * OrderSourceEnum + */ +export default new Enum([ + { key: 'MASTER', name: '普通订单', value: 10 }, + { key: 'BARGAIN', name: '砍价订单', value: 20 }, + { key: 'SHARP', name: '秒杀订单', value: 30 } +]) diff --git a/common/enum/order/OrderStatus.js b/common/enum/order/OrderStatus.js new file mode 100644 index 0000000..f706e43 --- /dev/null +++ b/common/enum/order/OrderStatus.js @@ -0,0 +1,12 @@ +import Enum from '../enum' + +/** + * 枚举类:订单状态 + * OrderStatusEnum + */ +export default new Enum([ + { key: 'NORMAL', name: '进行中', value: 10 }, + { key: 'CANCELLED', name: '已取消', value: 20 }, + { key: 'APPLY_CANCEL', name: '待取消', value: 21 }, + { key: 'COMPLETED', name: '已完成', value: 30 } +]) diff --git a/common/enum/order/PayStatus.js b/common/enum/order/PayStatus.js new file mode 100644 index 0000000..f0e4b1d --- /dev/null +++ b/common/enum/order/PayStatus.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:订单支付状态 + * PayStatusEnum + */ +export default new Enum([ + { key: 'PENDING', name: '待支付', value: 10 }, + { key: 'SUCCESS', name: '已支付', value: 20 } +]) diff --git a/common/enum/order/PayType.js b/common/enum/order/PayType.js new file mode 100644 index 0000000..96612df --- /dev/null +++ b/common/enum/order/PayType.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:订单支付方式 + * PayTypeEnum + */ +export default new Enum([ + { key: 'BALANCE', name: '余额支付', value: 10 }, + { key: 'WECHAT', name: '微信支付', value: 20 } +]) diff --git a/common/enum/order/ReceiptStatus.js b/common/enum/order/ReceiptStatus.js new file mode 100644 index 0000000..34916a0 --- /dev/null +++ b/common/enum/order/ReceiptStatus.js @@ -0,0 +1,10 @@ +import Enum from '../enum' + +/** + * 枚举类:订单收货状态 + * ReceiptStatusEnum + */ +export default new Enum([ + { key: 'NOT_RECEIVED', name: '未收货', value: 10 }, + { key: 'RECEIVED', name: '已收货', value: 20 } +]) diff --git a/common/enum/order/index.js b/common/enum/order/index.js new file mode 100644 index 0000000..e8b7fa5 --- /dev/null +++ b/common/enum/order/index.js @@ -0,0 +1,17 @@ +import DeliveryStatusEnum from './DeliveryStatus' +import DeliveryTypeEnum from './DeliveryType' +import OrderSourceEnum from './OrderSource' +import OrderStatusEnum from './OrderStatus' +import PayStatusEnum from './PayStatus' +import PayTypeEnum from './PayType' +import ReceiptStatusEnum from './ReceiptStatus' + +export { + DeliveryStatusEnum, + DeliveryTypeEnum, + OrderSourceEnum, + OrderStatusEnum, + PayStatusEnum, + PayTypeEnum, + ReceiptStatusEnum +} diff --git a/common/enum/order/refund/AuditStatus.js b/common/enum/order/refund/AuditStatus.js new file mode 100644 index 0000000..2d9f5f0 --- /dev/null +++ b/common/enum/order/refund/AuditStatus.js @@ -0,0 +1,11 @@ +import Enum from '../../enum' + +/** + * 枚举类:商家审核状态 + * AuditStatusEnum + */ +export default new Enum([ + { key: 'WAIT', name: '待审核', value: 0 }, + { key: 'REVIEWED', name: '已同意', value: 10 }, + { key: 'REJECTED', name: '已拒绝', value: 20 } +]) diff --git a/common/enum/order/refund/RefundStatus.js b/common/enum/order/refund/RefundStatus.js new file mode 100644 index 0000000..78331b2 --- /dev/null +++ b/common/enum/order/refund/RefundStatus.js @@ -0,0 +1,12 @@ +import Enum from '../../enum' + +/** + * 枚举类:售后单状态 + * RefundStatusEnum + */ +export default new Enum([ + { key: 'NORMAL', name: '进行中', value: 0 }, + { key: 'REJECTED', name: '已拒绝', value: 10 }, + { key: 'COMPLETED', name: '已完成', value: 20 }, + { key: 'CANCELLED', name: '已取消', value: 30 } +]) diff --git a/common/enum/order/refund/RefundType.js b/common/enum/order/refund/RefundType.js new file mode 100644 index 0000000..63999cd --- /dev/null +++ b/common/enum/order/refund/RefundType.js @@ -0,0 +1,10 @@ +import Enum from '../../enum' + +/** + * 枚举类:售后类型 + * RefundTypeEnum + */ +export default new Enum([ + { key: 'RETURN', name: '退货退款', value: 10 }, + { key: 'EXCHANGE', name: '换货', value: 20 } +]) diff --git a/common/enum/order/refund/index.js b/common/enum/order/refund/index.js new file mode 100644 index 0000000..803b268 --- /dev/null +++ b/common/enum/order/refund/index.js @@ -0,0 +1,9 @@ +import AuditStatusEnum from './AuditStatus' +import RefundStatusEnum from './RefundStatus' +import RefundTypeEnum from './RefundType' + +export { + AuditStatusEnum, + RefundStatusEnum, + RefundTypeEnum +} diff --git a/common/enum/setting/Key.js b/common/enum/setting/Key.js new file mode 100644 index 0000000..319f127 --- /dev/null +++ b/common/enum/setting/Key.js @@ -0,0 +1,27 @@ +import Enum from '../enum' + +/** + * 枚举类:设置项索引 + * SettingKeyEnum + */ +export default new Enum([{ + key: 'REGISTER', + name: '账户注册设置', + value: 'register' + }, + { + key: 'PAGE_CATEGORY_TEMPLATE', + name: '分类页模板', + value: 'page_category_template' + }, + { + key: 'POINTS', + name: '积分设置', + value: 'points' + }, + { + key: 'RECHARGE', + name: '充值设置', + value: 'recharge' + } +]) diff --git a/common/enum/store/page/category/Style.js b/common/enum/store/page/category/Style.js new file mode 100644 index 0000000..c33c486 --- /dev/null +++ b/common/enum/store/page/category/Style.js @@ -0,0 +1,12 @@ +import Enum from '../../../enum' + +/** + * 枚举类:地址类型 + * PageCategoryStyleEnum + */ +export default new Enum([ + { key: 'ONE_LEVEL_BIG', name: '一级分类[大图]', value: 10 }, + { key: 'ONE_LEVEL_SMALL', name: '一级分类[小图]', value: 11 }, + { key: 'TWO_LEVEL', name: '二级分类', value: 20 }, + { key: 'COMMODITY', name: '一级分类+商品', value: 30 } +]) diff --git a/common/enum/store/page/category/index.js b/common/enum/store/page/category/index.js new file mode 100644 index 0000000..bf350e1 --- /dev/null +++ b/common/enum/store/page/category/index.js @@ -0,0 +1,3 @@ +import PageCategoryStyleEnum from './Style' + +export { PageCategoryStyleEnum } diff --git a/common/model/Region.js b/common/model/Region.js new file mode 100644 index 0000000..a495513 --- /dev/null +++ b/common/model/Region.js @@ -0,0 +1,57 @@ +import * as Api from '@/api/region' +import storage from '@/utils/storage' + +const REGION_TREE = 'region_tree' + +/** + * 商品分类 model类 + * RegionModel + */ +export default { + + // 从服务端获取全部地区数据(树状) + getTreeDataFromApi () { + return new Promise((resolve, reject) => { + Api.tree().then(result => resolve(result.data.list)) + }) + }, + + // 获取所有地区(树状) + getTreeData () { + return new Promise((resolve, reject) => { + // 判断缓存中是否存在 + const data = storage.get(REGION_TREE) + // 从服务端获取全部地区数据 + if (data) { + resolve(data) + } else { + this.getTreeDataFromApi().then(list => { + // 缓存24小时 + storage.set(REGION_TREE, list, 24 * 60 * 60) + resolve(list) + }) + } + }) + }, + + // 获取所有地区的总数 + getCitysCount () { + return new Promise((resolve, reject) => { + // 获取所有地区(树状) + this.getTreeData().then(data => { + const cityIds = [] + // 遍历省份 + for (const pidx in data) { + const province = data[pidx] + // 遍历城市 + for (const cidx in province.city) { + const cityItem = province.city[cidx] + cityIds.push(cityItem.id) + } + } + resolve(cityIds.length) + }) + }) + } + +} diff --git a/common/model/Setting.js b/common/model/Setting.js new file mode 100644 index 0000000..206b736 --- /dev/null +++ b/common/model/Setting.js @@ -0,0 +1,77 @@ +import Config from '@/core/config' +import storage from '@/utils/storage' +import * as SettingApi from '@/api/setting' + +const CACHE_KEY = 'Setting' +const OTHER = '_other' + +// 写入缓存, 到期时间10分钟 +const setStorage = (data) => { + const expireTime = 10 * 60 + storage.set(CACHE_KEY, data, expireTime) +} + +// 获取缓存中的数据 +const getStorage = () => { + return storage.get(CACHE_KEY) +} + +// 获取后端接口商城设置 (最新) +const getApiData = () => { + return new Promise((resolve, reject) => { + SettingApi.data() + .then(result => { + resolve(result.data.setting) + }) + }) +} + +/** + * 获取商城设置 + * 有缓存的情况下返回缓存, 没有缓存从后端api获取 + * @param {bool} isCache 是否从缓存中获取 [优点不用每次请求后端api 缺点后台更新设置后需等待时效性] + */ +const data = isCache => { + if (isCache == undefined) { + isCache = Config.get('enabledSettingCache') + } + return new Promise((resolve, reject) => { + const cacheData = getStorage() + if (isCache && cacheData) { + resolve(cacheData) + } else { + getApiData() + .then(data => { + setStorage(data) + resolve(data) + }) + } + }) +} + +// 获取商城设置(指定项) +const item = (key, isCache) => { + return new Promise((resolve, reject) => { + data(isCache) + .then(setting => { + resolve(setting[key]) + }) + }) +} + +// 获取H5端访问地址 +const h5Url = (isCache = false) => { + return new Promise((resolve, reject) => { + data(isCache) + .then(setting => { + const h5Url = setting[OTHER]['h5Url'] + resolve(h5Url) + }) + }) +} + +export default { + data, + item, + h5Url +} diff --git a/components/add-cart-btn/index.vue b/components/add-cart-btn/index.vue new file mode 100644 index 0000000..5ef4141 --- /dev/null +++ b/components/add-cart-btn/index.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/components/add-cart-popup/index.vue b/components/add-cart-popup/index.vue new file mode 100644 index 0000000..a171e9a --- /dev/null +++ b/components/add-cart-popup/index.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/components/avatar-image/index.vue b/components/avatar-image/index.vue new file mode 100644 index 0000000..f16e91b --- /dev/null +++ b/components/avatar-image/index.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/components/empty/index.vue b/components/empty/index.vue new file mode 100644 index 0000000..908823a --- /dev/null +++ b/components/empty/index.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/components/goods-sku-popup/index.vue b/components/goods-sku-popup/index.vue new file mode 100644 index 0000000..45d2503 --- /dev/null +++ b/components/goods-sku-popup/index.vue @@ -0,0 +1,1364 @@ + + + + + diff --git a/components/goods-sku-popup/number-box/index.vue b/components/goods-sku-popup/number-box/index.vue new file mode 100644 index 0000000..ab8d9aa --- /dev/null +++ b/components/goods-sku-popup/number-box/index.vue @@ -0,0 +1,450 @@ + + + + + diff --git a/components/mescroll-uni/components/mescroll-down.css b/components/mescroll-uni/components/mescroll-down.css new file mode 100644 index 0000000..72bf106 --- /dev/null +++ b/components/mescroll-uni/components/mescroll-down.css @@ -0,0 +1,55 @@ +/* 下拉刷新区域 */ +.mescroll-downwarp { + position: absolute; + top: -100%; + left: 0; + width: 100%; + height: 100%; + text-align: center; +} + +/* 下拉刷新--内容区,定位于区域底部 */ +.mescroll-downwarp .downwarp-content { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + min-height: 60rpx; + padding: 20rpx 0; + text-align: center; +} + +/* 下拉刷新--提示文本 */ +.mescroll-downwarp .downwarp-tip { + display: inline-block; + font-size: 28rpx; + vertical-align: middle; + margin-left: 16rpx; + /* color: gray; 已在style设置color,此处删去*/ +} + +/* 下拉刷新--旋转进度条 */ +.mescroll-downwarp .downwarp-progress { + display: inline-block; + width: 32rpx; + height: 32rpx; + border-radius: 50%; + border: 2rpx solid gray; + border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ + vertical-align: middle; +} + +/* 旋转动画 */ +.mescroll-downwarp .mescroll-rotate { + animation: mescrollDownRotate 0.6s linear infinite; +} + +@keyframes mescrollDownRotate { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/components/mescroll-uni/components/mescroll-down.vue b/components/mescroll-uni/components/mescroll-down.vue new file mode 100644 index 0000000..9fd1567 --- /dev/null +++ b/components/mescroll-uni/components/mescroll-down.vue @@ -0,0 +1,47 @@ + + + + + + diff --git a/components/mescroll-uni/components/mescroll-empty.vue b/components/mescroll-uni/components/mescroll-empty.vue new file mode 100644 index 0000000..51fdef8 --- /dev/null +++ b/components/mescroll-uni/components/mescroll-empty.vue @@ -0,0 +1,90 @@ + + + + + + diff --git a/components/mescroll-uni/components/mescroll-top.vue b/components/mescroll-uni/components/mescroll-top.vue new file mode 100644 index 0000000..5115fd8 --- /dev/null +++ b/components/mescroll-uni/components/mescroll-top.vue @@ -0,0 +1,83 @@ + + + + + + diff --git a/components/mescroll-uni/components/mescroll-up.css b/components/mescroll-uni/components/mescroll-up.css new file mode 100644 index 0000000..cbf48cd --- /dev/null +++ b/components/mescroll-uni/components/mescroll-up.css @@ -0,0 +1,47 @@ +/* 上拉加载区域 */ +.mescroll-upwarp { + box-sizing: border-box; + min-height: 110rpx; + padding: 30rpx 0; + text-align: center; + clear: both; +} + +/*提示文本 */ +.mescroll-upwarp .upwarp-tip, +.mescroll-upwarp .upwarp-nodata { + display: inline-block; + font-size: 28rpx; + vertical-align: middle; + /* color: gray; 已在style设置color,此处删去*/ +} + +.mescroll-upwarp .upwarp-tip { + margin-left: 16rpx; +} + +/*旋转进度条 */ +.mescroll-upwarp .upwarp-progress { + display: inline-block; + width: 32rpx; + height: 32rpx; + border-radius: 50%; + border: 2rpx solid gray; + border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ + vertical-align: middle; +} + +/* 旋转动画 */ +.mescroll-upwarp .mescroll-rotate { + animation: mescrollUpRotate 0.6s linear infinite; +} + +@keyframes mescrollUpRotate { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/components/mescroll-uni/components/mescroll-up.vue b/components/mescroll-uni/components/mescroll-up.vue new file mode 100644 index 0000000..11c2e1f --- /dev/null +++ b/components/mescroll-uni/components/mescroll-up.vue @@ -0,0 +1,39 @@ + + + + + + diff --git a/components/mescroll-uni/mescroll-body.css b/components/mescroll-uni/mescroll-body.css new file mode 100644 index 0000000..1107710 --- /dev/null +++ b/components/mescroll-uni/mescroll-body.css @@ -0,0 +1,19 @@ +.mescroll-body { + position: relative; /* 下拉刷新区域相对自身定位 */ + height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/ + overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */ + box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ +} + +/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */ +.mescroll-body.mescorll-sticky{ + overflow: unset !important +} + +/* 适配 iPhoneX */ +@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { + .mescroll-safearea { + padding-bottom: constant(safe-area-inset-bottom); + padding-bottom: env(safe-area-inset-bottom); + } +} \ No newline at end of file diff --git a/components/mescroll-uni/mescroll-body.vue b/components/mescroll-uni/mescroll-body.vue new file mode 100644 index 0000000..687d1bc --- /dev/null +++ b/components/mescroll-uni/mescroll-body.vue @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + diff --git a/components/mescroll-uni/mescroll-mixins.js b/components/mescroll-uni/mescroll-mixins.js new file mode 100644 index 0000000..71360c7 --- /dev/null +++ b/components/mescroll-uni/mescroll-mixins.js @@ -0,0 +1,65 @@ +// mescroll-body 和 mescroll-uni 通用 + +// import MescrollUni from "./mescroll-uni.vue"; +// import MescrollBody from "./mescroll-body.vue"; + +const MescrollMixin = { + // components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册 + // MescrollUni, + // MescrollBody + // }, + data() { + return { + mescroll: null //mescroll实例对象 + } + }, + // 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例) + onPullDownRefresh(){ + this.mescroll && this.mescroll.onPullDownRefresh(); + }, + // 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效) + onPageScroll(e) { + this.mescroll && this.mescroll.onPageScroll(e); + }, + // 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效) + onReachBottom() { + this.mescroll && this.mescroll.onReachBottom(); + }, + methods: { + // mescroll组件初始化的回调,可获取到mescroll对象 + mescrollInit(mescroll) { + this.mescroll = mescroll; + this.mescrollInitByRef(); // 兼容字节跳动小程序 + }, + // 以ref的方式初始化mescroll对象 (兼容字节跳动小程序) + mescrollInitByRef() { + if(!this.mescroll || !this.mescroll.resetUpScroll){ + let mescrollRef = this.$refs.mescrollRef; + if(mescrollRef) this.mescroll = mescrollRef.mescroll + } + }, + // 下拉刷新的回调 (mixin默认resetUpScroll) + downCallback() { + if(this.mescroll.optUp.use){ + this.mescroll.resetUpScroll() + }else{ + setTimeout(()=>{ + this.mescroll.endSuccess(); + }, 500) + } + }, + // 上拉加载的回调 + upCallback() { + // mixin默认延时500自动结束加载 + setTimeout(()=>{ + this.mescroll.endErr(); + }, 500) + } + }, + mounted() { + this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况 + } + +} + +export default MescrollMixin; diff --git a/components/mescroll-uni/mescroll-uni-option.js b/components/mescroll-uni/mescroll-uni-option.js new file mode 100644 index 0000000..f30c6d3 --- /dev/null +++ b/components/mescroll-uni/mescroll-uni-option.js @@ -0,0 +1,37 @@ +// 全局配置 +// mescroll-body 和 mescroll-uni 通用 +const GlobalOption = { + down: { + // 其他down的配置参数也可以写,这里只展示了常用的配置: + textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本 + textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本 + textLoading: '加载中 ...', // 加载中的提示文本 + textSuccess: '加载成功', // 加载成功的文本 + textErr: '加载失败', // 加载失败的文本 + beforeEndDelay: 100, // 延时结束的时长 (显示加载成功/失败的时长) + offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调 + native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例) + }, + up: { + // 其他up的配置参数也可以写,这里只展示了常用的配置: + textLoading: '加载中 ...', // 加载中的提示文本 + textNoMore: '亲, 没有更多了', // 没有更多数据的提示文本 + offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance ) + toTop: { + // 回到顶部按钮,需配置src才显示 + src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png ) + offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px + right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx) + bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx) + width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx) + }, + empty: { + use: true, // 是否显示空布局 + // icon: "https://www.mescroll.com/img/mescroll-empty.png", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png ) + icon: '/static/empty.png', + tip: '亲,暂无相关数据' // 提示 + } + } +} + +export default GlobalOption diff --git a/components/mescroll-uni/mescroll-uni.css b/components/mescroll-uni/mescroll-uni.css new file mode 100644 index 0000000..39438cd --- /dev/null +++ b/components/mescroll-uni/mescroll-uni.css @@ -0,0 +1,36 @@ +.mescroll-uni-warp{ + height: 100%; +} + +.mescroll-uni-content{ + height: 100%; +} + +.mescroll-uni { + position: relative; + width: 100%; + height: 100%; + min-height: 200rpx; + overflow-y: auto; + box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ +} + +/* 定位的方式固定高度 */ +.mescroll-uni-fixed{ + z-index: 1; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: auto; /* 使right生效 */ + height: auto; /* 使bottom生效 */ +} + +/* 适配 iPhoneX */ +@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { + .mescroll-safearea { + padding-bottom: constant(safe-area-inset-bottom); + padding-bottom: env(safe-area-inset-bottom); + } +} diff --git a/components/mescroll-uni/mescroll-uni.js b/components/mescroll-uni/mescroll-uni.js new file mode 100644 index 0000000..01bdbb6 --- /dev/null +++ b/components/mescroll-uni/mescroll-uni.js @@ -0,0 +1,799 @@ +/* mescroll + * version 1.3.3 + * 2020-09-15 wenju + * https://www.mescroll.com + */ + +export default function MeScroll(options, isScrollBody) { + let me = this; + me.version = '1.3.3'; // mescroll版本号 + me.options = options || {}; // 配置 + me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view + + me.isDownScrolling = false; // 是否在执行下拉刷新的回调 + me.isUpScrolling = false; // 是否在执行上拉加载的回调 + let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback + + // 初始化下拉刷新 + me.initDownScroll(); + // 初始化上拉加载,则初始化 + me.initUpScroll(); + + // 自动加载 + setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例 + // 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新) + if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) { + if (me.optDown.autoShowLoading) { + me.triggerDownScroll(); // 显示下拉进度,执行下拉回调 + } else { + me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调 + } + } + // 自动触发上拉加载 + if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback + setTimeout(function(){ + me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll(); + },100) + } + }, 30); // 需让me.optDown.inited和me.optUp.inited先执行 +} + +/* 配置参数:下拉刷新 */ +MeScroll.prototype.extendDownScroll = function(optDown) { + // 下拉刷新的配置 + MeScroll.extend(optDown, { + use: true, // 是否启用下拉刷新; 默认true + auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true + native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例) + autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false + isLock: false, // 是否锁定下拉刷新,默认false; + offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调 + startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差 + inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉 + outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉 + bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行 + minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突; + textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本 + textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本 + textLoading: '加载中 ...', // 加载中的提示文本 + textSuccess: '加载成功', // 加载成功的文本 + textErr: '加载失败', // 加载失败的文本 + beforeEndDelay: 100, // 延时结束的时长 (显示加载成功/失败的时长) + bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop) + textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色) + inited: null, // 下拉刷新初始化完毕的回调 + inOffset: null, // 下拉的距离进入offset范围内那一刻的回调 + outOffset: null, // 下拉的距离大于offset那一刻的回调 + onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度 + beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】 + showLoading: null, // 显示下拉刷新进度的回调 + afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用) + beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】 + endDownScroll: null, // 结束下拉刷新的回调 + afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用) + callback: function(mescroll) { + // 下拉刷新的回调;默认重置上拉加载列表为第一页 + mescroll.resetUpScroll(); + } + }) +} + +/* 配置参数:上拉加载 */ +MeScroll.prototype.extendUpScroll = function(optUp) { + // 上拉加载的配置 + MeScroll.extend(optUp, { + use: true, // 是否启用上拉加载; 默认true + auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true + isLock: false, // 是否锁定上拉加载,默认false; + isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发; + callback: null, // 上拉加载的回调;function(page,mescroll){ } + page: { + num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始 + size: 10, // 每页数据的数量 + time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复; + }, + noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看 + offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance ) + textLoading: '加载中 ...', // 加载中的提示文本 + textNoMore: '-- END --', // 没有更多数据的提示文本 + bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom) + textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色) + inited: null, // 初始化完毕的回调 + showLoading: null, // 显示加载中的回调 + showNoMore: null, // 显示无更多数据的回调 + hideUpScroll: null, // 隐藏上拉加载的回调 + errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效 + toTop: { + // 回到顶部按钮,需配置src才显示 + src: null, // 图片路径,默认null (绝对路径或网络图) + offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000 + duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项) + btnClick: null, // 点击按钮的回调 + onShow: null, // 是否显示的回调 + zIndex: 9990, // fixed定位z-index值 + left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx) + right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx) + bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx) + safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值) + width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx) + radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx) + }, + empty: { + use: true, // 是否显示空布局 + icon: null, // 图标路径 + tip: '~ 暂无相关数据 ~', // 提示 + btnText: '', // 按钮 + btnClick: null, // 点击按钮的回调 + onShow: null, // 是否显示的回调 + fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute) + top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx") + zIndex: 99 // fixed定位z-index值 + }, + onScroll: false // 是否监听滚动事件 + }) +} + +/* 配置参数 */ +MeScroll.extend = function(userOption, defaultOption) { + if (!userOption) return defaultOption; + for (let key in defaultOption) { + if (userOption[key] == null) { + let def = defaultOption[key]; + if (def != null && typeof def === 'object') { + userOption[key] = MeScroll.extend({}, def); // 深度匹配 + } else { + userOption[key] = def; + } + } else if (typeof userOption[key] === 'object') { + MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配 + } + } + return userOption; +} + +/* 简单判断是否配置了颜色 (非透明,非白色) */ +MeScroll.prototype.hasColor = function(color) { + if(!color) return false; + let c = color.toLowerCase(); + return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white" +} + +/* -------初始化下拉刷新------- */ +MeScroll.prototype.initDownScroll = function() { + let me = this; + // 配置参数 + me.optDown = me.options.down || {}; + if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色 + me.extendDownScroll(me.optDown); + + // 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新 + if(me.isScrollBody && me.optDown.native){ + me.optDown.use = false + }else{ + me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持 + } + + me.downHight = 0; // 下拉区域的高度 + + // 在页面中加入下拉布局 + if (me.optDown.use && me.optDown.inited) { + // 初始化完毕的回调 + setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例 + me.optDown.inited(me); + }, 0) + } +} + +/* 列表touchstart事件 */ +MeScroll.prototype.touchstartEvent = function(e) { + if (!this.optDown.use) return; + + this.startPoint = this.getPoint(e); // 记录起点 + this.startTop = this.getScrollTop(); // 记录此时的滚动条位置 + this.startAngle = 0; // 初始角度 + this.lastPoint = this.startPoint; // 重置上次move的点 + this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况) + this.inTouchend = false; // 标记不是touchend +} + +/* 列表touchmove事件 */ +MeScroll.prototype.touchmoveEvent = function(e) { + if (!this.optDown.use) return; + let me = this; + + let scrollTop = me.getScrollTop(); // 当前滚动条的距离 + let curPoint = me.getPoint(e); // 当前点 + + let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 + + // 向下拉 && 在顶部 + // mescroll-body,直接判定在顶部即可 + // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove + // scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等 + if (moveY > 0 && ( + (me.isScrollBody && scrollTop <= 0) + || + (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) ) + )) { + // 可下拉的条件 + if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && + me.optUp.isBoth))) { + + // 下拉的初始角度是否在配置的范围内 + if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90] + if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新 + + // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发 + if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) { + me.inTouchend = true; // 标记执行touchend + me.touchendEvent(); // 提前触发touchend + return; + } + + me.preventDefault(e); // 阻止默认事件 + + let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上) + + // 下拉距离 < 指定距离 + if (me.downHight < me.optDown.offset) { + if (me.movetype !== 1) { + me.movetype = 1; // 加入标记,保证只执行一次 + me.isDownEndSuccess = null; // 重置是否加载成功的状态 (wxs执行的是wxs.wxs) + me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次 + me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 + } + me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小 + + // 指定距离 <= 下拉距离 + } else { + if (me.movetype !== 2) { + me.movetype = 2; // 加入标记,保证只执行一次 + me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次 + me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 + } + if (diff > 0) { // 向下拉 + me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小 + } else { // 向上收 + me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度 + } + } + + me.downHight = Math.round(me.downHight) // 取整 + let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值 + me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行 + } + } + + me.lastPoint = curPoint; // 记录本次移动的点 +} + +/* 列表touchend事件 */ +MeScroll.prototype.touchendEvent = function(e) { + if (!this.optDown.use) return; + // 如果下拉区域高度已改变,则需重置回来 + if (this.isMoveDown) { + if (this.downHight >= this.optDown.offset) { + // 符合触发刷新的条件 + this.triggerDownScroll(); + } else { + // 不符合的话 则重置 + this.downHight = 0; + this.endDownScrollCall(this); + } + this.movetype = 0; + this.isMoveDown = false; + } else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件 + let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 + // 上滑 + if (isScrollUp) { + // 需检查滑动的角度 + let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90] + if (angle > 80) { + // 检查并触发上拉 + this.triggerUpScroll(true); + } + } + } +} + +/* 根据点击滑动事件获取第一个手指的坐标 */ +MeScroll.prototype.getPoint = function(e) { + if (!e) { + return { + x: 0, + y: 0 + } + } + if (e.touches && e.touches[0]) { + return { + x: e.touches[0].pageX, + y: e.touches[0].pageY + } + } else if (e.changedTouches && e.changedTouches[0]) { + return { + x: e.changedTouches[0].pageX, + y: e.changedTouches[0].pageY + } + } else { + return { + x: e.clientX, + y: e.clientY + } + } +} + +/* 计算两点之间的角度: 区间 [0,90]*/ +MeScroll.prototype.getAngle = function(p1, p2) { + let x = Math.abs(p1.x - p2.x); + let y = Math.abs(p1.y - p2.y); + let z = Math.sqrt(x * x + y * y); + let angle = 0; + if (z !== 0) { + angle = Math.asin(y / z) / Math.PI * 180; + } + return angle +} + +/* 触发下拉刷新 */ +MeScroll.prototype.triggerDownScroll = function() { + if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) { + //return true则处于完全自定义状态 + } else { + this.showDownScroll(); // 下拉刷新中... + !this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据 + } +} + +/* 显示下拉进度布局 */ +MeScroll.prototype.showDownScroll = function() { + this.isDownScrolling = true; // 标记下拉中 + if (this.optDown.native) { + uni.startPullDownRefresh(); // 系统自带的下拉刷新 + this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到 + } else{ + this.downHight = this.optDown.offset; // 更新下拉区域高度 + this.showDownLoadingCall(this.downHight); // 下拉刷新中... + } +} + +MeScroll.prototype.showDownLoadingCall = function(downHight) { + this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中... + this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码 +} + +/* 显示系统自带的下拉刷新时需要处理的业务 */ +MeScroll.prototype.onPullDownRefresh = function() { + this.isDownScrolling = true; // 标记下拉中 + this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到 + this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据 +} + +/* 结束下拉刷新 */ +MeScroll.prototype.endDownScroll = function() { + if (this.optDown.native) { // 结束原生下拉刷新 + this.isDownScrolling = false; + this.endDownScrollCall(this); + uni.stopPullDownRefresh(); + return + } + let me = this; + // 结束下拉刷新的方法 + let endScroll = function() { + me.downHight = 0; + me.isDownScrolling = false; + me.endDownScrollCall(me); + if(!me.isScrollBody){ + me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页 + me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响 + } + } + // 结束下拉刷新时的回调 + let delay = 0; + if (me.optDown.beforeEndDownScroll) { + delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms + if(me.isDownEndSuccess == null) delay = 0; // 没有执行加载中,则不延时 + } + if (typeof delay === 'number' && delay > 0) { + setTimeout(endScroll, delay); + } else { + endScroll(); + } +} + +MeScroll.prototype.endDownScrollCall = function() { + this.optDown.endDownScroll && this.optDown.endDownScroll(this); + this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this); +} + +/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */ +MeScroll.prototype.lockDownScroll = function(isLock) { + if (isLock == null) isLock = true; + this.optDown.isLock = isLock; +} + +/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */ +MeScroll.prototype.lockUpScroll = function(isLock) { + if (isLock == null) isLock = true; + this.optUp.isLock = isLock; +} + +/* -------初始化上拉加载------- */ +MeScroll.prototype.initUpScroll = function() { + let me = this; + // 配置参数 + me.optUp = me.options.up || {use: false} + if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色 + me.extendUpScroll(me.optUp); + + if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局 + me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页 + me.startNum = me.optUp.page.num + 1; // 记录page开始的页码 + + // 初始化完毕的回调 + if (me.optUp.inited) { + setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例 + me.optUp.inited(me); + }, 0) + } +} + +/*滚动到底部的事件 (仅mescroll-body生效)*/ +MeScroll.prototype.onReachBottom = function() { + if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom + if (!this.optUp.isLock && this.optUp.hasNext) { + this.triggerUpScroll(); + } + } +} + +/*列表滚动事件 (仅mescroll-body生效)*/ +MeScroll.prototype.onPageScroll = function(e) { + if (!this.isScrollBody) return; + + // 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部) + this.setScrollTop(e.scrollTop); + + // 顶部按钮的显示隐藏 + if (e.scrollTop >= this.optUp.toTop.offset) { + this.showTopBtn(); + } else { + this.hideTopBtn(); + } +} + +/*列表滚动事件*/ +MeScroll.prototype.scroll = function(e, onScroll) { + // 更新滚动条的位置 + this.setScrollTop(e.scrollTop); + // 更新滚动内容高度 + this.setScrollHeight(e.scrollHeight); + + // 向上滑还是向下滑动 + if (this.preScrollY == null) this.preScrollY = 0; + this.isScrollUp = e.scrollTop - this.preScrollY > 0; + this.preScrollY = e.scrollTop; + + // 上滑 && 检查并触发上拉 + this.isScrollUp && this.triggerUpScroll(true); + + // 顶部按钮的显示隐藏 + if (e.scrollTop >= this.optUp.toTop.offset) { + this.showTopBtn(); + } else { + this.hideTopBtn(); + } + + // 滑动监听 + this.optUp.onScroll && onScroll && onScroll() +} + +/* 触发上拉加载 */ +MeScroll.prototype.triggerUpScroll = function(isCheck) { + if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) { + // 是否校验在底部; 默认不校验 + if (isCheck === true) { + let canUp = false; + // 还有下一页 && 没有锁定 && 不在下拉中 + if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) { + if (this.getScrollBottom() <= this.optUp.offset) { // 到底部 + canUp = true; // 标记可上拉 + } + } + if (canUp === false) return; + } + this.showUpScroll(); // 上拉加载中... + this.optUp.page.num++; // 预先加一页,如果失败则减回 + this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调 + this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响 + this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响 + this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响 + this.optUp.callback(this); // 执行回调,联网加载数据 + } +} + +/* 显示上拉加载中 */ +MeScroll.prototype.showUpScroll = function() { + this.isUpScrolling = true; // 标记上拉加载中 + this.optUp.showLoading && this.optUp.showLoading(this); // 回调 +} + +/* 显示上拉无更多数据 */ +MeScroll.prototype.showNoMore = function() { + this.optUp.hasNext = false; // 标记无更多数据 + this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调 +} + +/* 隐藏上拉区域**/ +MeScroll.prototype.hideUpScroll = function() { + this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调 +} + +/* 结束上拉加载 */ +MeScroll.prototype.endUpScroll = function(isShowNoMore) { + if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用 + if (isShowNoMore) { + this.showNoMore(); // isShowNoMore=true,显示无更多数据 + } else { + this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载 + } + } + this.isUpScrolling = false; // 标记结束上拉加载 +} + +/* 重置上拉加载列表为第一页 + *isShowLoading 是否显示进度布局; + * 1.默认null,不传参,则显示上拉加载的进度布局 + * 2.传参true, 则显示下拉刷新的进度布局 + * 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据) + */ +MeScroll.prototype.resetUpScroll = function(isShowLoading) { + if (this.optUp && this.optUp.use) { + let page = this.optUp.page; + this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回 + this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回 + page.num = this.startNum; // 重置为第一页 + page.time = null; // 重置时间为空 + if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度; + if (isShowLoading == null) { + this.removeEmpty(); // 移除空布局 + this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局 + } else { + this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表 + } + } + this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调 + this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响 + this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响 + this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响 + this.optUp.callback && this.optUp.callback(this); // 执行上拉回调 + } +} + +/* 设置page.num的值 */ +MeScroll.prototype.setPageNum = function(num) { + this.optUp.page.num = num - 1; +} + +/* 设置page.size的值 */ +MeScroll.prototype.setPageSize = function(size) { + this.optUp.page.size = size; +} + +/* 联网回调成功,结束下拉刷新和上拉加载 + * dataSize: 当前页的数据量(必传) + * totalPage: 总页数(必传) + * systime: 服务器时间 (可空) + */ +MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) { + let hasNext; + if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页 + this.endSuccess(dataSize, hasNext, systime); +} + +/* 联网回调成功,结束下拉刷新和上拉加载 + * dataSize: 当前页的数据量(必传) + * totalSize: 列表所有数据总数量(必传) + * systime: 服务器时间 (可空) + */ +MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) { + let hasNext; + if (this.optUp.use && totalSize != null) { + let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数 + hasNext = loadSize < totalSize; // 是否还有下一页 + } + this.endSuccess(dataSize, hasNext, systime); +} + +/* 联网回调成功,结束下拉刷新和上拉加载 + * dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页 + * hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据. + * systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录 + */ +MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) { + let me = this; + // 结束下拉刷新 + if (me.isDownScrolling) { + me.isDownEndSuccess = true + me.endDownScroll(); + } + + // 结束上拉加载 + if (me.optUp.use) { + let isShowNoMore; // 是否已无更多数据 + if (dataSize != null) { + let pageNum = me.optUp.page.num; // 当前页码 + let pageSize = me.optUp.page.size; // 每页长度 + // 如果是第一页 + if (pageNum === 1) { + if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间 + } + if (dataSize < pageSize || hasNext === false) { + // 返回的数据不满一页时,则说明已无更多数据 + me.optUp.hasNext = false; + if (dataSize === 0 && pageNum === 1) { + // 如果第一页无任何数据且配置了空布局 + isShowNoMore = false; + me.showEmpty(); + } else { + // 总列表数少于配置的数量,则不显示无更多数据 + let allDataSize = (pageNum - 1) * pageSize + dataSize; + if (allDataSize < me.optUp.noMoreSize) { + isShowNoMore = false; + } else { + isShowNoMore = true; + } + me.removeEmpty(); // 移除空布局 + } + } else { + // 还有下一页 + isShowNoMore = false; + me.optUp.hasNext = true; + me.removeEmpty(); // 移除空布局 + } + } + + // 隐藏上拉 + me.endUpScroll(isShowNoMore); + } +} + +/* 回调失败,结束下拉刷新和上拉加载 */ +MeScroll.prototype.endErr = function(errDistance) { + // 结束下拉,回调失败重置回原来的页码和时间 + if (this.isDownScrolling) { + this.isDownEndSuccess = false + let page = this.optUp.page; + if (page && this.prePageNum) { + page.num = this.prePageNum; + page.time = this.prePageTime; + } + this.endDownScroll(); + } + // 结束上拉,回调失败重置回原来的页码 + if (this.isUpScrolling) { + this.optUp.page.num--; + this.endUpScroll(false); + // 如果是mescroll-body,则需往回滚一定距离 + if(this.isScrollBody && errDistance !== 0){ // 不处理0 + if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认 + this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离 + } + } +} + +/* 显示空布局 */ +MeScroll.prototype.showEmpty = function() { + this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true) +} + +/* 移除空布局 */ +MeScroll.prototype.removeEmpty = function() { + this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false) +} + +/* 显示回到顶部的按钮 */ +MeScroll.prototype.showTopBtn = function() { + if (!this.topBtnShow) { + this.topBtnShow = true; + this.optUp.toTop.onShow && this.optUp.toTop.onShow(true); + } +} + +/* 隐藏回到顶部的按钮 */ +MeScroll.prototype.hideTopBtn = function() { + if (this.topBtnShow) { + this.topBtnShow = false; + this.optUp.toTop.onShow && this.optUp.toTop.onShow(false); + } +} + +/* 获取滚动条的位置 */ +MeScroll.prototype.getScrollTop = function() { + return this.scrollTop || 0 +} + +/* 记录滚动条的位置 */ +MeScroll.prototype.setScrollTop = function(y) { + this.scrollTop = y; +} + +/* 滚动到指定位置 */ +MeScroll.prototype.scrollTo = function(y, t) { + this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法 +} + +/* 自定义scrollTo */ +MeScroll.prototype.resetScrollTo = function(myScrollTo) { + this.myScrollTo = myScrollTo +} + +/* 滚动条到底部的距离 */ +MeScroll.prototype.getScrollBottom = function() { + return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop() +} + +/* 计步器 + star: 开始值 + end: 结束值 + callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器; + t: 计步时长,传0则直接回调end值;不传则默认300ms + rate: 周期;不传则默认30ms计步一次 + * */ +MeScroll.prototype.getStep = function(star, end, callback, t, rate) { + let diff = end - star; // 差值 + if (t === 0 || diff === 0) { + callback && callback(end); + return; + } + t = t || 300; // 时长 300ms + rate = rate || 30; // 周期 30ms + let count = t / rate; // 次数 + let step = diff / count; // 步长 + let i = 0; // 计数 + let timer = setInterval(function() { + if (i < count - 1) { + star += step; + callback && callback(star, timer); + i++; + } else { + callback && callback(end, timer); // 最后一次直接设置end,避免计算误差 + clearInterval(timer); + } + }, rate); +} + +/* 滚动容器的高度 */ +MeScroll.prototype.getClientHeight = function(isReal) { + let h = this.clientHeight || 0 + if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差) + h = this.getBodyHeight() + } + return h +} +MeScroll.prototype.setClientHeight = function(h) { + this.clientHeight = h; +} + +/* 滚动内容的高度 */ +MeScroll.prototype.getScrollHeight = function() { + return this.scrollHeight || 0; +} +MeScroll.prototype.setScrollHeight = function(h) { + this.scrollHeight = h; +} + +/* body的高度 */ +MeScroll.prototype.getBodyHeight = function() { + return this.bodyHeight || 0; +} +MeScroll.prototype.setBodyHeight = function(h) { + this.bodyHeight = h; +} + +/* 阻止浏览器默认滚动事件 */ +MeScroll.prototype.preventDefault = function(e) { + // 小程序不支持e.preventDefault, 已在wxs中禁止 + // app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止 + // cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用 + if (e && e.cancelable && !e.defaultPrevented) e.preventDefault() +} \ No newline at end of file diff --git a/components/mescroll-uni/mescroll-uni.vue b/components/mescroll-uni/mescroll-uni.vue new file mode 100644 index 0000000..402c995 --- /dev/null +++ b/components/mescroll-uni/mescroll-uni.vue @@ -0,0 +1,424 @@ + + + + + + + + + + + + + + + diff --git a/components/mescroll-uni/mixins/mescroll-comp.js b/components/mescroll-uni/mixins/mescroll-comp.js new file mode 100644 index 0000000..b504894 --- /dev/null +++ b/components/mescroll-uni/mixins/mescroll-comp.js @@ -0,0 +1,48 @@ +/** + * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期 + */ +const MescrollCompMixin = { + // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级) + onPageScroll(e) { + this.handlePageScroll(e) + }, + onReachBottom() { + this.handleReachBottom() + }, + // 当down的native: true时, 还需传递此方法进到子组件 + onPullDownRefresh(){ + this.handlePullDownRefresh() + }, + // mescroll-body写在子子子...组件的情况 (多级) + data() { + return { + mescroll: { + onPageScroll: e=>{ + this.handlePageScroll(e) + }, + onReachBottom: ()=>{ + this.handleReachBottom() + }, + onPullDownRefresh: ()=>{ + this.handlePullDownRefresh() + } + } + } + }, + methods:{ + handlePageScroll(e){ + let item = this.$refs["mescrollItem"]; + if(item && item.mescroll) item.mescroll.onPageScroll(e); + }, + handleReachBottom(){ + let item = this.$refs["mescrollItem"]; + if(item && item.mescroll) item.mescroll.onReachBottom(); + }, + handlePullDownRefresh(){ + let item = this.$refs["mescrollItem"]; + if(item && item.mescroll) item.mescroll.onPullDownRefresh(); + } + } +} + +export default MescrollCompMixin; diff --git a/components/mescroll-uni/mixins/mescroll-more-item.js b/components/mescroll-uni/mixins/mescroll-more-item.js new file mode 100644 index 0000000..5cb920b --- /dev/null +++ b/components/mescroll-uni/mixins/mescroll-more-item.js @@ -0,0 +1,59 @@ +/** + * mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例) + */ +const MescrollMoreItemMixin = { + // 支付宝小程序不支持props的mixin,需写在具体的页面中 + // #ifndef MP-ALIPAY || MP-DINGTALK + props:{ + i: Number, // 每个tab页的专属下标 + index: { // 当前tab的下标 + type: Number, + default(){ + return 0 + } + } + }, + // #endif + data() { + return { + downOption:{ + auto:false // 不自动加载 + }, + upOption:{ + auto:false // 不自动加载 + }, + isInit: false // 当前tab是否已初始化 + } + }, + watch:{ + // 监听下标的变化 + index(val){ + if (this.i === val && !this.isInit) { + this.isInit = true; // 标记为true + this.mescroll && this.mescroll.triggerDownScroll(); + } + } + }, + methods: { + // 以ref的方式初始化mescroll对象 (兼容字节跳动小程序) + mescrollInitByRef() { + if(!this.mescroll || !this.mescroll.resetUpScroll){ + // 字节跳动小程序编辑器不支持一个页面存在相同的ref, 多mescroll的ref需动态生成, 格式为'mescrollRef下标' + let mescrollRef = this.$refs.mescrollRef || this.$refs['mescrollRef'+this.i]; + if(mescrollRef) this.mescroll = mescrollRef.mescroll + } + }, + // mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit) + mescrollInit(mescroll) { + this.mescroll = mescroll; + this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 + // 自动加载当前tab的数据 + if(this.i === this.index){ + this.isInit = true; // 标记为true + this.mescroll.triggerDownScroll(); + } + }, + } +} + +export default MescrollMoreItemMixin; diff --git a/components/mescroll-uni/mixins/mescroll-more.js b/components/mescroll-uni/mixins/mescroll-more.js new file mode 100644 index 0000000..d765088 --- /dev/null +++ b/components/mescroll-uni/mixins/mescroll-more.js @@ -0,0 +1,74 @@ +/** + * mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期 + */ +const MescrollMoreMixin = { + data() { + return { + tabIndex: 0, // 当前tab下标 + mescroll: { + onPageScroll: e=>{ + this.handlePageScroll(e) + }, + onReachBottom: ()=>{ + this.handleReachBottom() + }, + onPullDownRefresh: ()=>{ + this.handlePullDownRefresh() + } + } + } + }, + // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 + onPageScroll(e) { + this.handlePageScroll(e) + }, + onReachBottom() { + this.handleReachBottom() + }, + // 当down的native: true时, 还需传递此方法进到子组件 + onPullDownRefresh(){ + this.handlePullDownRefresh() + }, + methods:{ + handlePageScroll(e){ + let mescroll = this.getMescroll(this.tabIndex); + mescroll && mescroll.onPageScroll(e); + }, + handleReachBottom(){ + let mescroll = this.getMescroll(this.tabIndex); + mescroll && mescroll.onReachBottom(); + }, + handlePullDownRefresh(){ + let mescroll = this.getMescroll(this.tabIndex); + mescroll && mescroll.onPullDownRefresh(); + }, + // 根据下标获取对应子组件的mescroll + getMescroll(i){ + if(!this.mescrollItems) this.mescrollItems = []; + if(!this.mescrollItems[i]) { + // v-for中的refs + let vForItem = this.$refs["mescrollItem"]; + if(vForItem){ + this.mescrollItems[i] = vForItem[i] + }else{ + // 普通的refs,不可重复 + this.mescrollItems[i] = this.$refs["mescrollItem"+i]; + } + } + let item = this.mescrollItems[i] + return item ? item.mescroll : null + }, + // 切换tab,恢复滚动条位置 + tabChange(i){ + let mescroll = this.getMescroll(i); + if(mescroll){ + // 延时(比$nextTick靠谱一些),确保元素已渲染 + setTimeout(()=>{ + mescroll.scrollTo(mescroll.getScrollTop(),0) + },30) + } + } + } +} + +export default MescrollMoreMixin; diff --git a/components/mescroll-uni/wxs/mixins.js b/components/mescroll-uni/wxs/mixins.js new file mode 100644 index 0000000..34ffa3c --- /dev/null +++ b/components/mescroll-uni/wxs/mixins.js @@ -0,0 +1,109 @@ +// 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信 +const WxsMixin = { + data() { + return { + // 传入wxs视图层的数据 (响应式) + wxsProp: { + optDown:{}, // 下拉刷新的配置 + scrollTop:0, // 滚动条的距离 + bodyHeight:0, // body的高度 + isDownScrolling:false, // 是否正在下拉刷新中 + isUpScrolling:false, // 是否正在上拉加载中 + isScrollBody:true, // 是否为mescroll-body滚动 + isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新 + t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer) + }, + + // 标记调用wxs视图层的方法 + callProp: { + callType: '', // 方法名 + t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer) + }, + + // 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs) + // #ifndef MP-WEIXIN || MP-QQ || APP-PLUS || H5 + wxsBiz: { + //注册列表touchstart事件,用于下拉刷新 + touchstartEvent: e=> { + this.mescroll.touchstartEvent(e); + }, + //注册列表touchmove事件,用于下拉刷新 + touchmoveEvent: e=> { + this.mescroll.touchmoveEvent(e); + }, + //注册列表touchend事件,用于下拉刷新 + touchendEvent: e=> { + this.mescroll.touchendEvent(e); + }, + propObserver(){}, // 抹平wxs的写法 + callObserver(){} // 抹平wxs的写法 + }, + // #endif + + // 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js) + // #ifndef APP-PLUS || H5 + renderBiz: { + propObserver(){} // 抹平renderjs的写法 + } + // #endif + } + }, + methods: { + // wxs视图层调用逻辑层的回调 + wxsCall(msg){ + if(msg.type === 'setWxsProp'){ + // 更新wxsProp数据 (值改变才触发更新) + this.wxsProp = { + optDown: this.mescroll.optDown, + scrollTop: this.mescroll.getScrollTop(), + bodyHeight: this.mescroll.getBodyHeight(), + isDownScrolling: this.mescroll.isDownScrolling, + isUpScrolling: this.mescroll.isUpScrolling, + isUpBoth: this.mescroll.optUp.isBoth, + isScrollBody:this.mescroll.isScrollBody, + t: Date.now() + } + }else if(msg.type === 'setLoadType'){ + // 设置inOffset,outOffset的状态 + this.downLoadType = msg.downLoadType + // 状态挂载到mescroll对象, 以便在其他组件中使用, 比如中 + this.$set(this.mescroll, 'downLoadType', this.downLoadType) + // 重置是否加载成功的状态 + this.$set(this.mescroll, 'isDownEndSuccess', null) + }else if(msg.type === 'triggerDownScroll'){ + // 主动触发下拉刷新 + this.mescroll.triggerDownScroll(); + }else if(msg.type === 'endDownScroll'){ + // 结束下拉刷新 + this.mescroll.endDownScroll(); + }else if(msg.type === 'triggerUpScroll'){ + // 主动触发上拉加载 + this.mescroll.triggerUpScroll(true); + } + } + }, + mounted() { + // #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 + // 配置主动触发wxs显示加载进度的回调 + this.mescroll.optDown.afterLoading = ()=>{ + this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新) + } + // 配置主动触发wxs隐藏加载进度的回调 + this.mescroll.optDown.afterEndDownScroll = ()=>{ + this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新) + let delay = 300 + (this.mescroll.optDown.beforeEndDelay || 0) + setTimeout(()=>{ + if(this.downLoadType === 4 || this.downLoadType === 0){ + this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新) + } + // 状态挂载到mescroll对象, 以便在其他组件中使用, 比如中 + this.$set(this.mescroll, 'downLoadType', this.downLoadType) + }, delay) + } + // 初始化wxs的数据 + this.wxsCall({type: 'setWxsProp'}) + // #endif + } +} + +export default WxsMixin; diff --git a/components/mescroll-uni/wxs/renderjs.js b/components/mescroll-uni/wxs/renderjs.js new file mode 100644 index 0000000..207f388 --- /dev/null +++ b/components/mescroll-uni/wxs/renderjs.js @@ -0,0 +1,92 @@ +// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce +// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止) +// https://uniapp.dcloud.io/frame?id=renderjs + +// 与wxs的me实例一致 +var me = {} + +// 初始化window对象的touch事件 (仅初始化一次) +if(window && !window.$mescrollRenderInit){ + window.$mescrollRenderInit = true + + + window.addEventListener('touchstart', function(e){ + if (me.disabled()) return; + me.startPoint = me.getPoint(e); // 记录起点 + }, {passive: true}) + + + window.addEventListener('touchmove', function(e){ + if (me.disabled()) return; + if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce + + var curPoint = me.getPoint(e); // 当前点 + var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 + // 向下拉 + if (moveY > 0) { + // 可下拉的条件 + if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) { + + // 只有touch在mescroll的view上面,才禁止bounce + var el = e.target; + var isMescrollTouch = false; + while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") { + var cls = el.classList; + if (cls && cls.contains('mescroll-render-touch')) { + isMescrollTouch = true + break; + } + el = el.parentNode; // 继续检查其父元素 + } + // 禁止bounce (不会对swiper和iOS侧滑返回造成影响) + if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault(); + } + } + }, {passive: false}) +} + +/* 获取滚动条的位置 */ +me.getScrollTop = function() { + return me.scrollTop || 0 +} + +/* 是否禁用下拉刷新 */ +me.disabled = function(){ + return !me.optDown || !me.optDown.use || me.optDown.native +} + +/* 根据点击滑动事件获取第一个手指的坐标 */ +me.getPoint = function(e) { + if (!e) { + return {x: 0,y: 0} + } + if (e.touches && e.touches[0]) { + return {x: e.touches[0].pageX,y: e.touches[0].pageY} + } else if (e.changedTouches && e.changedTouches[0]) { + return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY} + } else { + return {x: e.clientX,y: e.clientY} + } +} + +/** + * 监听逻辑层数据的变化 (实时更新数据) + */ +function propObserver(wxsProp) { + me.optDown = wxsProp.optDown + me.scrollTop = wxsProp.scrollTop + me.isDownScrolling = wxsProp.isDownScrolling + me.isUpScrolling = wxsProp.isUpScrolling + me.isUpBoth = wxsProp.isUpBoth +} + +/* 导出模块 */ +const renderBiz = { + data() { + return { + propObserver: propObserver, + } + } +} + +export default renderBiz; \ No newline at end of file diff --git a/components/mescroll-uni/wxs/wxs.wxs b/components/mescroll-uni/wxs/wxs.wxs new file mode 100644 index 0000000..3fb4ad9 --- /dev/null +++ b/components/mescroll-uni/wxs/wxs.wxs @@ -0,0 +1,268 @@ +// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响 +// https://uniapp.dcloud.io/frame?id=wxs +// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html + +// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致 +var me = {} + +// ------ 自定义下拉刷新动画 start ------ + +/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */ +me.onMoving = function (ins, rate, downHight){ + ins.requestAnimationFrame(function () { + ins.selectComponent('.mescroll-wxs-content').setStyle({ + 'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题 + 'transform': 'translateY(' + downHight + 'px)', + 'transition': '' + }) + // 环形进度条 + var progress = ins.selectComponent('.mescroll-wxs-progress') + progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'}) + }) +} + +/* 显示下拉刷新进度 */ +me.showLoading = function (ins){ + me.downHight = me.optDown.offset + ins.requestAnimationFrame(function () { + ins.selectComponent('.mescroll-wxs-content').setStyle({ + 'will-change': 'auto', + 'transform': 'translateY(' + me.downHight + 'px)', + 'transition': 'transform 300ms' + }) + }) +} + +/* 结束下拉 */ +me.endDownScroll = function (ins){ + me.downHight = 0; + me.isDownScrolling = false; + ins.requestAnimationFrame(function () { + ins.selectComponent('.mescroll-wxs-content').setStyle({ + 'will-change': 'auto', + 'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空) + 'transition': 'transform 300ms' + }) + }) +} + +/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */ +me.clearTransform = function (ins){ + ins.requestAnimationFrame(function () { + ins.selectComponent('.mescroll-wxs-content').setStyle({ + 'will-change': '', + 'transform': '', + 'transition': '' + }) + }) +} + +// ------ 自定义下拉刷新动画 end ------ + +/** + * 监听逻辑层数据的变化 (实时更新数据) + */ +function propObserver(wxsProp) { + me.optDown = wxsProp.optDown + me.scrollTop = wxsProp.scrollTop + me.bodyHeight = wxsProp.bodyHeight + me.isDownScrolling = wxsProp.isDownScrolling + me.isUpScrolling = wxsProp.isUpScrolling + me.isUpBoth = wxsProp.isUpBoth + me.isScrollBody = wxsProp.isScrollBody + me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确 +} + +/** + * 监听逻辑层数据的变化 (调用wxs的方法) + */ +function callObserver(callProp, oldValue, ins) { + if (me.disabled()) return; + if(callProp.callType){ + // 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style + if(callProp.callType === 'showLoading'){ + me.showLoading(ins) + }else if(callProp.callType === 'endDownScroll'){ + me.endDownScroll(ins) + }else if(callProp.callType === 'clearTransform'){ + me.clearTransform(ins) + } + } +} + +/** + * touch事件 + */ +function touchstartEvent(e, ins) { + me.downHight = 0; // 下拉的距离 + me.startPoint = me.getPoint(e); // 记录起点 + me.startTop = me.getScrollTop(); // 记录此时的滚动条位置 + me.startAngle = 0; // 初始角度 + me.lastPoint = me.startPoint; // 重置上次move的点 + me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况) + me.inTouchend = false; // 标记不是touchend + + me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步) +} + +function touchmoveEvent(e, ins) { + var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效) + + if (me.disabled()) return isPrevent; + + var scrollTop = me.getScrollTop(); // 当前滚动条的距离 + var curPoint = me.getPoint(e); // 当前点 + + var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 + + // 向下拉 && 在顶部 + // mescroll-body,直接判定在顶部即可 + // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove + // scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等 + if (moveY > 0 && ( + (me.isScrollBody && scrollTop <= 0) + || + (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) ) + )) { + // 可下拉的条件 + if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && + me.isUpBoth))) { + + // 下拉的角度是否在配置的范围内 + if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90] + if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新 + + // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发 + if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) { + me.inTouchend = true; // 标记执行touchend + touchendEvent(e, ins); // 提前触发touchend + return isPrevent; + } + + isPrevent = false // 小程序是return false + + var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上) + + // 下拉距离 < 指定距离 + if (me.downHight < me.optDown.offset) { + if (me.movetype !== 1) { + me.movetype = 1; // 加入标记,保证只执行一次 + // me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次 + me.callMethod(ins, {type: 'setLoadType', downLoadType: 1}) + me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 + } + me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小 + + // 指定距离 <= 下拉距离 + } else { + if (me.movetype !== 2) { + me.movetype = 2; // 加入标记,保证只执行一次 + // me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次 + me.callMethod(ins, {type: 'setLoadType', downLoadType: 2}) + me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 + } + if (diff > 0) { // 向下拉 + me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小 + } else { // 向上收 + me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度 + } + } + + me.downHight = Math.round(me.downHight) // 取整 + var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值 + // me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行 + me.onMoving(ins, rate, me.downHight) + } + } + + me.lastPoint = curPoint; // 记录本次移动的点 + + return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效) +} + +function touchendEvent(e, ins) { + // 如果下拉区域高度已改变,则需重置回来 + if (me.isMoveDown) { + if (me.downHight >= me.optDown.offset) { + // 符合触发刷新的条件 + me.downHight = me.optDown.offset; // 更新下拉区域高度 + // me.triggerDownScroll(); + me.callMethod(ins, {type: 'triggerDownScroll'}) + } else { + // 不符合的话 则重置 + me.downHight = 0; + // me.optDown.endDownScroll && me.optDown.endDownScroll(me); + me.callMethod(ins, {type: 'endDownScroll'}) + } + me.movetype = 0; + me.isMoveDown = false; + } else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件 + var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 + // 上滑 + if (isScrollUp) { + // 需检查滑动的角度 + var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90] + if (angle > 80) { + // 检查并触发上拉 + // me.triggerUpScroll(true); + me.callMethod(ins, {type: 'triggerUpScroll'}) + } + } + } + me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步) +} + +/* 是否禁用下拉刷新 */ +me.disabled = function(){ + return !me.optDown || !me.optDown.use || me.optDown.native +} + +/* 根据点击滑动事件获取第一个手指的坐标 */ +me.getPoint = function(e) { + if (!e) { + return {x: 0,y: 0} + } + if (e.touches && e.touches[0]) { + return {x: e.touches[0].pageX,y: e.touches[0].pageY} + } else if (e.changedTouches && e.changedTouches[0]) { + return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY} + } else { + return {x: e.clientX,y: e.clientY} + } +} + +/* 计算两点之间的角度: 区间 [0,90]*/ +me.getAngle = function (p1, p2) { + var x = Math.abs(p1.x - p2.x); + var y = Math.abs(p1.y - p2.y); + var z = Math.sqrt(x * x + y * y); + var angle = 0; + if (z !== 0) { + angle = Math.asin(y / z) / Math.PI * 180; + } + return angle +} + +/* 获取滚动条的位置 */ +me.getScrollTop = function() { + return me.scrollTop || 0 +} + +/* 获取body的高度 */ +me.getBodyHeight = function() { + return me.bodyHeight || 0; +} + +/* 调用逻辑层的方法 */ +me.callMethod = function(ins, param) { + if(ins) ins.callMethod('wxsCall', param) +} + +/* 导出模块 */ +module.exports = { + propObserver: propObserver, + callObserver: callObserver, + touchstartEvent: touchstartEvent, + touchmoveEvent: touchmoveEvent, + touchendEvent: touchendEvent +} \ No newline at end of file diff --git a/components/page/article/index.vue b/components/page/article/index.vue new file mode 100644 index 0000000..c67788d --- /dev/null +++ b/components/page/article/index.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/components/page/banner/index.vue b/components/page/banner/index.vue new file mode 100644 index 0000000..bab3232 --- /dev/null +++ b/components/page/banner/index.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/components/page/blank/index.vue b/components/page/blank/index.vue new file mode 100644 index 0000000..1ae7bed --- /dev/null +++ b/components/page/blank/index.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/components/page/goods/index.vue b/components/page/goods/index.vue new file mode 100644 index 0000000..10099fc --- /dev/null +++ b/components/page/goods/index.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/components/page/guide/index.vue b/components/page/guide/index.vue new file mode 100644 index 0000000..e46ca13 --- /dev/null +++ b/components/page/guide/index.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/components/page/image/index.vue b/components/page/image/index.vue new file mode 100644 index 0000000..68b3267 --- /dev/null +++ b/components/page/image/index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/components/page/index.vue b/components/page/index.vue new file mode 100644 index 0000000..5bc11e0 --- /dev/null +++ b/components/page/index.vue @@ -0,0 +1,108 @@ + + + + diff --git a/components/page/mixin.js b/components/page/mixin.js new file mode 100644 index 0000000..61cf4f4 --- /dev/null +++ b/components/page/mixin.js @@ -0,0 +1,23 @@ +import util from '@/utils/util' + +export default { + data() { + return {} + }, + methods: { + + /** + * link对象点击事件 + * 支持tabBar页面 + */ + onLink(linkObj) { + if (!linkObj) return false + // 跳转到指定页面 + if (linkObj.type === 'PAGE') { + this.$navTo(linkObj.param.path, linkObj.param.query) + } + return true + } + }, + +} diff --git a/components/page/navBar/index.vue b/components/page/navBar/index.vue new file mode 100644 index 0000000..f2fdc1d --- /dev/null +++ b/components/page/navBar/index.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/components/page/notice/index.vue b/components/page/notice/index.vue new file mode 100644 index 0000000..0fd4028 --- /dev/null +++ b/components/page/notice/index.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/components/page/richText/index.vue b/components/page/richText/index.vue new file mode 100644 index 0000000..a6a91e2 --- /dev/null +++ b/components/page/richText/index.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/components/page/search/index.vue b/components/page/search/index.vue new file mode 100644 index 0000000..3923b3a --- /dev/null +++ b/components/page/search/index.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/components/page/service/index.vue b/components/page/service/index.vue new file mode 100644 index 0000000..8e748e4 --- /dev/null +++ b/components/page/service/index.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/components/page/video/index.vue b/components/page/video/index.vue new file mode 100644 index 0000000..5af2566 --- /dev/null +++ b/components/page/video/index.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/components/page/window/index.vue b/components/page/window/index.vue new file mode 100644 index 0000000..5fd6064 --- /dev/null +++ b/components/page/window/index.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/components/search/index.vue b/components/search/index.vue new file mode 100644 index 0000000..b12d7d5 --- /dev/null +++ b/components/search/index.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/components/select-region/select-region.vue b/components/select-region/select-region.vue new file mode 100644 index 0000000..e9abaa4 --- /dev/null +++ b/components/select-region/select-region.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/components/shortcut/index.vue b/components/shortcut/index.vue new file mode 100644 index 0000000..681aa7f --- /dev/null +++ b/components/shortcut/index.vue @@ -0,0 +1,292 @@ + + + + + diff --git a/config.js b/config.js new file mode 100644 index 0000000..ccd81fa --- /dev/null +++ b/config.js @@ -0,0 +1,16 @@ +module.exports = { + + // 系统名称 + name: "萤火商城2.0", + + // 必填: 后端api地址, 斜杠/结尾, 参照下面格式 + // 例如: https://www.你的域名.com/index.php?s=/api/ + apiUrl: "https://www.你的域名.com/index.php?s=/api/", + + /** + * 是否启用商城设置缓存 + * 将减少用户端重复请求; 正式运营时请设为true, 开启后商城设置同步前端需10分钟缓存 + */ + enabledSettingCache: true, + +} diff --git a/core/app.js b/core/app.js new file mode 100644 index 0000000..8a4aa2b --- /dev/null +++ b/core/app.js @@ -0,0 +1,211 @@ +import store from '@/store' +import * as util from '@/utils/util' +import { paginate } from '@/common/constant' + +/** + * 显示成功提示框 + */ +export const showSuccess = (msg, callback) => { + uni.showToast({ + title: msg, + icon: 'success', + mask: true, + duration: 1500, + success() { + callback && callback() + } + }) +} + +/** + * 显示失败提示框 + */ +export const showError = (msg, callback) => { + uni.showModal({ + title: '友情提示', + content: msg, + showCancel: false, + success(res) { + callback && callback() + } + }) +} + +/** + * 显示纯文字提示框 + */ +export const showToast = (msg, duration = 1500, mask = true) => { + uni.showToast({ + title: msg, // 提示的内容 + icon: 'none', + mask, // 是否显示透明蒙层,防止触摸穿透 + duration // 提示的延迟时间,单位毫秒,默认:1500 + }) +} + +/** + * tabBar页面路径列表 (用于链接跳转时判断) + * tabBarLinks为常量, 无需修改 + */ +export const getTabBarLinks = () => { + const tabBarLinks = [ + 'pages/index/index', + 'pages/category/index', + 'pages/cart/index', + 'pages/user/index' + ] + return tabBarLinks +} + +/** + * 生成完整的H5地址 [带参数] + * @param {string} h5Url H5访问地址 + * @param {string} path 页面路径 + * @param {object} params 页面参数 + * @return {string} + */ +export const buildUrL = (h5Url, path, params) => { + let complete = h5Url + if (!util.isEmpty(path)) { + complete += '#/' + path + const shareParamsStr = getShareUrlParams(params) + if (!util.isEmpty(shareParamsStr)) { + complete += '?' + shareParamsStr + } + } + return complete +} + +/** + * 生成转发的url参数(string格式) + */ +export const getShareUrlParams = (params) => { + return util.urlEncode({ + // refereeId: store.getters.userId, // 推荐人ID + ...params + }) +} + +/** + * 跳转到指定页面url + * 支持tabBar页面 + * @param {string} url 页面路径 + * @param {object} query 页面参数 + * @param {string} modo 跳转类型(默认navigateTo) + */ +export const navTo = (url, query = {}, modo = 'navigateTo') => { + if (!url || url.length == 0) { + return false + } + // tabBar页面, 使用switchTab + if (util.inArray(url, getTabBarLinks())) { + uni.switchTab({ + url: `/${url}` + }) + return true + } + // 生成query参数 + const queryStr = !util.isEmpty(query) ? '?' + util.urlEncode(query) : '' + // 普通页面, 使用navigateTo + modo === 'navigateTo' && uni.navigateTo({ + url: `/${url}${queryStr}` + }) + // 特殊指定, 使用redirectTo + modo === 'redirectTo' && uni.redirectTo({ + url: `/${url}${queryStr}` + }) + return true +} + +/** + * 获取购物车商品总数量 + * @param {*} value + */ +export const getCartTotalNum = (value) => { + const cartTotal = uni.getStorageSync('cartTotalNum') || 0 + return checkLogin() ? cartTotal : 0 +} + +/** + * 记录购物车商品总数量 + * @param {*} value + */ +export const setCartTotalNum = (value) => { + uni.setStorageSync('cartTotalNum', Number(value)) +} + +/** + * 设置购物车tabbar的角标 + * 该方法只能在tabbar页面中调用, 其他页面调用会报错 + */ +export const setCartTabBadge = () => { + const cartTabbarIndex = 2 + const cartTotal = getCartTotalNum() + if (cartTotal > 0) { + uni.setTabBarBadge({ + index: cartTabbarIndex, + text: `${cartTotal}` + }) + } else { + uni.removeTabBarBadge({ + index: cartTabbarIndex + }) + } + return +} + +/** + * 验证是否已登录 + */ +export const checkLogin = () => { + return !!store.getters.userId +} + +/** + * 发起支付请求 + * @param {Object} 参数 + */ +export const wxPayment = (option) => { + const options = { + timeStamp: '', + nonceStr: '', + prepay_id: '', + paySign: '', + ...option + } + return new Promise((resolve, reject) => { + uni.requestPayment({ + provider: 'wxpay', + timeStamp: options.timeStamp, + nonceStr: options.nonceStr, + 'package': `prepay_id=${options.prepay_id}`, + signType: 'MD5', + paySign: options.paySign, + success: res => resolve(res), + fail: res => reject(res) + }) + }) +} + +/** + * 加载更多列表数据 + * @param {Object} resList 新列表数据 + * @param {Object} oldList 旧列表数据 + * @param {int} pageNo 当前页码 + */ +export const getEmptyPaginateObj = () => { + return util.cloneObj(paginate) +} + +/** + * 加载更多列表数据 + * @param {Object} resList 新列表数据 + * @param {Object} oldList 旧列表数据 + * @param {int} pageNo 当前页码 + */ +export const getMoreListData = (resList, oldList, pageNo) => { + // 如果是第一页需手动制空列表 + if (pageNo == 1) oldList.data = [] + // 合并新数据 + return oldList.data.concat(resList.data) +} diff --git a/core/bootstrap.js b/core/bootstrap.js new file mode 100644 index 0000000..1939ae4 --- /dev/null +++ b/core/bootstrap.js @@ -0,0 +1,13 @@ +import store from '@/store' +import storage from '@/utils/storage' +import platform from '@/core/platform' +import { ACCESS_TOKEN, USER_ID } from '@/store/mutation-types' + +export default function Initializer() { + // 当前运行的终端 + store.commit('SET_PLATFORM', platform) + // 用户认证token + store.commit('SET_TOKEN', storage.get(ACCESS_TOKEN)) + // 当前用户ID + store.commit('SET_USER_ID', storage.get(USER_ID)) +} diff --git a/core/config/defaultConfig.js b/core/config/defaultConfig.js new file mode 100644 index 0000000..172129b --- /dev/null +++ b/core/config/defaultConfig.js @@ -0,0 +1,20 @@ +// ** 本文件是config.js的默认数据 (请勿修改本文件中的内容) +// ** 如需修改配置请移步到根目录的config.js文件 +export default { + + // 系统名称 + name: "萤火商城2.0", + + /** + * 后端api地址 (必填; 斜杠/结尾; 请确保能访问) + * 例如: https://www.你的域名.com/index.php?s=/api/ + */ + apiUrl: "./index.php?s=/api/", + + /** + * 是否启用商城设置缓存 + * 正式运营时启用, 将减少用户端重复请求 + */ + enabledSettingCache: false, + +} diff --git a/core/config/index.js b/core/config/index.js new file mode 100644 index 0000000..c51d80c --- /dev/null +++ b/core/config/index.js @@ -0,0 +1,25 @@ +import config from '@/config.js' +import defaultConfig from './defaultConfig.js' + +// 合并用户配置和默认配置 +const mergeConfig = Object.assign({}, defaultConfig, config) + +/** + * 配置文件工具类 + * mix: 如需在项目中获取配置项, 请使用本工具类的方法, 不要直接import根目录的config.js + */ +export default { + // 获取全部配置 + all() { + return mergeConfig + }, + + // 获取指定配置 + get(key, def = undefined) { + if (mergeConfig.hasOwnProperty(key)) { + return mergeConfig[key] + } + console.error(`检测到不存在的配置项: ${key}`) + return def + } +} diff --git a/core/mixins/app.js b/core/mixins/app.js new file mode 100644 index 0000000..85208c9 --- /dev/null +++ b/core/mixins/app.js @@ -0,0 +1,14 @@ +import Vue from 'vue' +import { mapGetters } from 'vuex' +import store from '@/store/index' +import platform from '@/core/platform' + +export default { + data() { + return { + platform + } + }, + computed: {} + +} diff --git a/core/platform.js b/core/platform.js new file mode 100644 index 0000000..7f5da6c --- /dev/null +++ b/core/platform.js @@ -0,0 +1,64 @@ +/** + * 获取当前运行的客户端(APP H5 小程序) + * https://uniapp.dcloud.io/platform + */ +const getPlatform = () => { + // #ifdef APP-PLUS + const platform = 'APP' + // #endif + // #ifdef APP-PLUS-NVUE + const platform = 'APP' + // #endif + // #ifdef H5 + const platform = weixinOfficial() ? 'H5-WEIXIN' : 'H5' + // #endif + // #ifdef MP-WEIXIN + const platform = 'MP-WEIXIN' + // #endif + // #ifdef MP-ALIPAY + const platform = 'MP-ALIPAY' + // #endif + // #ifdef MP-BAIDU + const platform = 'MP-BAIDU' + // #endif + // #ifdef MP-TOUTIAO + const platform = 'MP-TOUTIAO' + // #endif + // #ifdef MP-LARK + const platform = 'MP-LARK' + // #endif + // #ifdef MP-QQ + const platform = 'MP-QQ' + // #endif + // #ifdef MP-KUAISHOU + const platform = 'MP-KUAISHOU' + // #endif + // #ifdef MP-360 + const platform = 'MP-360' + // #endif + // #ifdef QUICKAPP-WEBVIEW + const platform = 'QUICKAPP-WEBVIEW' + // #endif + return platform +} + +// 是否为微信公众号端 +const weixinOfficial = () => { + // #ifdef H5 + const ua = window.navigator.userAgent.toLowerCase() + return String(ua.match(/MicroMessenger/i)) === 'micromessenger' + // #endif + return false +} + +const platfrom = getPlatform() + +export const isH5 = platfrom === 'H5' +export const isApp = platfrom === 'APP' +export const isMpWeixin = platfrom === 'MP-WEIXIN' + +// 是否为微信公众号端 +// 相当于H5端运行在微信内置浏览器, 但是需要使用微信的jssdk所以要单独区分 +export const isWeixinOfficial = platfrom === 'H5-WEIXIN' + +export default platfrom diff --git a/js_sdk/ican-H5Api/ican-H5Api.js b/js_sdk/ican-H5Api/ican-H5Api.js new file mode 100644 index 0000000..ea5823d --- /dev/null +++ b/js_sdk/ican-H5Api/ican-H5Api.js @@ -0,0 +1,71 @@ +//#ifdef H5 +/** clipboard.js v2.0.4**/ +!function(t,e){try{window.ClipboardJS=e();}catch(e){};"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports} + return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;ndata}) + cb.on('success',function(res){ + window.__clipboard__=data; + success&&Types.isFunction(success)&&success({data:res.text}) + complete&&Types.isFunction(complete)&&complete() + cb.off('error') + cb.off('success') + cb.destroy()}) + cb.on('error',function(err){fail&&Types.isFunction(fail)&&fail(err) + complete&&Types.isFunction(complete)&&complete() + cb.off('error') + cb.off('success') + cb.destroy()}) + cb.onClick(e)}; +uni.getClipboardData=function(options){let emptyFun=function(){} + let config={data:null,event:null,success:emptyFun,fail:emptyFun,complete:emptyFun} + if(options&&Types.isObject(options)){config=Object.assign({},config,options)} + let success=config.success||emptyFun + let fail=config.fail||emptyFun + let complete=config.complete||emptyFun + if(window.__clipboard__!==undefined){success&&Types.isFunction(success)&&success({data:window.__clipboard__})}else{fail&&Types.isFunction(fail)&&fail({data:null})} + complete&&Types.isFunction(complete)&&complete()}; +function fileDownLoad(data){var linkElement=document.createElement('a') + linkElement.setAttribute('href',data.blob) + linkElement.setAttribute('downLoad',data.name) + linkElement.click()} +uni.saveImageToPhotosAlbum=uni.saveVideoToPhotosAlbum=function(options){let emptyFun=function(){} + let config={filePath:null,success:emptyFun,fail:emptyFun,complete:emptyFun} + if(options&&Types.isObject(options)){config=Object.assign({},config,options)} + if(options&&Types.isString(options)){config=Object.assign({},config,{filePath:options})} + let filePath=config.filePath + let success=config.success||emptyFun + let fail=config.fail||emptyFun + let complete=config.complete||emptyFun + if(!filePath){fail&&Types.isFunction(fail)&&fail({msg:'no File'}) + complete&&Types.isFunction(complete)&&complete() + return} + let names=filePath.split('/') + let name=names[names.length-1] + uni.downloadFile({url:filePath,success:function(res){let tempFilePath=res.tempFilePath + fileDownLoad({name:name,blob:tempFilePath}) + success&&Types.isFunction(success)&&success({filePath:filePath})},fail:function(err){fail&&Types.isFunction(fail)&&fail({msg:err})},complete:function(){complete&&Types.isFunction(complete)&&complete()}})} +//#endif diff --git a/license.md b/license.md new file mode 100644 index 0000000..c40d577 --- /dev/null +++ b/license.md @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "{}" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright 2018 萤火科技 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..827b7ca --- /dev/null +++ b/main.js @@ -0,0 +1,39 @@ +import Vue from 'vue' +import uView from 'uview-ui' +import App from './App' +import store from './store' +import bootstrap from './core/bootstrap' +import mixin from './core/mixins/app' +import './js_sdk/ican-H5Api/ican-H5Api' +import { + navTo, + showToast, + showSuccess, + showError, + getShareUrlParams +} from './core/app' + +Vue.config.productionTip = false + +App.mpType = 'app' + +// 载入uView库 +Vue.use(uView) + +// 全局mixin +Vue.mixin(mixin) + +// 挂载全局函数 +Vue.prototype.$toast = showToast +Vue.prototype.$success = showSuccess +Vue.prototype.$error = showError +Vue.prototype.$navTo = navTo +Vue.prototype.$getShareUrlParams = getShareUrlParams + +// 实例化应用 +const app = new Vue({ + ...App, + store, + created: bootstrap +}) +app.$mount() diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..2d9d328 --- /dev/null +++ b/manifest.json @@ -0,0 +1,111 @@ +{ + "name" : "萤火商城2.0", + "appid" : "", + "description" : "萤火商城V2.0,是2021年全新推出的一款轻量级、高性能、前后端分离的电商系统", + "versionName" : "2.0.3", + "versionCode" : 203, + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : {}, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "autoSdkPermissions" : true, + "permissionExternalStorage" : { + "request" : "once", + "prompt" : "应用保存运行状态等信息,需要获取读写手机存储(系统提示为访问设备上的照片、媒体内容和文件)权限,请允许。" + }, + "permissionPhoneState" : { + "request" : "none", + "prompt" : "为保证您正常、安全地使用,需要获取设备识别码(部分手机提示为获取手机号码)使用权限,请允许。" + } + }, + /* ios打包配置 */ + "ios" : {}, + /* SDK配置 */ + "sdkConfigs" : { + "ad" : {} + }, + "splashscreen" : { + "iosStyle" : "common" + } + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + // 是否检查安全域名和 TLS 版本 + "urlCheck" : true, + // es6转es5 + "es6" : false, + // 上传代码时自动压缩 + "minified" : true, + // 调试器 wxml面板展示 shadow-root + "showShadowRootInWxmlPanel" : true, + // 上传代码时是否自动压缩样式文件 + "minifyWXSS" : true, + // 上传代码时是否自动压缩wxml文件 + // 因uniappp构建出来的wxml本身就是压缩好的, 如果再用微信开发工具压缩一次的话, 有可能会破坏到文件, 所以此处设为false + "minifyWXML" : false + }, + "usingComponents" : true, + "lazyCodeLoading" : "requiredComponents", + "permission" : {} + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "h5" : { + "router" : { + "mode" : "history", + "base" : "./" + } + } +} diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..9cfbc4d --- /dev/null +++ b/pages.json @@ -0,0 +1,261 @@ +{ + "tabBar": { + "color": "#000000", + "selectedColor": "#fa2209", + "borderStyle": "black", + "backgroundColor": "#ffffff", + "list": [{ + "pagePath": "pages/index/index", + "iconPath": "static/tabbar/home.png", + "selectedIconPath": "static/tabbar/home-active.png", + "text": "首页" + }, { + "pagePath": "pages/category/index", + "iconPath": "static/tabbar/cate.png", + "selectedIconPath": "static/tabbar/cate-active.png", + "text": "分类" + }, { + "pagePath": "pages/cart/index", + "iconPath": "static/tabbar/cart.png", + "selectedIconPath": "static/tabbar/cart-active.png", + "text": "购物车" + }, { + "pagePath": "pages/user/index", + "iconPath": "static/tabbar/user.png", + "selectedIconPath": "static/tabbar/user-active.png", + "text": "我的" + }] + }, + "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + { + "path": "pages/index/index", + "style": { + "enablePullDownRefresh": true + } + }, + { + "path": "pages/category/index", + "style": { + "navigationBarTitleText": "全部分类" + } + }, + { + "path": "pages/cart/index", + "style": { + "navigationBarTitleText": "购物车" + } + }, + { + "path": "pages/user/index", + "style": { + "navigationBarTitleText": "个人中心", + "enablePullDownRefresh": true, + "navigationStyle": "custom" + } + }, + { + "path": "pages/custom/index", + "style": { + "enablePullDownRefresh": true + } + }, + { + "path": "pages/search/index", + "style": { + "navigationBarTitleText": "商品搜索" + } + }, + { + "path": "pages/login/index", + "style": { + "navigationBarTitleText": "会员登录" + } + }, + { + "path": "pages/user/bind/index", + "style": { + "navigationBarTitleText": "绑定手机" + } + }, + { + "path": "pages/user/personal/index", + "style": { + "navigationBarTitleText": "个人信息" + } + }, + { + "path": "pages/article/index", + "style": { + "navigationBarTitleText": "资讯列表" + } + }, + { + "path": "pages/article/detail", + "style": { + "navigationBarTitleText": "资讯详情" + } + }, + { + "path": "pages/help/index", + "style": { + "navigationBarTitleText": "帮助中心" + } + }, + { + "path": "pages/coupon/index", + "style": { + "navigationBarTitleText": "领券中心" + } + }, + { + "path": "pages/goods/list", + "style": { + "navigationBarTitleText": "商品列表", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/goods/detail", + "style": { + "navigationBarTitleText": "商品详情页" + } + }, + { + "path": "pages/comment/index", + "style": { + "navigationBarTitleText": "商品评价页" + } + }, + { + "path": "pages/my-coupon/index", + "style": { + "navigationBarTitleText": "我的优惠券" + } + }, + { + "path": "pages/address/index", + "style": { + "navigationBarTitleText": "收货地址" + } + }, + { + "path": "pages/address/create", + "style": { + "navigationBarTitleText": "新增收货地址" + } + }, + { + "path": "pages/address/update", + "style": { + "navigationBarTitleText": "编辑收货地址" + } + }, + { + "path": "pages/points/log", + "style": { + "navigationBarTitleText": "账单明细" + } + }, + { + "path": "pages/wallet/index", + "style": { + "navigationBarTitleText": "我的钱包" + } + }, + { + "path": "pages/wallet/balance/log", + "style": { + "navigationBarTitleText": "账单详情" + } + }, + { + "path": "pages/wallet/recharge/index", + "style": { + "navigationBarTitleText": "充值中心" + } + }, + { + "path": "pages/wallet/recharge/order", + "style": { + "navigationBarTitleText": "充值记录" + } + }, + { + "path": "pages/checkout/index", + "style": { + "navigationBarTitleText": "订单结算台" + } + }, + { + "path": "pages/order/center", + "style": { + "navigationBarTitleText": "订单中心", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/order/index", + "style": { + "navigationBarTitleText": "我的订单", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/order/detail", + "style": { + "navigationBarTitleText": "订单详情", + "navigationBarTextStyle": "white", + "navigationBarBackgroundColor": "#e8c269" + } + }, + { + "path": "pages/order/express/index", + "style": { + "navigationBarTitleText": "物流跟踪" + } + }, + { + "path": "pages/order/comment/index", + "style": { + "navigationBarTitleText": "订单评价" + } + }, + { + "path": "pages/refund/index", + "style": { + "navigationBarTitleText": "退换/售后" + } + }, + { + "path": "pages/refund/detail", + "style": { + "navigationBarTitleText": "售后详情" + } + }, + { + "path": "pages/refund/apply", + "style": { + "navigationBarTitleText": "申请售后" + } + } + ], + "globalStyle": { + // #ifdef H5 + // "navigationStyle": "custom", + "maxWidth": 750, + "rpxCalcMaxDeviceWidth": 750, // rpx 计算所支持的最大设备宽度,单位 px,默认值为 960 + "rpxCalcBaseDeviceWidth": 560, // rpx 计算使用的基准设备宽度,设备实际宽度超出 rpx 计算所支持的最大设备宽度时将按基准宽度计算,单位 px,默认值为 375 + "rpxCalcIncludeWidth": 9999, // rpx 计算特殊处理的值,始终按实际的设备宽度计算,单位 rpx,默认值为 750 + // #endif + "navigationBarBackgroundColor": "#ffffff", + "navigationBarTitleText": "", + "navigationBarTextStyle": "black", + "backgroundTextStyle": "dark" + }, + "easycom": { + "autoscan": true, + "custom": { + "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" + } + } +} diff --git a/pages/address/create.vue b/pages/address/create.vue new file mode 100644 index 0000000..2208a8d --- /dev/null +++ b/pages/address/create.vue @@ -0,0 +1,187 @@ + + + + + + diff --git a/pages/address/index.vue b/pages/address/index.vue new file mode 100644 index 0000000..182a589 --- /dev/null +++ b/pages/address/index.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/pages/address/update.vue b/pages/address/update.vue new file mode 100644 index 0000000..c2fc682 --- /dev/null +++ b/pages/address/update.vue @@ -0,0 +1,225 @@ + + + + + + diff --git a/pages/article/detail.vue b/pages/article/detail.vue new file mode 100644 index 0000000..9023d15 --- /dev/null +++ b/pages/article/detail.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/pages/article/index.vue b/pages/article/index.vue new file mode 100644 index 0000000..35ef17e --- /dev/null +++ b/pages/article/index.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/pages/cal/index.vue b/pages/cal/index.vue new file mode 100644 index 0000000..31dbe18 --- /dev/null +++ b/pages/cal/index.vue @@ -0,0 +1,331 @@ + + + + + \ No newline at end of file diff --git a/pages/cart/index.vue b/pages/cart/index.vue new file mode 100644 index 0000000..d57cc28 --- /dev/null +++ b/pages/cart/index.vue @@ -0,0 +1,500 @@ + + + + + + diff --git a/pages/category/components/commodity.vue b/pages/category/components/commodity.vue new file mode 100644 index 0000000..5d4d7c9 --- /dev/null +++ b/pages/category/components/commodity.vue @@ -0,0 +1,402 @@ + + + + + diff --git a/pages/category/components/primary.vue b/pages/category/components/primary.vue new file mode 100644 index 0000000..7fbd9e2 --- /dev/null +++ b/pages/category/components/primary.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/pages/category/components/secondary.vue b/pages/category/components/secondary.vue new file mode 100644 index 0000000..47ca7dc --- /dev/null +++ b/pages/category/components/secondary.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/pages/category/index.vue b/pages/category/index.vue new file mode 100644 index 0000000..f3c4bcc --- /dev/null +++ b/pages/category/index.vue @@ -0,0 +1,165 @@ + + + + + + diff --git a/pages/checkout/index.vue b/pages/checkout/index.vue new file mode 100644 index 0000000..29f1220 --- /dev/null +++ b/pages/checkout/index.vue @@ -0,0 +1,523 @@ + + + + + diff --git a/pages/checkout/style.scss b/pages/checkout/style.scss new file mode 100644 index 0000000..974f47c --- /dev/null +++ b/pages/checkout/style.scss @@ -0,0 +1,445 @@ + +// 配送信息 +.flow-delivery { + padding: 34rpx 30rpx; + background: #fff url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANYAAAANCAYAAADVGpDCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3Yjk4M2ExYy1jMDhkLTQ1OTktYTI0Ny1kZjNjYzdiYTQ5ZTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDQwNkY3RkU5N0NGMTFFNUI3N0M4NTU4MzM2RjlFODIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDQwNkY3RkQ5N0NGMTFFNUI3N0M4NTU4MzM2RjlFODIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNzgwZWI1NS03OGFhLTQzOTUtODQ4OC1lOWI5YmVlYTY1ZDciIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1OTRiYzUyMy1jMzc3LTExNzgtYTdkZS04NGY3YmM1ZGIxMDMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz556PLxAAACBElEQVR42tyaSyhEYRTHP48imlKibDQeSSlkSlEWLCRFsZNH5FE2FqQ8ErIRC9lIkTwXSpMkWWChhEJCSnlkoUZGSsr78f98n43CMFPu/Z/6NZuZ2zn33/+cb869XkmLx8IDEQaGQJbgiytQDSY3MyL+LYnL/HxPXSoHDIJQQq2WQQk4Dbbb/yUB29LJ+6e3B66VB3ZITbUIEqSpCGoJBP1ghtBUD6ARpEtTGSEhXzd+awE9oJzQUPegWdf3QlBPMhgDMYRa7YNisGWkpP5qrBQtVBShUHugUE9hs4fUtwG0utlEjRivoA/Ug1sj3vjffr8FNJEK1auPFHcE9UTq5pdK2PwcoAzMG7mjuRrRYEIfK9jiDJSCBZJ6ynSTsBBqNQ0qgdPISbq6vJCFbJOaagrEk5gqWNczRGiqG1Ah1LLMafRkf5pYIUKtZnMJDXUNasAIST2ZYFioRx9ssQaKwJFZEv5uYmWDXVJTrYBEElP562PfPKGpnkAbSDOTqb6aWAGgW6iHol5kQj2CdtAJngnqkc1hHMQRNr9DPaXWzZj8Z2PZtFCxhEIdaKE2CGqRJ4060AH8CLUaALX6f5VpBZLhI9SaeZXQVHKNLt84SCIxVbhQi5YuQlNd6OVElZlN9TGxrGBUn2PZ4lyoTdIsST0FQj0UDSLUak6ot3gcBLVY3wQYAJoVXxmNERajAAAAAElFTkSuQmCC') bottom left repeat-x; + background-size: 120rpx auto; + + .detail-location { + font-size: 36rpx; + } + .detail-content { + padding: 0 20rpx; + .detail-content__title-phone { + margin-left: 10rpx; + } + .detail-content__describe { + font-size: 28rpx; + color: #777; + } + } + .detail-content__title { + margin-bottom: 6rpx; + } +} + +// 买家留言 +.flow-all-money { + .ipt-wrapper { + input { + font-size: 28rpx; + width: 100%; + height: 75rpx; + } + } +} + +// 商品列表 +.checkout_list { + padding: 20rpx 30rpx 4rpx 30rpx; + background: #fff; + border-bottom: 1rpx solid rgb(248, 248, 248); + .flow-shopList { + padding: 5rpx 0 10rpx; + border-bottom: 1rpx solid rgb(248, 248, 248); + &:last-child { + border-bottom: 0; + } + } +} + +.flow-header-left { + padding-left: 90rpx; +} + +/* 会员价 */ +.flow-shopList { + + .flow-list-right { + .flow-cont { + + &.price-delete { + font-size: 26rpx; + color: #777; + text-decoration: line-through; + } + + } + } + + .grade-price { + padding-top: 8rpx; + font-size: 28rpx; + color: #ff495e; + text-align: right; + } + + .goods-name{ + font-size: 28rpx; + color: #333; + } + +} + +/* 优惠券选择 */ +.popup__coupon { + width: 750rpx; + background: #fff; + box-sizing: border-box; + padding: 30rpx; + + .coupon__do_not { + .control { + width: 90%; + height: 72rpx; + margin-bottom: 24rpx; + color: #888; + border: 1rpx solid #e3e3e3; + border-radius: 10rpx; + /* #ifdef H5 */ + max-width: 1120rpx; + /* #endif */ + } + } + + .coupon__title { + text-align: center; + margin-bottom: 30rpx; + } + + .coupon-list { + /* #ifdef H5 */ + max-width: 1120rpx; + margin: 0 auto; + /* #endif */ + } + + .coupon-item { + overflow: hidden; + margin-bottom: 22rpx; + } + + .item-wrapper { + width: 100%; + display: flex; + background: #fff; + border-radius: 8rpx; + color: #fff; + height: 180rpx; + + .coupon-type { + position: absolute; + top: 0; + right: 0; + z-index: 10; + width: 128rpx; + padding: 6rpx 0; + background: #a771ff; + font-size: 20rpx; + text-align: center; + color: #ffffff; + transform: rotate(45deg); + transform-origin: 64rpx 64rpx; + } + + &.color-blue { + background: linear-gradient(-125deg, #57bdbf, #2f9de2); + } + + &.color-red { + background: linear-gradient(-128deg, #ff6d6d, #ff3636); + } + + &.color-violet { + background: linear-gradient(-113deg, #ef86ff, #b66ff5); + + .coupon-type { + background: #55b5ff; + } + } + + &.color-yellow { + background: linear-gradient(-141deg, #f7d059, #fdb054); + } + + &.color-gray { + background: linear-gradient(-113deg, #bdbdbd, #a2a1a2); + + .coupon-type { + background: #9e9e9e; + } + } + + .content { + flex: 1; + padding: 30rpx 20rpx; + border-radius: 16rpx 0 0 16rpx; + + .title { + font-size: 32rpx; + } + + .bottom { + .time { + font-size: 24rpx; + } + + .receive { + height: 46rpx; + width: 122rpx; + line-height: 46rpx; + text-align: center; + border: 1rpx solid #fff; + border-radius: 30rpx; + color: #fff; + font-size: 24rpx; + + &.state { + border: none; + } + } + } + } + + .tip { + position: relative; + flex: 0 0 32%; + text-align: center; + border-radius: 0 16rpx 16rpx 0; + + .money { + font-weight: bold; + font-size: 52rpx; + } + + .pay-line { + font-size: 22rpx; + } + } + + .split-line { + position: relative; + flex: 0 0 0; + border-left: 4rpx solid #fff; + margin: 0 10rpx 0 6rpx; + background: #fff; + + &:before, + { + border-radius: 0 0 16rpx 16rpx; + top: 0; + } + + &:after { + border-radius: 16rpx 16rpx 0 0; + bottom: 0; + } + + &:before, + &:after { + content: ''; + position: absolute; + width: 24rpx; + height: 12rpx; + background: #f7f7f7; + left: -14rpx; + z-index: 1; + } + + + } + } + +} + +/* 积分抵扣 */ +.points { + + .title { + margin-right: 5rpx; + } + + .icon-help { + font-size: 28rpx; + } + + .points-money { + margin-right: 20rpx; + } + +} +/* 支付方式 */ +.pay-method { + + .pay-item { + padding: 24rpx 0; + font-size: 28rpx; + border-bottom: 1rpx solid rgb(248, 248, 248); + + .item-left_icon { + margin-right: 20rpx; + font-size: 32rpx; + + &.wechat { + color: #00c800; + } + + &.balance { + color: #ff9700; + } + + } + } + + .user-balance { + margin-left: 20rpx; + font-size: 26rpx; + // color: #464646; + } + +} + +// 商品规格 +.goods-props { + padding-top: 10rpx; + font-size: 24rpx; + color: #999; + + .goods-props-item { + float: left; + .group-name { + margin-right: 6rpx; + } + } +} + +// 右侧箭头 +.right-arrow { + margin-left: 16rpx; + // color: #777; + font-size: 26rpx; +} + +// 底部操作栏 +.flow-fixed-footer { + position: fixed; + bottom: var(--window-bottom); + left: var(--window-left); + right: var(--window-right); + // width: 100%; + background: #fff; + border-top: 1px solid #eee; + z-index: 11; + // 设置ios刘海屏底部横线安全区域 + padding-bottom: calc(constant(safe-area-inset-bottom) + var(--window-bottom)); + padding-bottom: calc(env(safe-area-inset-bottom) + var(--window-bottom)); + + .chackout-left { + font-size: 28rpx; + line-height: 92rpx; + color: #777; + flex: 4; + padding-left: 12px; + } + + .chackout-right { + font-size: 34rpx; + flex: 2; + } + + + // 提交按钮 + .flow-btn { + background: linear-gradient(to right, #f9211c, #ff6335); + color: #fff; + text-align: center; + line-height: 92rpx; + display: block; + font-size: 28rpx; + // 禁用按钮 + &.disabled { + background: #ff9779; + } + } +} + + // 积分说明 +.points-content { + padding: 30rpx 48rpx; + font-size: 28rpx; + line-height: 50rpx; + text-align: left; + color: #606266; + height: 620rpx; + box-sizing: border-box; +} + +/* 共几件商品 */ +.flow-num-box { + font-size: 28rpx; + color: #777; + padding: 16rpx 24rpx; + text-align: right; +} + +/* app.scss */ +.flow-shopList { + padding: 18rpx 0; + + .flow-list-left { + margin-right: 20rpx; + + image { + width: 180rpx; + height: 180rpx; + border: 1rpx solid #eee; + background: #fff; + } + + } + + .flow-list-right { + + .flow-cont { + font-size: 28rpx; + color: #fa2209; + } + + .small { + font-size: 26rpx; + color: #777; + } + + .flow-list-cont { + padding-top: 10rpx; + } + + } + +} + +.flow-all-money { + padding: 0 24rpx; + color: #444; + + .flow-all-list { + font-size: 28rpx; + padding: 20rpx 0; + border-bottom: 1rpx solid rgb(248, 248, 248); + } + + .flow-all-list:last-child { + border-bottom: none; + } + + .flow-all-list-cont { + font-size: 28rpx; + padding: 10rpx 0; + } + + .flow-arrow { + justify-content: flex-end; + align-items: center; + } + +} diff --git a/pages/comment/index.vue b/pages/comment/index.vue new file mode 100644 index 0000000..66f6970 --- /dev/null +++ b/pages/comment/index.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/pages/coupon/index.vue b/pages/coupon/index.vue new file mode 100644 index 0000000..bf5a56c --- /dev/null +++ b/pages/coupon/index.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/pages/custom/index.vue b/pages/custom/index.vue new file mode 100644 index 0000000..43bbbce --- /dev/null +++ b/pages/custom/index.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/pages/empty.vue b/pages/empty.vue new file mode 100644 index 0000000..89f7847 --- /dev/null +++ b/pages/empty.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/pages/goods/components/Comment.vue b/pages/goods/components/Comment.vue new file mode 100644 index 0000000..7baa05c --- /dev/null +++ b/pages/goods/components/Comment.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/pages/goods/components/Service.vue b/pages/goods/components/Service.vue new file mode 100644 index 0000000..ed8e5d7 --- /dev/null +++ b/pages/goods/components/Service.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/pages/goods/components/SkuPopup.vue b/pages/goods/components/SkuPopup.vue new file mode 100644 index 0000000..a01934a --- /dev/null +++ b/pages/goods/components/SkuPopup.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/pages/goods/components/SlideImage.vue b/pages/goods/components/SlideImage.vue new file mode 100644 index 0000000..adda7bc --- /dev/null +++ b/pages/goods/components/SlideImage.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/pages/goods/detail.scss b/pages/goods/detail.scss new file mode 100644 index 0000000..41cdb53 --- /dev/null +++ b/pages/goods/detail.scss @@ -0,0 +1,227 @@ +.container { + // 设置ios刘海屏底部横线安全区域 + // 110 - 18 + 4 + padding-bottom: calc(constant(safe-area-inset-bottom) + 106rpx + 6rpx); + padding-bottom: calc(env(safe-area-inset-bottom) + 106rpx + 6rpx); +} + +// 商品信息 +.goods-info { + background: #fff; + padding: 25rpx 30rpx; +} + +.info-item__top { + min-height: 40rpx; + margin-bottom: 20rpx; + line-height: 1; +} + +.floor-price__samll { + font-size: 26rpx; + line-height: 1; + color: #FA2209; + margin-bottom: -10rpx; +} + +// 商品价 +.floor-price { + color: #FA2209; + margin-right: 15rpx; + font-size: 42rpx; +} + +.original-price { + font-size: 26rpx; + text-decoration: line-through; + color: #959595; + margin-right: 15rpx; + margin-bottom: -6rpx; +} + +// 会员价标签 +.user-grade { + background: #3c3c3c; + border-radius: 6rpx; + padding: 8rpx 14rpx; + margin-right: 15rpx; + font-size: 24rpx; + color: #EEE0C3; +} + +.goods-sales { + font-size: 24rpx; + color: #959595; +} + +.info-item__name .goods-name { + font-size: 30rpx; +} + +/* 商品分享 */ + +.goods-share__line { + border-left: 1rpx solid #f4f4f4; + height: 60rpx; + margin: 0 30rpx; +} + +.goods-share .share-btn { + line-height: normal; + padding: 0; + background: none; + border-radius: 0; + box-shadow: none; + font-size: 8pt; + border: none; + color: #191919; +} + +.goods-share .share-btn::after { + border: none; +} + +.goods-share .share__icon { + font-size: 40rpx; + margin-bottom: 5rpx; +} + +// 商品卖点 +.info-item_selling-point { + margin-top: 8rpx; + font-size: 24rpx; + color: #808080; +} + +// 选择商品规格 +.goods-choice { + padding: 26rpx 30rpx; + font-size: 28rpx; + + .spec-list { + display: flex; + align-items: center; + + .spec-name { + margin-right: 10rpx; + } + } +} + +// 商品详情 +.goods-content .item-title { + padding: 26rpx 30rpx; + font-size: 28rpx; +} + +// 底部操作栏 +.footer-fixed { + position: fixed; + bottom: var(--window-bottom); + left: 0; + right: 0; + display: flex; + z-index: 11; + box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24); + background: #fff; + + // 设置ios刘海屏底部横线安全区域 + padding-bottom: constant(safe-area-inset-bottom); + padding-bottom: env(safe-area-inset-bottom); +} + +.footer-container { + width: 100%; + display: flex; + height: 106rpx; +} + +// 快捷菜单 +.foo-item-fast { + box-sizing: border-box; + min-width: 214rpx; + line-height: 1; + display: flex; + align-items: center; + justify-content: space-evenly; + margin-right: 12rpx; + + .fast-item { + position: relative; + padding: 4rpx 0; + line-height: 1; + text-align: center; + width: 84rpx; + + &--cart { + margin-left: 6rpx; + .fast-icon { margin-left: -12rpx; } + } + + // 角标 + .fast-badge { + display: inline-block; + box-sizing: border-box; + min-width: 16px; + padding: 0 3px; + color: #fff; + font-weight: 500; + font-size: 12px; + font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif; + line-height: 1.2; + text-align: center; + background-color: #ee0a24; + border: 1px solid #fff; + border-radius: 999px; + } + + .fast-badge--fixed { + position: absolute; + top: 0; + right: 0; + transform-origin: 100% + } + + .fast-icon { + font-size: 44rpx; + margin-bottom: 8rpx; + } + + .fast-text { + font-size: 22rpx; + } + } +} + +// 操作按钮 +.foo-item-btn { + flex: 1; + + .btn-wrapper { + height: 100%; + display: flex; + align-items: center; + } + + .btn-item { + flex: 1; + font-size: 28rpx; + height: 72rpx; + margin-right: 16rpx; + color: #fff; + border-radius: 50rpx; + display: flex; + justify-content: center; + align-items: center; + } + + // 立即购买按钮 + .btn-item-main { + background: linear-gradient(to right, #f9211c, #ff6335); + } + + // 购物车按钮 + .btn-item-deputy { + background: linear-gradient(to right, #ffa600, #ffbb00); + } +} \ No newline at end of file diff --git a/pages/goods/detail.vue b/pages/goods/detail.vue new file mode 100644 index 0000000..c6f5dac --- /dev/null +++ b/pages/goods/detail.vue @@ -0,0 +1,290 @@ + + + + + + diff --git a/pages/goods/list.vue b/pages/goods/list.vue new file mode 100644 index 0000000..be74a76 --- /dev/null +++ b/pages/goods/list.vue @@ -0,0 +1,463 @@ + + + + + diff --git a/pages/help/index.vue b/pages/help/index.vue new file mode 100644 index 0000000..d0599ed --- /dev/null +++ b/pages/help/index.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/pages/index/home.vue b/pages/index/home.vue new file mode 100644 index 0000000..1fdbf0f --- /dev/null +++ b/pages/index/home.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/pages/index/index.vue b/pages/index/index.vue new file mode 100644 index 0000000..b35568d --- /dev/null +++ b/pages/index/index.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/pages/login/components/main.vue b/pages/login/components/main.vue new file mode 100644 index 0000000..2d0a5d1 --- /dev/null +++ b/pages/login/components/main.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/pages/login/components/mp-weixin-mobile.vue b/pages/login/components/mp-weixin-mobile.vue new file mode 100644 index 0000000..93c1e02 --- /dev/null +++ b/pages/login/components/mp-weixin-mobile.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/pages/login/components/mp-weixin.vue b/pages/login/components/mp-weixin.vue new file mode 100644 index 0000000..5c56073 --- /dev/null +++ b/pages/login/components/mp-weixin.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/pages/login/index.vue b/pages/login/index.vue new file mode 100644 index 0000000..08b5a9b --- /dev/null +++ b/pages/login/index.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/pages/my-coupon/index.vue b/pages/my-coupon/index.vue new file mode 100644 index 0000000..65f9528 --- /dev/null +++ b/pages/my-coupon/index.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/pages/order/center.vue b/pages/order/center.vue new file mode 100644 index 0000000..a2a220f --- /dev/null +++ b/pages/order/center.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/pages/order/comment/index.vue b/pages/order/comment/index.vue new file mode 100644 index 0000000..fdd9ba5 --- /dev/null +++ b/pages/order/comment/index.vue @@ -0,0 +1,571 @@ + + + + + diff --git a/pages/order/detail.vue b/pages/order/detail.vue new file mode 100644 index 0000000..4cb0648 --- /dev/null +++ b/pages/order/detail.vue @@ -0,0 +1,866 @@ + + + + + + diff --git a/pages/order/express/index.vue b/pages/order/express/index.vue new file mode 100644 index 0000000..9408d4c --- /dev/null +++ b/pages/order/express/index.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/pages/order/index.vue b/pages/order/index.vue new file mode 100644 index 0000000..b2555c5 --- /dev/null +++ b/pages/order/index.vue @@ -0,0 +1,596 @@ + + + + + diff --git a/pages/points/log.vue b/pages/points/log.vue new file mode 100644 index 0000000..8ffc797 --- /dev/null +++ b/pages/points/log.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/pages/refund/apply.vue b/pages/refund/apply.vue new file mode 100644 index 0000000..659924e --- /dev/null +++ b/pages/refund/apply.vue @@ -0,0 +1,429 @@ + + + + + diff --git a/pages/refund/detail.vue b/pages/refund/detail.vue new file mode 100644 index 0000000..027b57f --- /dev/null +++ b/pages/refund/detail.vue @@ -0,0 +1,483 @@ + + + + + diff --git a/pages/refund/index.vue b/pages/refund/index.vue new file mode 100644 index 0000000..9eb70b3 --- /dev/null +++ b/pages/refund/index.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/pages/search/index.vue b/pages/search/index.vue new file mode 100644 index 0000000..6ec900f --- /dev/null +++ b/pages/search/index.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/pages/user/bind/index.vue b/pages/user/bind/index.vue new file mode 100644 index 0000000..52d57a2 --- /dev/null +++ b/pages/user/bind/index.vue @@ -0,0 +1,311 @@ + + + + + diff --git a/pages/user/index.vue b/pages/user/index.vue new file mode 100644 index 0000000..f62ac48 --- /dev/null +++ b/pages/user/index.vue @@ -0,0 +1,637 @@ + + + + + diff --git a/pages/user/personal/index.vue b/pages/user/personal/index.vue new file mode 100644 index 0000000..4110c0b --- /dev/null +++ b/pages/user/personal/index.vue @@ -0,0 +1,240 @@ + + + + + + diff --git a/pages/wallet/balance/log.vue b/pages/wallet/balance/log.vue new file mode 100644 index 0000000..9192ae9 --- /dev/null +++ b/pages/wallet/balance/log.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/pages/wallet/index.vue b/pages/wallet/index.vue new file mode 100644 index 0000000..3a4bdce --- /dev/null +++ b/pages/wallet/index.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/pages/wallet/recharge/index.vue b/pages/wallet/recharge/index.vue new file mode 100644 index 0000000..b061bf2 --- /dev/null +++ b/pages/wallet/recharge/index.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/pages/wallet/recharge/order.vue b/pages/wallet/recharge/order.vue new file mode 100644 index 0000000..ede8ed9 --- /dev/null +++ b/pages/wallet/recharge/order.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/static/app-plus/privacy.html b/static/app-plus/privacy.html new file mode 100644 index 0000000..1c3c7c6 --- /dev/null +++ b/static/app-plus/privacy.html @@ -0,0 +1,746 @@ + + + + + + 隐私政策 + + + + + + + + +
+

隐私政策

+

+   + 在使用萤火商城各项产品或服务前,请您务必仔细阅读并透彻理解本政策,特别是以粗体或下划线标识的条款,您应重点阅读,在确认充分理解并同意后再开始使用。如对本政策内容有任何疑问、意见或建议,您可通过萤火商城提供的各种联系方式与我们联系。 +

+

+
+

+

+   + 您的信任对我们非常重要,我们深知个人信息对您的重要性,我们将按法律法规要求,采取相应安全保护措施,尽力保护您的个人信息安全可控。鉴于此,萤火商城服务提供者(或简称“我们”或“萤火商城”)制定本《隐私政策》(下称“本政策/本隐私政策”)并提醒您:本政策适用于萤火商城提供的所有产品和服务及我们的关联公司的产品或服务(如萤火商城小程序),您可使用萤火商城平台帐号登录上述产品或服务,如上述产品或服务未设独立隐私政策的,则本政策同样适用于该部分产品或服务。 +

+ +

+   需要特别说明的是,本政策不适用于其他第三方向您提供的服务,也不适用于萤火商城中已另行独立设置隐私政策的产品或服务。 +

+

+   第一部分 定义 +

+

+   萤火商城服务提供者:烟台晴好网络科技有限公司 +

+

+   个人信息:指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。 +

+

+   个人敏感信息:指包括身份证件号码、个人生物识别信息、银行账号、财产信息、行踪轨迹、交易信息、14岁以下(含)儿童信息个人信息(我们将在本隐私政策中对具体个人敏感信息以粗体进行显著标识)。 +

+

+   个人信息删除:指在实现日常业务功能所涉及的系统中去除个人信息的行为,使其保持不可被检索、访问的状态。 +

+

+   儿童:指不满十四周岁的未成年人。 +

+

+   除另有约定外,本政策所用定义与萤火商城平台用户协议中的定义具有相同的涵义。 +

+

+   第二部分 隐私政策 +

+

+   本隐私政策部分将帮助您了解以下内容: +

+

+   一、我们如何收集和使用您的信息 +

+

+   二、我们如何使用Cookie +

+

+   三、我们如何共享、转让、公开披露您的信息 +

+

+   四、我们如何存储您的信息 +

+

+   五、我们如何保护您的信息 +

+

+   六、您如何管理您的信息 +

+

+   七、我们如何处理未成年人的信息 +

+

+   八、您的信息如何在全球范围转移 +

+

+   九、本隐私政策如何更新 +

+

+   十、如何联系我们 +

+

+   一、我们如何收集和使用您的信息 +

+

+   在您使用我们的产品及/或服务时,我们需要/可能需要收集和使用的您的个人信息包括如下两种: +

+

+   + 1、为实现向您提供我们产品及/或服务的基本功能,我们将按照《网络安全法》的规定收集、使用您的个人信息。如您拒绝提供相应信息,您将无法正常使用我们的产品及/或服务,但不影响您浏览我们的网页及客户端页面; +

+

+   + 2、为实现向您提供我们产品及/或服务的附加功能,您可选择授权我们收集、使用您的个人敏感信息。如您拒绝提供,您将无法正常使用相关附加功能或无法达到我们拟达到的功能效果,但并不会影响您正常使用我们产品及/或服务的基本功能。 +

+

+   您理解并同意: +

+

+   + 1、我们致力于打造多样的产品和服务以满足您的需求。因我们向您提供的产品和服务种类众多,且不同用户选择使用的具体产品/服务范围存在差异,因此基本/附加功能及收集使用的个人信息类型、范围会有所区别,请以具体的产品/服务功能为准; +

+

+   + 2、为给您带来更好的产品和服务体验,我们在持续努力改进我们的技术,随之我们可能会不时推出新的或优化后的功能,可能需要重新收集、使用您的个人信息或变更个人信息使用目的或方式。对此,我们将通过更新本政策、弹窗、页面提示方式另行向您说明对应信息的收集目的、范围及使用方式,并征求您的明示同意。在此过程中,如果您有任何疑问、意见或建议的,您可通过萤火商城提供的各种联系方式与我们联系,我们会尽快为您作出解答。 +

+

+   我们会为实现本政策下述的各项功能,收集和使用您的个人信息: +

+

+   (一) 帮助您成为我们的会员 +

+

+   1、基础会员服务 +

+

+   注册成为会员并使用我们的会员服务,您需要提供手机号码、拟使用的会员名和密码用于创建萤火商城平台账户。如果您仅需使用浏览、搜索服务,您不需要注册成为我们的会员及提供上述信息。 +

+

+   + 在您登录帐户时,我们可能会根据您提供的上述信息校验您的会员身份,确保我们是在为您本人提供服务。若存在依法需确定您会员身份的场景(包括依法为您扣缴税费、行政执法或司法诉讼中相关主体认定等)时,您授权我们可获取您对应支付账户的相关认证信息用于上述目的。 +

+

+   我们会根据您的会员账户使用情况为您提供相应会员所对应的基本权益。 +

+

+   2、附加会员服务 +

+

+   如果您选择提供真实姓名、性别、出生年月日、居住地个人信息,我们可以为您提供更加个性化的会员服务。为保证交易辨识度,您的帐户昵称、头像将公开显示。 +

+

+   + 授权登录:经您同意后我们向第三方共享您的账户信息(如微信,我们可能会共享您的头像、昵称),使您可以便捷地以萤火商城平台帐户实现第三方平台的注册或登录。此外,我们可能会根据您的授权从第三方处获取您的第三方账户信息,并与您的萤火商城平台账户进行绑定,使您可通过第三方账户直接登录、使用我们的产品及/或服务。我们将在您授权同意的范围内使用您的相关信息。 +

+

+   + 3、账户信息展示:如果您已拥有萤火商城平台账户,我们可能会在萤火商城平台服务中显示您的上述个人信息(实人认证仅显示认证是否通过的结果),以及您在萤火商城平台上或与萤火商城平台账户相关联的产品和服务中执行的操作(您可通过萤火商城平台账户在我们提供的手机萤火商城入口或其他产品/服务入口使用我们及/或关联公司提供的产品或服务),包括通过萤火商城平台账户集中展示您的个人资料、优惠权益、交易订单。我们会尊重您对萤火商城平台服务和萤火商城平台账户设置所做的选择。 +

+

+   (二) 为您提供商品或服务信息展示 +

+

+   在您使用我们服务过程中,为识别账号异常状态、了解产品适配性、向您提供更契合您需求的页面展示和搜索结果,我们可能会自动收集您的使用情况并存储为网络日志信息,包括: +

+

+   + 设备信息:我们会根据您在软件安装及/或使用中的具体操作,接收并记录您所使用的设备相关信息(包括设备型号、操作系统版本、设备设置、唯一设备标识符、设备环境软硬件特征信息)、设备所在位置相关信息(包括您授权的GPS位置以及WLAN接入点)。 +

+

+   + 服务日志信息:当您使用我们的网站或客户端提供的产品或服务时,我们会自动收集您对我们服务的详细使用情况,作为服务日志保存,包括浏览、点击查看、搜索查询、收藏、添加至购物车、交易、售后、关注分享信息、发布信息,以及IP地址、浏览器类型、电信运营商、使用语言、访问日期和时间。 +

+

+   + 请注意,单独的设备信息、服务日志信息是无法识别特定自然人身份的信息。如果我们将这类非个人信息与其他信息结合用于识别特定自然人身份,或者将其与个人信息结合使用,则在结合使用期间,这类非个人信息将被视为个人信息,除取得您授权或法律法规另有规定外,我们会将这类信息做匿名化、去标识化处理。 +

+

+   如果您不想接受我们给您发送的商业广告,您可按照信息中提示的退订步骤进行退订或与我们的客服联系进行退订。在您使用我们提供的站内搜索服务时,我们也同时提供了不针对您个人偏好的商品、服务。 +

+

+   此外,我们也会为了不断改进和优化上述的功能来使用您的上述信息。 +

+

+   (三) 为您提供收藏、加购、关注与分享功能 +

+

+   + 在您浏览我们客户端的过程中,您可以选择对感兴趣的商品及/或服务进行收藏、添加至购物车、与您感兴趣的商家/品牌建立关注关系、通过我们提供的功能组件向其他第三方分享信息。在您使用上述功能的过程中,我们会收集包括您的收藏及添加购物车的记录、关注关系、分享历史在内的服务日志信息用于实现上述功能及其他我们明确告知的目的。 +

+

+   (四)帮助您完成下单及订单管理 +

+

+   + 当您在我们的产品及/或服务中订购具体商品及/或服务时,我们会通过系统为您生成购买该商品及/或服务的订单。在下单过程中,您需至少提供您的收货人姓名、收货地址、收货人联系电话,同时该订单中会载明您所购买的商品及/或服务信息、具体订单号、订单创建时间、您应支付的金额,我们收集这些信息是为了帮助您顺利完成交易、保障您的交易安全、查询订单信息、提供客服与售后服务及其他我们明确告知的目的。 +

+

+   您可以通过萤火商城为其他人订购商品及/或服务,您需要提供该实际订购人的前述个人信息,若其中涉及儿童个人信息的,您需在提供前征得对应儿童监护人的同意。 +

+

+   为便于您了解查询订单信息并对订单信息进行管理,我们会收集您在使用我们服务过程中产生的订单信息用于向您展示及便于您对订单进行管理。 +

+

+   您可额外填写/选择包括其他联系电话、收货时间在内的更多附加信息以确保商品或服务的准确送达。 +

+

+   (五) 帮助您完成支付 +

+

+   + 为完成订单支付,您需要提供支付账户并选择付款方式,我们会将您的萤火商城平台账户会员名、对应的支付账户会员名、订单支付相关信息及其他反洗钱法律要求的必要信息与第三方支付公司共享。如您选择由其他金融机构为您提供支付服务的,我们或我们的关联公司、合作伙伴还会将您的包括银行卡号、有效期在内的银行卡支付必要信息与您选择的相应金融机构共享。 +

+

+   您可以请求其他人为您付款,那么您需要提供代付人的支付账户及/或手机号码。 +

+

+   为使我们及时获悉并确认您的支付进度及状态,为您提供售后与争议解决服务,您同意我们可自您所选择的交易对象、支付公司或您选择的其他金融机构处收集与支付进度相关信息。 +

+

+   (六) 帮助向您完成商品或服务的交付 +

+

+   + 为保证您购买的商品及/或服务能够顺利、安全、准确送达,我们会向为萤火商城平台提供物流信息系统和技术服务的物流配送公司披露订单相关配送信息,并由其根据商品及/或服务提供主体的指定向相应的物流配送主体同步相关配送信息。您知晓并同意相应物流配送主体不可避免地获知及使用您的配送信息,用于完成交付目的。 +

+

+   为使我们及时获悉并确认交付进度及状态,向您提供售后与争议解决服务,您同意我们可自物流相关服务主体处收集与交付进度相关信息。 +

+

+   (七) 客服及争议处理 +

+

+   当您与我们联系或提出售中售后、争议纠纷处理申请时,为了保障您的账户及系统安全,我们需要您提供必要的个人信息以核验您的会员身份。 +

+

+   + 为便于与您联系、尽快帮助您解决问题或记录相关问题的处理方案及结果,我们可能会保存您与我们的通信/通话记录及相关内容(包括账号信息、订单信息、您为了证明相关事实提供的其他信息,或您留下的联系方式信息),如果您针对具体订单进行咨询、投诉或提供建议的,我们会使用您的账号信息和订单信息。 +

+

+   (八) 为您提供安全保障 +

+

+   + 为提高您使用我们及我们关联公司、合作伙伴提供服务的安全性,保护您或其他用户或公众的人身财产安全免遭侵害,更好地预防钓鱼网站、欺诈、网络漏洞、计算机病毒、网络攻击、网络侵入安全风险,更准确地识别违反法律法规或萤火商城相关协议规则的情况,我们可能使用或整合您的会员信息、交易信息、设备信息、服务日志信息以及我们关联公司、合作伙伴取得您授权或依据法律共享的信息,来综合判断您账户及交易风险、进行身份验证、检测及防范安全事件,并依法采取必要的记录、审计、分析、处置措施。 +

+

+   (九)为您提供其他附加服务 +

+

+   + 为向您提供更便捷、更优质、个性化的产品及/或服务,努力提升您的体验,我们在向您提供的以下附加服务中可能会收集、缓存和使用您的个人信息。如果您不提供这些信息,不会影响您使用萤火商城的浏览、搜索基本服务,但您可能无法获得这些附加服务给您带来的用户体验。这些附加服务包括: +

+

+   1、基于相机/摄像头的附加服务:您可在开启相机(摄像头)权限后使用该功能。即使您已同意开启相机(摄像头)权限,我们也仅会在您主动点击相应图标或录制视频时通过相机获取照片信息。。 +

+

+   2、基于读取、写入外置存储卡的附加服务:您可以在开启存储权限后,使用该功能上传您的照片/图片/视频,以实现发表评论/分享或与客服沟通提供证明等功能。 +

+

+   3.基于读取手机状态和身份信息的附加服务:我们将在您开启权限后收集您的IMEI、MSI、设备MAC地址、软件列表、设备序列号、android ID,根据您的设备确认您的账户安全,实现安全风控。 +

+

+   4、基于查看WLAN连接的附加功能:查看wifi连接状态,确保网络可靠性以及大流量场景下提示用户流量使用。 +

+

+   5、基于检索正在运行应用的附加功能:此功能可以使您在浏览萤火商城商品、服务时跳转到第三方APP的指定页面。 +

+

+   + 6、基于相册(图片库/视频库)的图片/视频访问及上传、外部存储、缓存、发布音视频功能的附加服务:您可在开启相册权限后使用该功能上传您的照片/图片/视频,发表评论/分享、拍照购物或与客服沟通提供证明等功能。我们可能会通过您所上传的照片/图片来识别您需要购买的商品或服务,或使用包含您所上传照片或图片的评论信息。您如果拒绝授权提供,将无法使用此功能,但不影响您正常使用萤火商城的其他功能。 +

+

+   + 7、基于麦克风的语音技术相关附加服务:您可在开启麦克风权限后使用麦克风实现语音购物功能,或与客服联系或与客服机器人实现语音交互,在这些功能中我们会收集您的录音内容以识别您的购物需求,或响应您的客服及争议处理等需求。请您知晓,即使您已同意开启麦克风权限,我们也仅会在您主动点击客户端内麦克风图标或录制视频时通过麦克风获取语音信息。 +

+

+   8、基于安装应用的附加服务:平台会定期更新应用的客户端,您可以在更新的时候,打开APP直接进行新版本的更新安装及使用。 +

+

+   9、基于应用程序获得当前运行任务的信息的附加服务:平台在统计APP数据的情况下,会对APP的页面进行判断。 +

+

+   10、基于保持APP在唤醒状态下的附加服务:您在本应用观看视频的情况下,需要保持应用为唤醒的状态。 +

+

+   您理解并同意,上述附加服务可能需要您在您的设备中开启您的位置信息 +

+

+   + (地理位置)、摄像头(相机)、相册(图片库)、麦克风(语音)及/或日历、外部存储的访问权限,以实现这些权限所涉及信息的收集和使用。请您注意,您开启任一权限即代表您授权我们可以收集和使用相关个人信息来为您提供对应服务,您一旦关闭任一权限即代表您取消了授权,我们将不再基于对应权限继续收集和使用相关个人信息,也无法为您提供该权限所对应的服务。但是,您关闭权限的决定不会影响此前基于您的授权所进行的信息收集及使用。 +

+

+   (十)其他 +

+

+   + 1、若你提供的信息中含有其他用户的个人信息,在向萤火商城提供这些个人信息之前,您需确保您已经取得合法的授权。若其中涉及儿童个人信息的,您需在发布前取得对应儿童监护人的同意,前述情形下监护人有权通过本政策第十条的途径联系我们,要求更正或删除涉及儿童个人信息的内容。 +

+

+   2、若我们将信息用于本政策未载明的其他用途,或者将基于特定目的收集而来的信息用于其他目的,或者我们主动从第三方处获取您的个人信息,均会事先获得您的同意。 +

+

+   + 若我们从第三方处间接获取您的信息的,我们会在收集前明确以书面形式要求该第三方在已依法取得您同意后收集个人信息,并向您告知共享的信息内容,且涉及敏感信息的在提供给我们使用前需经过您的明确确认,要求第三方对个人信息来源的合法性和合规性作出承诺,如第三方有违反行为的,我们会明确要求对方承担相应法律责任;同时,我们对个人信息会进行安全加固(包括敏感信息报备、敏感信息加密存储、访问权限控制)。我们会使用不低于我们对自身用户个人信息同等的保护手段与措施对间接获取的个人信息进行保护。 +

+

+   3、征得授权同意的例外 +

+

+   您充分理解并同意,我们在以下情况下收集、使用您的个人信息无需您的授权同意,且我们可能不会响应您提出的更正/修改、删除、注销、撤回同意、索取信息的请求: +

+

+   (1)与国家安全、国防安全有关的; +

+

+   (2)与公共安全、公共卫生、重大公共利益有关的; +

+

+   (3)与犯罪侦查、起诉、审判和判决执行等司法或行政执法有关的; +

+

+   (4)出于维护您或其他个人的生命、财产相关的重大合法权益但又很难得到本人同意的; +

+

+   (5)您自行向社会公众公开的个人信息; +

+

+   (6)从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。 +

+

+   (7)根据与您签订和履行相关协议或其他书面文件所必需的; +

+

+   (8)用于维护所提供的产品及/或服务的安全稳定运行所必需的,例如发现、处置产品及/或服务的故障; +

+

+   (9)为合法的新闻报道所必需的; +

+

+   (10)学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的; +

+

+   (11)法律法规规定的其他情形。 +

+

+   + 请知悉,根据适用的法律,若我们对个人信息采取技术措施和其他必要措施进行处理,使得数据接收方无法重新识别特定个人且不能复原,或我们可能会对收集的信息进行去标识化地研究、统计分析和预测,用于改善萤火商城的内容和布局,为商业决策提供产品或服务支撑,以及改进我们的产品和服务(包括使用匿名数据进行机器学习或模型算法训练),则此类处理后数据的使用无需另行向您通知并征得您的同意。 +

+

+   + 4、如我们停止运营萤火商城产品或服务,我们将及时停止继续收集您个人信息的活动,将停止运营的通知以逐一送达或公告的形式通知您,并对我们所持有的与已关停业务相关的个人信息进行删除或匿名化处理。涉及儿童个人信息的,我们会并将停止运营的通知及时告知儿童监护人。 +

+

+   二、我们如何使用Cookie +

+

+   + 为使您获得更轻松的访问体验、向您推荐您可能感兴趣的内容,我们会在您的移动设备上存储名为Cookie的小数据文件。Cookie通常包含标识符、站点名称以及一些号码和字符。借助Cookie,我们能够存储您的账户信息、商品记录、订单记录、商品的数据。 +

+

+   若您不同意我们在您的移动设备上存储Cookie的小数据文件,您可停止使用萤火商城。 +

+

+   三、我们如何共享、转让、公开披露您的信息 +

+

+   (一)共享 +

+

+   我们不会与萤火商城服务提供者以外的公司、组织和个人共享您的个人信息,但以下情况除外: +

+

+   1、在法定情形下的共享:我们可能会根据法律法规规定、诉讼、争议解决需要,或按行政、司法机关依法提出的要求,对外共享您的个人信息。 +

+

+   2、在获取明确同意的情况下共享:获得您的明确同意后,我们会与其他方共享您的个人信息。 +

+

+   3、在您主动选择情况下共享:您通过萤火商城平台购买商品或服务,我们会根据您的选择,将您的订单信息中与交易有关的必要信息共享给相关商品或服务的提供者,以实现您的交易及售后服务需求。 +

+

+   + 4、与关联公司间共享:为便于我们基于萤火商城平台账户向您提供产品和服务,推荐您可能感兴趣的信息,识别会员账号异常,保护萤火商城关联公司或其他用户或公众的人身财产安全免遭侵害,您的个人信息可能会与我们的关联公司和/或其指定的服务提供商共享。我们只会共享必要的个人信息,且受本隐私政策中所声明目的的约束,如果我们共享您的个人敏感信息或关联公司改变个人信息的使用及处理目的,将再次征求您的授权同意。 +

+

+   + 5、与授权合作伙伴共享:我们可能委托授权合作伙伴为您提供某些服务或代表我们履行职能,我们仅会出于本隐私政策声明的合法、正当、必要、特定、明确的目的共享您的信息,授权合作伙伴只能接触到其履行职责所需信息,且不得将此信息用于其他任何目的。对于涉及儿童个人信息的,我们不允许合作伙伴进行转委托。 +

+

+   目前,我们的授权合作伙伴包括以下类型: +

+

+   + (1)广告、分析服务类的授权合作伙伴。我们会委托这些合作伙伴处理与广告覆盖面和有效性相关的信息,但不会提供您的个人身份信息,或者我们将这些信息进行去标识化处理,以便它不会识别您个人。这类合作伙伴可能将上述信息与他们合法获取的其他数据相结合,以执行我们委托的广告服务或决策建议,帮助其在不识别您个人身份的前提下提升广告有效触达率。 +

+

+   + (2)供应商、服务提供商和其他合作伙伴。我们将信息发送给支持我们业务的供应商、服务提供商和其他合作伙伴,这些支持包括受我们委托提供的技术基础设施服务、分析我们服务的使用方式、衡量广告和服务的有效性、提供客户服务、支付便利或进行学术研究和调查,这些信息难以与您的身份信息相关联,这些信息将帮助我们分析、衡量广告和相关服务的有效性。 +

+

+   + 为保障我们客户端的稳定运行、功能实现,使您能够使用和享受更多的服务及功能,我们的应用中会嵌入授权合作伙伴的SDK。我们会对授权合作伙伴获取有关信息的应用程序接口(API)、软件工具开发包(SDK)进行严格的安全检测,并与授权合作伙伴约定严格的数据保护措施,令其按照本政策以及其他任何相关的保密和安全措施来处理个人信息。 +

+

+   (二)转让 +

+

+   我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外: +

+

+   1、在获取明确同意的情况下转让:获得您的明确同意后,我们会向其他方转让您的个人信息; +

+

+   + 2、在萤火商城服务提供者发生合并、收购或破产清算情形,或其他涉及合并、收购或破产清算情形时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本政策的约束,否则我们将要求该公司、组织和个人重新向您征求授权同意。 +

+

+   (三)公开披露 +

+

+   我们仅会在以下情况下,公开披露您的个人信息: +

+

+   1、获得您明确同意或基于您的主动选择,我们可能会公开披露您的个人信息; +

+

+   + 2、如果我们确定您出现违反法律法规或严重违反萤火商城平台相关协议及规则的情况,或为保护萤火商城平台用户或公众的人身财产安全免遭侵害,我们可能依据法律法规或征得您同意的情况下披露关于您的个人信息,包括相关违规行为以及萤火商城平台已对您采取的措施。 +

+

+   (四)共享、转让、公开披露个人信息时事先征得授权同意的例外 +

+

+   以下情形中,共享、转让、公开披露您的个人信息无需事先征得您的授权同意: +

+

+   1、与国家安全、国防安全有关的; +

+

+   2、与公共安全、公共卫生、重大公共利益有关的; +

+

+   3、与犯罪侦查、起诉、审判和判决执行等司法或行政执法有关的; +

+

+   4、出于维护您或其他个人的生命、财产相关的重大合法权益但又很难得到本人同意的; +

+

+   5、您自行向社会公众公开的个人信息; +

+

+   6、从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开。 +

+

+   请知悉,根据适用的法律,若我们对个人信息采取技术措施和其他必要措施进行处理,使得数据接收方无法重新识别特定个人且不能复原,则此类处理后数据的共享、转让、公开披露无需另行向您通知并征得您的同意。 +

+

+   四、我们如何存储您的信息 +

+

+   (一)存储地点 +

+

+   我们在中华人民共和国境内运营中收集和产生的个人信息,将存储在中国境内。以下情形下,我们会在履行了法律规定的义务后,向境外实体提供您的个人信息: +

+

+   1、适用的法律有明确规定; +

+

+   2、获得您的明确授权; +

+

+   3、您通过互联网进行跨境交易等个人主动行为。 +

+

+   针对以上情形,我们会通过合同等形式确保以不低于本政策规定的程度保护您的个人信息。 +

+

+   (二)存储期限 +

+

+   + 我们会采取合理可行的措施,尽力避免收集和处理无关的个人信息。我们只会在达成本政策所述目的所需的期限内保留您的个人信息,除非法律有强制的留存要求,例如《中华人民共和国电子商务法》要求商品和服务信息、交易信息保存时间自交易完成之日起不少于三年。我们判断个人信息的存储期限主要参考以下标准并以其中较长者为准: +

+

+   1、完成与您相关的交易目的、维护相应交易及业务记录,以应对您可能的查询或投诉; +

+

+   2、保证我们为您提供服务的安全和质量; +

+

+   3、您是否同意更长的留存期间; +

+

+   4、是否存在保留期限的其他特别约定。 +

+

+   + 在超出保留期间后,我们会在15天内根据适用法律的要求删除您的个人信息,或使其匿名化处理。此外,在注销账户后,我们将在15天内停止为您提供产品和服务,并根据您的要求,删除您的个人信息或匿名化处理,法律法规另有规定的除外。 +

+

+   五、我们如何保护您的信息 +

+

+   (一)我们已采取符合业界标准、合理可行的安全防护措施保护您的信息,防止个人信息遭到未经授权访问、公开披露、使用、修改、损坏或丢失。例如,在您的浏览器与服务器之间交换数据时受 +

+

+   + SSL协议加密保护;我们同时对萤火商城网站提供HTTPS协议安全浏览方式;我们会使用加密技术提高个人信息的安全性;我们会使用受信赖的保护机制防止个人信息遭到恶意攻击;我们会部署访问控制机制,尽力确保只有授权人员才可访问个人信息;以及我们会举办安全和隐私保护培训课程,加强员工对于保护个人信息重要性的认识。 +

+

+   + (二)我们会采取合理可行的措施,尽力避免收集无关的个人信息。我们只会在达成本政策所述目的所需的期限内保留您的个人信息,除非法律有强制的存留要求,例如《中华人民共和国电子商务法》要求商品和服务信息、交易信息保存时间自交易完成之日起不少于三年。而我们判断前述期限的标准包括: +

+

+   1、完成与您相关的交易目的、维护相应交易及业务记录、应对您可能的查询或投诉; +

+

+   2、保证我们为您提供服务的安全和质量; +

+

+   3、您是否同意更长的留存期间; +

+

+   4、是否存在保留期限的其他特别约定。 +

+

+   在您的个人信息超出保留期间后,我们会根据适用法律的要求删除您的个人信息,或使其匿名化处理。 +

+

+   + (三)互联网并非绝对安全的环境,使用萤火商城平台服务时,我们强烈建议您不要使用非萤火商城平台推荐的通信方式发送您的信息。您可以通过我们的服务建立联系和相互分享。当您通过我们的服务创建交流、交易或分享时,您可以自主选择沟通、交易或分享的对象,作为能够看到您的交易内容、联络方式、交流信息或分享内容相关信息的第三方。 +

+

+   + 在使用萤火商城服务进行网上交易时,您不可避免地要向交易对方或潜在的交易对方披露自己的个人信息,如联络方式或联系地址。请您妥善保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息尤其是您的账户或密码发生泄露,请您立即联络萤火商城客服,以便我们根据您的申请采取相应措施。 +

+

+   请注意,您在使用我们服务时自愿共享甚至公开分享的信息,可能会涉及您或他人的个人信息甚至个人敏感信息。请您更加谨慎地考虑,是否在使用我们的服务时共享甚至公开分享相关信息。 +

+

+   + 请使用复杂密码,协助我们保证您的账号安全。我们将尽力保障您发送给我们的任何信息的安全性。如果我们的物理、技术或管理防护设施遭到破坏,导致信息被非授权访问、公开披露、篡改或毁坏,导致您的合法权益受损,我们将承担相应的法律责任。 +

+

+   (四)我们将不定期更新并公开安全风险、个人信息安全影响有关内容,您可通过萤火商城公告方式获得。 +

+

+   + (五)在不幸发生个人信息安全事件后,我们将按照法律法规的要求向您告知:安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施。事件相关情况我们将以邮件、信函、电话、推送通知的方式告知您,难以逐一告知个人信息主体时,我们会采取合理、有效的方式发布公告。 +

+

+   + 在不幸发生儿童个人信息安全事件后,我们将按照法律法规的要求,及时向儿童及其监护人告知:安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、儿童及其监护人可自主防范和降低风险的建议、对儿童及其监护人的补救措施。我们将及时将事件相关情况以APP推送通知、发送邮件/短消息告知儿童及其监护人。难以逐一告知时,我们会采取合理、有效的方式发布相关警示信息。同时,我们还将按照监管部门要求,主动上报儿童个人信息安全事件的处置情况。若儿童及其监护人的合法权益受损,我们将依法承担相应的法律责任。同时,我们还将按照监管部门要求,上报个人信息安全事件的处置情况。 +

+

+   六、您如何管理您的信息 +

+

+   您可以通过以下方式访问及管理您的信息: +

+

+   (一)查询、更正和补充您的信息 +

+

+   您有权查询、更正或补充您的信息。您可以通过以下方式自行进行: +

+

+   1、登录手机萤火商城客户端,进入“我的”页面,点击页面右上方设置的图标查询、更正个人资料及个人账户相关信息; +

+

+   (二)删除您的信息 +

+

+   您可以通过“(一)查询、更正和补充您的信息”中列明的方式注销您的部分信息。 +

+

+   在以下情形中,您可以向我们提出删除个人信息的请求: +

+

+   1、如果我们处理个人信息的行为违反法律法规; +

+

+   2、如果我们收集、使用您的个人信息,却未征得您的明确同意; +

+

+   3、如果我们处理个人信息的行为严重违反了与您的约定; +

+

+   4、如果您不再使用我们的产品或服务,或您主动注销了账号; +

+

+   5、如果我们永久不再为您提供产品或服务。 +

+

+   若我们决定响应您的删除请求,我们还将同时尽可能通知从我们处获得您的个人信息的主体,并要求其及时删除(除非法律法规另有规定,或这些主体已独立获得您的授权)。 +

+

+   当您或我们协助您删除相关信息后,因为适用的法律和安全技术,我们可能无法立即从备份系统中删除相应的信息,我们将安全地存储您的个人信息并将其与任何进一步处理隔离,直到备份可以清除或实现匿名。 +

+

+   (三)改变您授权同意的范围 +

+

+   + 如果您不再选择将您的个人信息提供给我们或拒绝使用我们提供的部分服务,您可以通过设置您的智能移动设备关闭您授权给萤火商城的部分权限。以华为手机为例:通过手机中的“设置--隐私--权限管理”,选择关闭部分或全部您已授权给我们的权限。 +

+

+   当您收回同意后,我们将不再处理相应的个人信息。但您收回同意的决定,不会影响此前基于您的授权而开展的个人信息处理。 +

+

+   (四)约束信息系统自动决策 +

+

+   + 在某些业务功能中,我们可能仅依据信息系统、算法在内的非人工自动决策机制做出决定。如果这些决定显著影响您的合法权益,您有权要求我们做出解释,我们也将在不侵害萤火商城商业秘密或其他用户权益、社会公共利益的前提下提供申诉方法。 +

+

+   (五)响应您的上述请求 +

+

+   为保障安全,您可能需要提供书面请求,或以其他方式证明您的身份。我们可能会先要求您验证自己的身份,然后再处理您的请求。 +

+

+   我们将在15天内做出答复。如您不满意,还可以通过萤火商城客服发起投诉。 +

+

+   + 对于您合理的请求,我们原则上不收取费用,但对多次重复、超出合理限度的请求,我们将酌情收取一定费用。对于与您的身份不直接关联的信息、无端重复信息,或者需要过多技术手段(例如,需要开发新系统或从根本上改变现行惯例)、给他人合法权益带来风险或者不切实际的请求,我们可能会予以拒绝。 +

+

+   在以下情形中,按照法律法规要求,我们将无法响应您的请求: +

+

+   1、与国家安全、国防安全有关的; +

+

+   2、与公共安全、公共卫生、重大公共利益有关的; +

+

+   3、与犯罪侦查、起诉、审判和执行判决有关的; +

+

+   4、有充分证据表明个人信息主体存在主观恶意或滥用权利的; +

+

+   5、响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的; +

+

+   6、涉及商业秘密的。 +

+

+   七、我们如何处理未成年人的信息 +

+

+   在电子商务活动中我们推定您具有相应的民事行为能力。如您为未成年人,我们要求您请您的父母或其他监护人仔细阅读本隐私政策,并在征得您的父母或其他监护人同意的前提下使用我们的服务或向我们提供信息。 +

+

+   + 如果我们识别出您是儿童用户的,请您告知我们您的监护人信息,我们将通知您的监护人并要求您的监护人同意儿童个人信息收集使用规则。对于经父母或其他监护人同意使用我们的产品或服务而收集儿童个人信息的情况,我们只会在法律法规允许、父母或其他监护人明确同意或者保护儿童所必要的情况下使用、共享、转让或披露此信息。 +

+

+   若您是儿童的父母或其他监护人,请您关注您监护的儿童是否是在取得您的授权同意之后使用我们的服务的。如您对您所监护的儿童的个人信息有疑问,请通过第九节中的联系方式与我们联系。 +

+

+   八、您的信息如何在全球范围转移 +

+

+   我们在中华人民共和国境内运营中收集和产生的个人信息,存储在中国境内,以下情形除外: +

+

+   1、适用的法律有明确规定; +

+

+   2、获得您的明确授权; +

+

+   3、您通过互联网进行跨境交易的个人主动行为。 +

+

+   针对以上情形,我们会确保依据本隐私政策对您的个人信息提供足够的保护。 +

+

+   九、本隐私政策如何更新 +

+

+   我们的隐私政策可能变更。 +

+

+   未经您明确同意,我们不会限制您按照本隐私政策所应享有的权利。我们会在App上发布对隐私政策的更新。 +

+

+   对于重大变更,我们还会提供更为显著的通知(包括我们会通过萤火商城公示的方式进行通知甚至向您提供弹窗提示)。 +

+

+   本政策所指的重大变更包括但不限于: +

+

+   1、我们的服务模式发生重大变化。如处理个人信息的目的、处理的个人信息类型、个人信息的使用方式; +

+

+   2、我们在控制权方面发生重大变化。如并购重组引起的信息控制者变更; +

+

+   3、个人信息共享、转让或公开披露的主要对象发生变化; +

+

+   4、您参与个人信息处理方面的权利及其行使方式发生重大变化; +

+

+   5、我们负责处理个人信息安全的责任部门、联络方式及投诉渠道发生变化; +

+

+   6、个人信息安全影响评估报告表明存在高风险。 +

+

+   十、如何联系我们 +

+

+   您可以通过以下方式与我们联系,我们将在15天内回复您的请求: +

+

+   1、如对本政策内容、儿童个人信息有任何疑问、意见或建议,您可通过萤火商城客服邮箱:admin@yiovo.com与我们联系。 +

+

+   2、如发现个人信息可能被泄露,您可以联系我们的客服人员进行投诉举报; +

+

+   如果您对我们的回复不满意,特别是您认为我们的个人信息处理行为损害了您的合法权 +

+

+    +

+
+ + \ No newline at end of file diff --git a/static/app-plus/protocol.html b/static/app-plus/protocol.html new file mode 100644 index 0000000..f7a36d1 --- /dev/null +++ b/static/app-plus/protocol.html @@ -0,0 +1,604 @@ + + + + + + 服务协议 + + + + + + + + +
+

服务协议

+

+   + 本应用在此特别提醒,在您注册使用本应用提供的软件、小程序、网站等(以下统称“本应用产品和服务”或“本应用”)前,仔细阅读本《服务协议》(下称本协议)中的各个条款,尤其是以粗体或下划线标示的条款,包括但不限于免除或者限制本应用责任的条款、对用户权利进行限制的条款以及约定争议解决方式、司法管辖的条款。您有权同意或者不同意本协议。 +

+

+   +

+

+   + 如您决定注册使用本应用,完成我们的注册流程并通过点击同意的形式在线签署本协议即表示您已充分理解并同意接受本协议所有及任何条款的约束。本协议包括我们根据法律法规规定、本协议制定的其他政策、规则、公告声明以及本协议包含的超链接协议等(除非特有所指,合称为“本协议”)。 +

+

+   +

+

+   +

+

+ 第一条 本应用平台服务 +

+

+   +

+

+ 1. 本协议是由烟台晴好网络科技有限公司的萤火商城APP(简称“本应用”或者“我们”)与用户(或称为“您”)就您使用本应用产品和服务所做的权利义务的约定。 +

+

+   +

+

+   +

+

+ 2. 本应用平台为用户提供以下平台服务: +

+

+   +

+

+ (1)为您展示商品或服务 +

+

+   +

+

+ 本应用会为您展示商品或服务的相关信息,供您浏览、了解所需的商品或服务。 +

+

+   +

+

+ (2)为您推送商品或服务 +

+

+   +

+

+ 本应用会为您制定更符合您喜好和需求的个性化推送商品或服务的页面展示和搜索结果,提升您的使用体验。 +

+

+   +

+

+ (3)为您在购物平台上购买商品或服务提供优惠通道 +

+

+   +

+

+ 当您欲购买在本应用上浏览的商品或服务时,您通过本应用领取优惠券或其他福利后,直接进入销售该商品或服务的淘宝、天猫店铺和其他商家店铺并完成下单流程。 +

+

+   +

+

+ (4)为您提供持续、稳定的技术平台服务 +

+

+   +

+

+     无论用户是否登录APP,本应用将持续、稳定的为您提供技术平台服务,直至您以书面告知或注销账户的方式停止使用本应用服务。 +

+

+   +

+ +

+ 4. + 使用本应用或通过本应用相关链接产生的购买行为应当基于真实的消费需求,不得存在对商品及/或服务实施恶意购买、恶意维权等扰乱电商平台正常交易秩序的行为。若您严重违背社会公德、提供虚假注册身份信息、经我们判定认为存在恶意退货或不合常理的高退货率以及其他不符合正常消费习惯的情形、或经判定认为属于损害本应用不正当行为时,基于维护交易秩序及交易安全的需要,本应用发现上述情形时有权主动执行对订单不予结算、冻结该用户账号等操作。 +

+

+   +

+

+ 5. 为了更好的服务用户,本应用可能会不定时举办各类活动,如果您选择参加活动,应当认真查阅各类活动另行制定的规则。您同意不会采取作弊、利用系统漏洞等手段扰乱本应用秩序。 +

+

+   +

+

+ 第二条 用户账户的注册、使用与注销 +

+

+   +

+

+ 1. + 本应用重视未成年人的安全与个人信息保护,注册本应用的用户应是具备完全民事行为能力和民事权利能力的自然人。用户一旦注册成功,我们将推定您具有相应的民事行为能力。若您不具备前述与您行为相适应的民事行为能力,则您及您的监护人应依照法律规定承担因使用本APP而可能产生的一切法律后果。 +

+

+   +

+

+ 2. + 用户应提供本人正在合法有效使用的手机号码进行“本应用”账号注册,并且您同意本应用将您手机号码及手机设备识别码等作为用户身份识别信息。该号码是我们与您联系的重要渠道,如发生变更,应及时通知我们。否则,可能会因无法有效联系到您,或无法及时处理与您相关的争议等事项而影响您的相关权益。 +

+

+   +

+

+ 3. + 用户注册账号所提供和使用的信息应当真实、有效、合法。注册成功后,本应用将为您配置账户,该账户之所有权本应用所有,您在本协议有效期内仅享有使用权。由于账户关联用户的个人信息及本应用商业信息,该账户仅限您本人使用,未经本应用书面同意,您不得出借、出租或作其它形式的转让。 +

+

+   +

+

+ 4. + 本应用有权根据法律法规或政策规定,或根据自行制定的规范或规则,对违规、不当使用账户等行为予以限制或予以注销,如本应用判断您的账户可能涉及信息泄漏及/或影响本应用信息安全或者正常运营的及/或侵害或影响其他用户权益的,本应用可拒绝向您提供服务或终止本协议。 +

+

+   +

+

+ 5. + 用户可以申请注销已注册的账户。如果您需要注销您的帐户,我们将在核实您的身份、要求您完成未完成的交易、完成资金结算等(例如已充值或已获得的虚拟资产等)后,为您提供帐户注销服务。在账户注销完成后,本应用不在为您提供任何服务。账户注销后,您的个人信息我们会使其在前台系统保持不可检索、访问的状态,或对其进行匿名化处理。您知晓,根据法律法规规定,相关交易信息保存时间应自交易完成之日起不少于三年。 +

+

+   +

+ +

+ 6. + 为使您更好地使用本应用的各项服务,并保障您的账户安全,我们将按照相关法律法规规定要求您完成实名认证。如涉及第三方支付、资金结算或其他可能需要您提供银行卡信息的业务,您应当保证提供的银行帐户信息或第三方支付帐户信息真实、有效、合法,且属于您本人所有,我们及关联公司或我们合作的第三方支付机构将根据您提供的银行帐户信息或第三方支付帐户信息与您进行资金结算。您提供的银行帐户信息或第三方支付帐户信息错误或您拒绝提供,您应当理解您将无法使用本APP部分功能、第三方支付机构提供的支付功能以及您将无法获得结算资金。您理解并同意如因您未能提供有效的上述信息导致资金无法结算时,该部分资金不会因为未支付给您而产生利息。我们可以向您发送通知以便于您可以及时提供信息,若超出一定时间未得到回复,我们有权收回该部分结算资金自行处理。 +

+

+   +

+

+ 7. + 如发现任何未经您授权使用您的账户登录本应用的情形,我们建议您第一时间与我们联系,但您应理解本应用对任何请求采取行动均需要合理时间,且本应用所采取的行动可能无法避免或者阻止侵害结果的形成或者扩大,除本应用存在法定过错外,本应用不对该侵害结果及扩大承担任何责任。 +

+

+   +

+

+   +

+

+   +

+

+ 第三条 用户个人信息收集、使用与保护 +

+

+   +

+

+ 1. 详见《隐私协议》。 +

+

+   +

+

+ 2. 《隐私协议》是本协议正文的主要组成部分,与本协议共同约束用户与本应用。 +

+

+   +

+

+ 3. + 本应用接入第三方SDK初衷是为了向用户提供更成熟、优质的平台技术服务,有关服务可能会涉及到个人信息收集,在收集用户信息之前,本应用会征求您的授权同意。如您对此存在疑问,您可以通过附件链接查阅第三方SDK发布的隐私协议。您的明示授权或继续使用,将视为您同意信息的合理采集以及相关的隐私协议。 +

+

+   +

+

+ 第四条 本应用使用规范 +

+

+   +

+

+ 1. + 用户在注册、使用本应用过程中制作、复制、发布、上传、传播的任何形式的内容,包括但不限于账号头像、昵称、标签、地址等注册信息及认证资料,或以文字、语音、图片、视频、图文等任何形式发送、回复或自动回复的消息、相关链接页面以及其他使用账号或本服务所产生的内容均不得违反现行法律、法规、规章、政策规定。 +

+

+   +

+

+ 2. 如果用户在境外注册、使用本应用,应同时遵守所在地或相关国家(地区)的法律法规。 +

+

+   +

+

+ 3. 用户不得利用“本应用”账号或提供的服务以任何形式制作、复制、发布、上传、传播如下法律、法规和政策禁止的内容: +

+

+   +

+

+ (1) 反对宪法所确定的基本原则的; +

+

+   +

+

+ (2) 危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的; +

+

+   +

+

+ (3) 损害国家荣誉和利益的; +

+

+   +

+

+ (4) 煽动民族仇恨、民族歧视,破坏民族团结的; +

+

+   +

+

+ (5) 破坏国家宗教政策,宣扬邪教和封建迷信的; +

+

+   +

+

+ (6) 散布谣言,扰乱社会秩序,破坏社会稳定的; +

+

+   +

+

+ (7) 散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的; +

+

+   +

+

+ (8) 侮辱或者诽谤他人,侵害他人合法权益的; +

+

+   +

+

+ (9) 含有法律、行政法规禁止的其他内容的信息。 +

+

+   +

+

+ 4.用户不得利用“本应用”账号或服务以任何形式制作、复制、发布、上传、传播如下干扰“本应用”正常运营,以及侵犯其他用户或第三方合法权益的内容: +

+

+   +

+

+ (1) 含有任何性或性暗示的; +

+

+   +

+

+ (2) 含有辱骂、恐吓、威胁内容的; +

+

+   +

+

+ (3) 含有骚扰、垃圾广告、恶意信息、诱骗信息的; +

+

+   +

+

+ (4) 涉及他人隐私、个人信息或资料的; +

+

+   +

+

+ (5) 侵害他人名誉权、肖像权、知识产权、商业秘密等合法权利的; +

+

+   +

+

+ (6) 含有其他干扰本服务正常运营和侵犯其他用户或第三方合法权益内容的信息。 +

+

+   +

+

+ 5. 用户不得利用“本应用”账号或服务进行如下行为: +

+

+   +

+

+ (1) 提交、发布虚假信息,或盗用他人头像或资料,冒充、利用他人名义的; +

+

+   +

+

+ (2) 虚构事实、隐瞒真相以误导、欺骗他人的; +

+

+   +

+

+ (3) 利用技术手段批量建立虚假账号的; +

+

+   +

+

+ (4) 利用“本应用”账号或服务从事任何违法犯罪活动的; +

+

+   +

+

+ (5) 制作、发布与以上行为相关的方法、工具,或对此类方法、工具进行运营或传播,无论这些行为是否为商业目的; +

+

+   +

+

+ (6) 其他违反法律法规规定、侵犯其他用户合法权益、干扰“本应用”正常运营或本应用未明示授权的行为。 +

+

+   +

+

+ 6. + 用户不得利用或针对本应用及相关服务进行任何危害计算机网络安全、危害社会经济秩序的相关行为,如果我们有理由认为您的行为违反或可能违反上述约定的,我们可独立进行判断并处理,且在任何时候有权在不事先通知的情况下终止向您提供服务,并依法追究相关责任。 +

+

+   +

+

+ 7. + 用户不得以本应用的名义传送或发布信息;也不得以易被公众或第三方混淆为本应用官方意见的方式传送或发布信息。用户在本应用传送、发布的任何内容并不反映或代表,也不得被视为反映或代表本应用的观点、立场;用户须保证其通过“本应用”账号或服务所传送的信息真实、合法、无害、准确、有效。如因用户传送的信息违法、给本应用或第三方造成损害的,用户应当依法予以赔偿,本应用有权依据法律法规、政策、本协议条款对相关违法、错误信息作出的处理。 +

+

+   +

+

+ 8. 经许可后,用户、第三方对本APP软件及相关服务的信息内容的分享、转发等行为,应符合以下规范: +

+

+   +

+

+ (1)对抓取、统计、获得的相关搜索热词、命中率、分类、搜索量、点击率、阅读量等相关数据,未经公司事先书面同意,不得将上述数据以任何方式公示、提供、泄露给任何第三人; +

+

+   +

+

+ (2)不得对本APP软件及相关服务的源网页进行任何形式的任何改动,包括但不限于本APP软件及相关服务的首页链接、广告系统链接等入口,也不得对本APP软件及相关服务的源页面的展示进行任何形式的遮挡、插入、弹窗等妨碍; +

+

+   +

+

+ (4)不得把相关数据内容用于公司许可范围之外的目的,进行任何形式的销售和商业使用,或向第三方泄露、提供或允许第三方为任何方式的使用。 +

+

+   +

+

+ (5)用户向任何第三人分享、转发、复制本APP软件及相关服务信息内容的行为,还应遵守公司为此制定的其他规范和标准。 +

+

+   +

+

+ 9. + 未经本应用许可,任何人不得以包括通过机器人、蜘蛛等程序或设备监视、复制、传播、展示、镜像、上载、下载等方式直接或间接盗取本APP软件及相关服务的视频、图文等信息内容,或以任何方式(包括但不限于隐藏或者修改域名、平台特有标识、用户名等)删除或改变相关信息内容的权利管理电子信息。本应用等文字及/或标识,以及其他标识、徽记、产品和服务名称均为本应用所有,如有宣传、展示等任何使用需要,您必须取得本应用事先书面授权。 +

+

+   +

+

+ 第五条 违规用户的处理 +

+

+   +

+

+ 1. + 如果本应用发现用户有违反法律法规、政策规定、“外部电商平台”管理规定、本协议条款约定,或被其他用户投诉具有违反法律法规、政策规定、本协议条款约定的行为,本应用有权依据法律法规、政策及本协议条款对用户的违法、错误的行为或内容进行处理,并视情节轻重对违规的用户处以包括但不限于警告、账号封禁、功能封禁、账户冻结、不予结算资金等处罚,并将有关违法信息向有关部门报告,用户应承担由此产生的一切法律后果。 +

+

+   +

+

+ 2. 本应用对上述违规内容的违规内容的审查仅为初步表面审查,不对审查结论承担责任;如果用户对处理结果存在异议,则应提供相应的证明文件,并与我们或投诉方沟通或采取法律途径解决争议。 +

+

+   +

+

+ 3. + 因用户违反法律法规、政策规定、本协议约定或发生其它过错,导致任何第三方向本应用或其合作方、关联方主张任何索赔、要求的,用户应承担相关责任。如果因此造成本应用或其合作方、关联方承担相关责任或发生其他任何损失的,包括律师费、调查取证费、电子数据公证费、差旅费等,本应用或其合作方、关联方有权要求该用户承担赔偿责任或者向该用户追偿。 +

+

+   +

+

+ 4. + 被处理用户如对投诉内容或处理决定有异议,可提交申诉意见,本应用将对申诉进行审查,并自行合理判断决定是否变更处罚措施。对于因投诉所导致的处理,本应用仍有权根据投诉方与用户共同确定的意见或司法机关生效法律文书增加、变更或撤销对用户的处理。 +

+

+   +

+

+ 第六条本协议的生效、变更与终止 +

+

+   +

+

+ 1. 本协议自您勾选并点击“我已阅读并同意”本协议时即生效。如未点击确认本协议之前事实上已在使用本平台功能的,则本协议在您的事实行为发生之时生效。 +

+

+   +

+

+ 2. + 由于互联网行业发展迅速、APP及服务频繁迭代更新等特点,我们可能会对相关的产品或服务进行升级优化,您理解并同意我们会根据商业需要,在符合法律规定的情况下对本协议的部分条款进行更新,更新后的协议条款将代替原来的协议并在正式发布之日起生效,您可以在相关服务页面查阅最新版本的协议条款。 +

+

+   +

+

+ 3. + 如本协议后续发生变更,我们在APP公布更新后的协议,该公布行为视为我们已通知您更新内容。我们也可能会采用本协议约定的其他通知方式通知您。如您对更新后的服务协议有任何疑问,您可通过本协议提及的联系邮箱向本应用工作人员反馈,我们会及时为您解答。如您不同意更新后的协议,您可以停止使用本APP,本协议修改后,如果您继续使用本APP的,即视为您已接受修改后的协议。 +

+

+   +

+

+ 4. 存在以下任一情形的,本协议在用户与本应用之间终止。因终止本协议给对方或其他第三方合法权益造成损失的,有权要求对造成的损失承担法律责任: +

+

+   +

+

+ (1)用户账号注销的,本协议自注销之日起在用户和本应用之间终止; +

+

+   +

+

+ (2)因违反法律法规、政策规定或本协议约定导致本协议终止的; +

+

+   +

+

+ (3)因 任一方违反本协议约定、发生其他违法或不当行为导致本协议终止的; +

+

+   +

+

+ (4)发生其他导致本协议终止的情形。 +

+

+   +

+

+ 5. 本协议终止时,本应用有权要求您就您账户下未完成的交易、未支付的订单、未结算的资金等进行处理,若您在合理时间内未处理,本应用有权以合理的方式代为处理,并就处理过程中可能带来损失的向您追偿。 +

+

+   +

+

+ 第七条 争议解决条款 +

+

+   +

+

+    本协议的效力、解释及纠纷的解决,适用于中华人民共和国法律。若用户和本应用之间发生任何纠纷或争议,首先应友好协商解决,协商不成的,用户同意将纠纷或争议提交本应用住所地有管辖权的人民法院管辖。 +

+

+   +

+

+   +

+

+   +

+

+ 第八条 不可抗力条款 +

+

+   +

+

+ 1. “不可抗力”是指本协议双方不能合理控制、不可预见或即使预见亦无法避免的事件,该事件妨碍、影响或延误任何一方根据本协议履行其全部或部分义务。该事件包括但不限于政府行为、自然灾害、战争或任何其他类似事件。 +

+

+   +

+

+ 2. 遭受不可抗力事件的一方可暂行中止履行本合同项下的义务直至不可抗力事件的影响消除为止并且无需为此而承担违约责任;但应尽最大努力克服该事件,减轻负面影响。 +

+

+   +

+

+ 第九条其他条款 +

+

+   +

+

+ 1. 除非我们另行声明,本应用的所有产品、技术、软件、程序、数据及其他信息(包括文字、图标、图片、照片、音频、视频、图表、色彩组合、版面设计等)的所有权利(包括版权、商标权、专利权、商业秘密及其他相关权利)均归本应用所有。 +

+

+   +

+

+ 2. 用户理解并接受:本协议的正文还包括《隐私协议》,与本协议具有同等法律效力,共同约束用户与本应用。 +

+

+   +

+

+ 3. 本协议的任何条款不论因何种原因无效或不具可执行性,不影响其余条款在用户与本应用双方之间的法律效力。 +

+

+   +

+

+ 4. 如您对本协议条款有任何疑问或者需要帮助,您可通过客服邮箱:admin@yiovo.com与我们联系。 +

+

+
+

+
+ + + \ No newline at end of file diff --git a/static/background/user-header2.png b/static/background/user-header2.png new file mode 100644 index 0000000..c53235e Binary files /dev/null and b/static/background/user-header2.png differ diff --git a/static/channel/wechat.png b/static/channel/wechat.png new file mode 100644 index 0000000..4e62d40 Binary files /dev/null and b/static/channel/wechat.png differ diff --git a/static/default-avatar.png b/static/default-avatar.png new file mode 100644 index 0000000..94e0a72 Binary files /dev/null and b/static/default-avatar.png differ diff --git a/static/empty-02.png b/static/empty-02.png new file mode 100644 index 0000000..3520e86 Binary files /dev/null and b/static/empty-02.png differ diff --git a/static/empty.png b/static/empty.png new file mode 100644 index 0000000..f9e360b Binary files /dev/null and b/static/empty.png differ diff --git a/static/next.png b/static/next.png new file mode 100644 index 0000000..18aa8a8 Binary files /dev/null and b/static/next.png differ diff --git a/static/order/refund-bg.png b/static/order/refund-bg.png new file mode 100644 index 0000000..817a45e Binary files /dev/null and b/static/order/refund-bg.png differ diff --git a/static/order/status/close.png b/static/order/status/close.png new file mode 100644 index 0000000..ca5739b Binary files /dev/null and b/static/order/status/close.png differ diff --git a/static/order/status/received.png b/static/order/status/received.png new file mode 100644 index 0000000..27f8a26 Binary files /dev/null and b/static/order/status/received.png differ diff --git a/static/order/status/wait_deliver.png b/static/order/status/wait_deliver.png new file mode 100644 index 0000000..b42c2e3 Binary files /dev/null and b/static/order/status/wait_deliver.png differ diff --git a/static/order/status/wait_pay.png b/static/order/status/wait_pay.png new file mode 100644 index 0000000..fe14070 Binary files /dev/null and b/static/order/status/wait_pay.png differ diff --git a/static/order/status/wait_receipt.png b/static/order/status/wait_receipt.png new file mode 100644 index 0000000..5837b74 Binary files /dev/null and b/static/order/status/wait_receipt.png differ diff --git a/static/prev.png b/static/prev.png new file mode 100644 index 0000000..961ca9c Binary files /dev/null and b/static/prev.png differ diff --git a/static/tabbar/cal-active.png b/static/tabbar/cal-active.png new file mode 100644 index 0000000..ebeb85f Binary files /dev/null and b/static/tabbar/cal-active.png differ diff --git a/static/tabbar/cal.png b/static/tabbar/cal.png new file mode 100644 index 0000000..b36a309 Binary files /dev/null and b/static/tabbar/cal.png differ diff --git a/static/tabbar/cart-active.png b/static/tabbar/cart-active.png new file mode 100644 index 0000000..e31a406 Binary files /dev/null and b/static/tabbar/cart-active.png differ diff --git a/static/tabbar/cart.png b/static/tabbar/cart.png new file mode 100644 index 0000000..7a44d8b Binary files /dev/null and b/static/tabbar/cart.png differ diff --git a/static/tabbar/cate-active.png b/static/tabbar/cate-active.png new file mode 100644 index 0000000..0718f58 Binary files /dev/null and b/static/tabbar/cate-active.png differ diff --git a/static/tabbar/cate.png b/static/tabbar/cate.png new file mode 100644 index 0000000..8290f99 Binary files /dev/null and b/static/tabbar/cate.png differ diff --git a/static/tabbar/home-active.png b/static/tabbar/home-active.png new file mode 100644 index 0000000..c4bf42d Binary files /dev/null and b/static/tabbar/home-active.png differ diff --git a/static/tabbar/home.png b/static/tabbar/home.png new file mode 100644 index 0000000..1766d72 Binary files /dev/null and b/static/tabbar/home.png differ diff --git a/static/tabbar/user-active.png b/static/tabbar/user-active.png new file mode 100644 index 0000000..8db5285 Binary files /dev/null and b/static/tabbar/user-active.png differ diff --git a/static/tabbar/user.png b/static/tabbar/user.png new file mode 100644 index 0000000..f1a527e Binary files /dev/null and b/static/tabbar/user.png differ diff --git a/static/wallet.png b/static/wallet.png new file mode 100644 index 0000000..d343bb5 Binary files /dev/null and b/static/wallet.png differ diff --git a/store/getters.js b/store/getters.js new file mode 100644 index 0000000..d27e783 --- /dev/null +++ b/store/getters.js @@ -0,0 +1,7 @@ +const getters = { + token: state => state.user.token, + userId: state => state.user.userId, + platform: state => state.app.platform +} + +export default getters diff --git a/store/index.js b/store/index.js new file mode 100644 index 0000000..b8fc6ed --- /dev/null +++ b/store/index.js @@ -0,0 +1,26 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import { + app, + user +} from './modules' +import getters from './getters' + +Vue.use(Vuex) + +export default new Vuex.Store({ + modules: { + app, + user + }, + state: { + + }, + mutations: { + + }, + actions: { + + }, + getters +}) diff --git a/store/modules/app.js b/store/modules/app.js new file mode 100644 index 0000000..eeb6d8b --- /dev/null +++ b/store/modules/app.js @@ -0,0 +1,24 @@ +import { + PLATFORM +} from '@/store/mutation-types' +import storage from '@/utils/storage' + + +const app = { + state: { + // 当前终端平台 + platform: '' + }, + + mutations: { + SET_PLATFORM: (state, value) => { + state.platform = value + } + }, + + actions: { + + } +} + +export default app diff --git a/store/modules/index.js b/store/modules/index.js new file mode 100644 index 0000000..877f548 --- /dev/null +++ b/store/modules/index.js @@ -0,0 +1,7 @@ +import app from './app' +import user from './user' + +export { + app, + user +} diff --git a/store/modules/user.js b/store/modules/user.js new file mode 100644 index 0000000..172f1b8 --- /dev/null +++ b/store/modules/user.js @@ -0,0 +1,94 @@ +import { ACCESS_TOKEN, USER_ID } from '@/store/mutation-types' +import storage from '@/utils/storage' +import * as LoginApi from '@/api/login' + +// 登陆成功后执行 +const loginSuccess = (commit, { token, userId }) => { + // 过期时间30天 + const expiryTime = 30 * 86400 + // 保存tokne和userId到缓存 + storage.set(USER_ID, userId, expiryTime) + storage.set(ACCESS_TOKEN, token, expiryTime) + // 记录到store全局变量 + commit('SET_TOKEN', token) + commit('SET_USER_ID', userId) +} + +const user = { + state: { + // 用户认证token + token: '', + // 用户ID + userId: null + }, + + mutations: { + SET_TOKEN: (state, value) => { + state.token = value + }, + SET_USER_ID: (state, value) => { + state.userId = value + } + }, + + actions: { + + // 用户登录(普通登录: 输入手机号和验证码) + Login({ commit }, data) { + return new Promise((resolve, reject) => { + LoginApi.login({ form: data }) + .then(response => { + const result = response.data + loginSuccess(commit, result) + resolve(response) + }) + .catch(reject) + }) + }, + + // 微信小程序一键授权登录(获取用户基本信息) + LoginMpWx({ commit }, data) { + return new Promise((resolve, reject) => { + LoginApi.loginMpWx({ form: data }, { isPrompt: false }) + .then(response => { + const result = response.data + loginSuccess(commit, result) + resolve(response) + }) + .catch(reject) + }) + }, + + // 微信小程序一键授权登录(授权手机号) + LoginMpWxMobile({ commit }, data) { + return new Promise((resolve, reject) => { + LoginApi.loginMpWxMobile({ form: data }, { isPrompt: false }) + .then(response => { + const result = response.data + loginSuccess(commit, result) + resolve(response) + }) + .catch(reject) + }) + }, + + // 退出登录 + Logout({ commit }, data) { + const store = this + return new Promise((resolve, reject) => { + if (store.getters.userId > 0) { + // 删除缓存中的tokne和userId + storage.remove(USER_ID) + storage.remove(ACCESS_TOKEN) + // 记录到store全局变量 + commit('SET_TOKEN', '') + commit('SET_USER_ID', null) + resolve() + } + }) + } + + } +} + +export default user diff --git a/store/mutation-types.js b/store/mutation-types.js new file mode 100644 index 0000000..4f0572f --- /dev/null +++ b/store/mutation-types.js @@ -0,0 +1,3 @@ +export const ACCESS_TOKEN = 'AccessToken' +export const USER_ID = 'userId' +export const PLATFORM = 'platform' diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..d38f52b --- /dev/null +++ b/uni.scss @@ -0,0 +1,81 @@ +/** + * 这里是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-text-color-active:#fa2209; // 激活的颜色红 + +/* 背景颜色 */ +$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:24rpx; +$uni-font-size-base:28rpx; +$uni-font-size-lg:32rpx; + +/* 图片尺寸 */ +$uni-img-size-sm:40rpx; +$uni-img-size-base:52rpx; +$uni-img-size-lg:80rpx; + +/* Border Radius */ +$uni-border-radius-sm: 4rpx; +$uni-border-radius-base: 6rpx; +$uni-border-radius-lg: 12rpx; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 10px; +$uni-spacing-row-base: 20rpx; +$uni-spacing-row-lg: 30rpx; + +/* 垂直间距 */ +$uni-spacing-col-sm: 8rpx; +$uni-spacing-col-base: 16rpx; +$uni-spacing-col-lg: 24rpx; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:40rpx; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:36rpx; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:30rpx; + + +/* 引入uView全局scss变量文件 */ +@import "uview-ui/theme.scss"; \ No newline at end of file diff --git a/uni_modules/mp-html/README.md b/uni_modules/mp-html/README.md new file mode 100644 index 0000000..d195174 --- /dev/null +++ b/uni_modules/mp-html/README.md @@ -0,0 +1,182 @@ +## 功能介绍 +- 全端支持(含 `v3、NVUE`) +- 支持丰富的标签(包括 `table`、`video`、`svg` 等) +- 支持丰富的事件效果(自动预览图片、链接处理等) +- 支持设置占位图(加载中、出错时、预览时) +- 支持锚点跳转、长按复制等丰富功能 +- 支持大部分 *html* 实体 +- 丰富的插件(关键词搜索、内容 **编辑** 等) +- 效率高、容错性强且轻量化 + +查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多 + +## 使用方法 +- `uni_modules` 方式 + 1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下 + 2. 在需要使用页面的 `(n)vue` 文件中添加 + ```html + + + ``` + ```javascript + export default { + data() { + return { + html: '
Hello World!
' + } + } + } + ``` + 3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可 + +- 源码方式 + 1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码 + 插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取 + 2. 在需要使用页面的 `(n)vue` 文件中添加 + ```html + + ``` + ```javascript + import mpHtml from '@/components/mp-html/mp-html' + export default { + // HBuilderX 2.5.5+ 可以通过 easycom 自动引入 + components: { + mpHtml + }, + data() { + return { + html: '
Hello World!
' + } + } + } + ``` + +- npm 方式 + 1. 在项目根目录下执行 + ```bash + npm install mp-html + ``` + 2. 在需要使用页面的 `(n)vue` 文件中添加 + ```html + + ``` + ```javascript + import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html' + export default { + // 不可省略 + components: { + mpHtml + }, + data() { + return { + html: '
Hello World!
' + } + } + } + ``` + 3. 需要更新版本时执行以下命令即可 + ```bash + npm update mp-html + ``` + + 使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687) + 如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行 + +查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多 + +## 组件属性 + +| 属性 | 类型 | 默认值 | 说明 | +|:---:|:---:|:---:|---| +| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210)) | +| content | String | | 用于渲染的 html 字符串 | +| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 | +| domain | String | | 主域名(用于链接拼接) | +| error-img | String | | 图片出错时的占位图链接 | +| lazy-load | Boolean | false | 是否开启图片懒加载 | +| loading-img | String | | 图片加载过程中的占位图链接 | +| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 | +| preview-img | Boolean | true | 是否允许图片被点击时自动预览 | +| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 | +| selectable | Boolean | false | 是否开启文本长按复制 | +| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 | +| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 | +| tag-style | Object | | 设置标签的默认样式 | +| use-anchor | Boolean | false | 是否使用锚点链接 | + +查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多 + +## 组件事件 + +| 名称 | 触发时机 | +|:---:|---| +| load | dom 树加载完毕时 | +| ready | 图片加载完毕时 | +| error | 发生渲染错误时 | +| imgtap | 图片被点击时 | +| linktap | 链接被点击时 | + +查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多 + +## api +组件实例上提供了一些 `api` 方法可供调用 + +| 名称 | 作用 | +|:---:|---| +| in | 将锚点跳转的范围限定在一个 scroll-view 内 | +| navigateTo | 锚点跳转 | +| getText | 获取文本内容 | +| getRect | 获取富文本内容的位置和大小 | +| setContent | 设置富文本内容 | +| imgList | 获取所有图片的数组 | + +查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多 + +## 插件扩展 +除基本功能外,本组件还提供了丰富的扩展,可按照需要选用 + +| 名称 | 作用 | +|:---:|---| +| audio | 音乐播放器 | +| editable | 富文本 **编辑**([示例项目](https://6874-html-foe72-1259071903.tcb.qcloud.la/editable.zip?sign=cc0017be203fb3dbca62d33a0c15792e&t=1608447445)) | +| emoji | 解析 emoji | +| highlight | 代码块高亮显示 | +| markdown | 渲染 markdown | +| search | 关键词搜索 | +| style | 匹配 style 标签中的样式 | +| txv-video | 使用腾讯视频 | +| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) | + +从插件市场导入的包中 **不含有** 扩展插件,需要使用插件参考以下方法: +1. 获取完整组件包 + ```bash + npm install mp-html + ``` +2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件 +3. 生成新的组件包 + 在 `node_modules/mp-html` 目录下执行 + ```bash + npm install + npm run build:uni-app + ``` +4. 拷贝 `dist/uni-app` 中的内容到项目根目录 + +查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多 + +## 示例体验 +![富文本插件](https://gitee.com/jin-yufeng/mp-html/raw/master/docs/assets/case/富文本插件.jpg) + +## 关于 nvue +`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面 +由于渲染方式与其他端不同,有以下限制: +1. 不支持 `lazy-load` 属性 +2. 视频不支持全屏播放 + +纯 `nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下) + +## 问题反馈 +遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题 +可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复) +提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复 + +查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多 \ No newline at end of file diff --git a/uni_modules/mp-html/changelog.md b/uni_modules/mp-html/changelog.md new file mode 100644 index 0000000..7bd9611 --- /dev/null +++ b/uni_modules/mp-html/changelog.md @@ -0,0 +1,69 @@ +## v2.2.1(2021-12-24) +1. `A` `editable` 插件增加上下移动标签功能 +2. `U` `editable` 插件支持在文本中间光标处插入内容 +3. `F` 修复了 `nvue` 端设置 `margin` 后可能导致高度不正确的问题 +4. `F` 修复了 `highlight` 插件使用压缩版的 `prism.css` 可能导致背景失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/367) +5. `F` 修复了编辑状态下使用 `emoji` 插件内容为空时可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/371) +6. `F` 修复了使用 `editable` 插件后将 `selectable` 属性设置为 `force` 不生效的问题 +## v2.2.0(2021-10-12) +1. `A` 增加 `customElements` 配置项,便于添加自定义功能性标签 [详细](https://github.com/jin-yufeng/mp-html/issues/350) +2. `A` `editable` 插件增加切换音视频自动播放状态的功能 [详细](https://github.com/jin-yufeng/mp-html/pull/341) by [@leeseett](https://github.com/leeseett) +3. `A` `editable` 插件删除媒体标签时触发 `remove` 事件,便于删除已上传的文件 +4. `U` `editable` 插件 `insertImg` 方法支持同时插入多张图片 [详细](https://github.com/jin-yufeng/mp-html/issues/342) +5. `U` `editable` 插入图片和音视频时支持拼接 `domian` 主域名 +6. `F` 修复了内部链接参数中包含 `://` 时被认为是外部链接的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/356) +7. `F` 修复了部分 `svg` 标签名或属性名大小写不正确时不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/351) +8. `F` 修复了 `nvue` 页面运行到非 `app` 平台时可能样式错误的问题 +## v2.1.5(2021-08-13) +1. `A` 增加支持标签的 `dir` 属性 +2. `F` 修复了 `ruby` 标签文字与拼音没有居中对齐的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/325) +3. `F` 修复了音视频标签内有 `a` 标签时可能无法播放的问题 +4. `F` 修复了 `externStyle` 中的 `class` 名包含下划线或数字时可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326) +5. `F` 修复了 `h5` 端引入 `externStyle` 可能不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326) +## v2.1.4(2021-07-14) +1. `F` 修复了 `rt` 标签无法设置样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/318) +2. `F` 修复了表格中有单元格同时合并行和列时可能显示不正确的问题 +3. `F` 修复了 `app` 端无法关闭图片长按菜单的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/322) +4. `F` 修复了 `editable` 插件只能添加图片链接不能修改的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/312) by [@leeseett](https://github.com/leeseett) +## v2.1.3(2021-06-12) +1. `A` `editable` 插件增加 `insertTable` 方法 +2. `U` `editable` 插件支持编辑表格中的空白单元格 [详细](https://github.com/jin-yufeng/mp-html/issues/310) +3. `F` 修复了 `externStyle` 中使用伪类可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/298) +4. `F` 修复了多个组件同时使用时 `tag-style` 属性时可能互相影响的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/305) by [@woodguoyu](https://github.com/woodguoyu) +5. `F` 修复了包含 `linearGradient` 的 `svg` 可能无法显示的问题 +6. `F` 修复了编译到头条小程序时可能报错的问题 +7. `F` 修复了 `nvue` 端不触发 `click` 事件的问题 +8. `F` 修复了 `editable` 插件尾部插入时无法撤销的问题 +9. `F` 修复了 `editable` 插件的 `insertHtml` 方法只能在末尾插入的问题 +10. `F` 修复了 `editable` 插件插入音频不显示的问题 +## v2.1.2(2021-04-24) +1. `A` 增加了 [img-cache](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#img-cache) 插件,可以在 `app` 端缓存图片 [详细](https://github.com/jin-yufeng/mp-html/issues/292) by [@PentaTea](https://github.com/PentaTea) +2. `U` 支持通过 `container-style` 属性设置 `white-space` 来保留连续空格和换行符 [详细](https://jin-yufeng.gitee.io/mp-html/#/question/faq#space) +3. `U` 代码风格符合 [standard](https://standardjs.com) 标准 +4. `U` `editable` 插件编辑状态下支持预览视频 [详细](https://github.com/jin-yufeng/mp-html/issues/286) +5. `F` 修复了 `svg` 标签内嵌 `svg` 时无法显示的问题 +6. `F` 修复了编译到支付宝和头条小程序时部分区域不可复制的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/291) +## v2.1.1(2021-04-09) +1. 修复了对 `p` 标签设置 `tag-style` 可能不生效的问题 +2. 修复了 `svg` 标签中的文本无法显示的问题 +3. 修复了使用 `editable` 插件编辑表格时可能报错的问题 +4. 修复了使用 `highlight` 插件运行到头条小程序时可能没有样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/280) +5. 修复了使用 `editable` 插件 `editable` 属性为 `false` 时会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/284) +6. 修复了 `style` 插件连续子选择器失效的问题 +7. 修复了 `editable` 插件无法修改图片和字体大小的问题 +## v2.1.0.2(2021-03-21) +修复了 `nvue` 端使用可能报错的问题 +## v2.1.0(2021-03-20) +1. `A` 增加了 [container-style](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#container-style) 属性 [详细](https://gitee.com/jin-yufeng/mp-html/pulls/1) +2. `A` 增加支持 `strike` 标签 +3. `A` `editable` 插件增加 `placeholder` 属性 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable) +4. `A` `editable` 插件增加 `insertHtml` 方法 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable) +5. `U` 外部样式支持标签名选择器 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart#setting) +6. `F` 修复了 `nvue` 端部分情况下可能不显示的问题 +## v2.0.5(2021-03-12) +1. `U` [linktap](https://jin-yufeng.gitee.io/mp-html/#/basic/event#linktap) 事件增加返回内部文本内容 `innerText` [详细](https://github.com/jin-yufeng/mp-html/issues/271) +2. `U` [selectable](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) 属性设置为 `force` 时能够在微信 `iOS` 端生效(文本块会变成 `inline-block`) [详细](https://github.com/jin-yufeng/mp-html/issues/267) +3. `F` 修复了部分情况下竖向无法滚动的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/182) +4. `F` 修复了多次修改富文本数据时部分内容可能不显示的问题 +5. `F` 修复了 [腾讯视频](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#txv-video) 插件可能无法播放的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/265) +6. `F` 修复了 [highlight](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#highlight) 插件没有设置高亮语言时没有应用默认样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/276) by [@fuzui](https://github.com/fuzui) diff --git a/uni_modules/mp-html/components/mp-html/mp-html.vue b/uni_modules/mp-html/components/mp-html/mp-html.vue new file mode 100644 index 0000000..f277ad2 --- /dev/null +++ b/uni_modules/mp-html/components/mp-html/mp-html.vue @@ -0,0 +1,435 @@ + + + + + diff --git a/uni_modules/mp-html/components/mp-html/node/node.vue b/uni_modules/mp-html/components/mp-html/node/node.vue new file mode 100644 index 0000000..9ef1759 --- /dev/null +++ b/uni_modules/mp-html/components/mp-html/node/node.vue @@ -0,0 +1,525 @@ +