1 files modified
301 files added
| New file |
| | |
| | | unpackage |
| | | node_modules |
| | | uview-ui |
| New file |
| | |
| | | .DS_Store |
| | | node_modules/ |
| | | dist/ |
| | | unpackage/ |
| | | .hbuilderx/ |
| | | |
| | | npm-debug.log* |
| | | yarn-debug.log* |
| | | yarn-error.log* |
| | | **/*.log |
| | | |
| | | tests/**/coverage/ |
| | | tests/e2e/reports |
| | | selenium-debug.log |
| | | |
| | | # Editor directories and files |
| | | .idea |
| | | .vscode |
| | | *.suo |
| | | *.ntvs* |
| | | *.njsproj |
| | | *.sln |
| | | *.local |
| | | .env.development |
| | | |
| | | package-lock.json |
| | | yarn.lock |
| New file |
| | |
| | | <script> |
| | | export default { |
| | | onLaunch: function() { |
| | | console.log('App Launch') |
| | | }, |
| | | onShow: function() { |
| | | console.log('App Show') |
| | | }, |
| | | onHide: function() { |
| | | console.log('App Hide') |
| | | } |
| | | } |
| | | </script> |
| | | <style lang="scss"> |
| | | /*uview全局样式*/ |
| | | @import "uview-ui/index.scss"; |
| | | /*app全局样式*/ |
| | | @import 'static/style/app.scss'; |
| | | </style> |
| New file |
| | |
| | | 8fe8a906a5a5cc31c8aee6953db98ab275def06d not-for-merge branch 'master' of http://192.168.0.105:10010/r/Inspection-uniapp |
| New file |
| | |
| | | ref: refs/heads/master |
| New file |
| | |
| | | BladeX商业授权许可协议 |
| | | |
| | | 一、 知识产权: |
| | | BladeX系列产品知识产权归上海布雷德网络科技独立所有 |
| | | |
| | | 二、 许可: |
| | | 1. 在您完全接受并遵守本协议的基础上,本协议授予您使用BladeX的某些权利和非独占性许可。 |
| | | 2. 本协议中,将本产品使用用途分为“专业版用途”和“企业版用途”。 |
| | | 3. “专业版用途”定义:指个人在非团体机构中出于任何目的使用本产品(任何目的包括商业目的或非盈利目的)。 |
| | | 4. “企业版用途”定义:指团体机构(例如公司企业、政府、学校、军队、医院、社会团体等各类组织)(不包含集团,若集团使用则需为各个子公司分别购买企业授权)出于任何目的使用本产品(任何目的包括商业目的或非盈利目的)。 |
| | | |
| | | 三、 约束和限制: |
| | | 1. 本产品只能由您为本协议许可的目的而使用,您不得透露给任何第三方; |
| | | 2. 从本产品取得的任何信息、软件、产品或服务,您不得对其进行修改、改编或基于以上内容创建同种类别的衍生产品并售卖。 |
| | | 3. 您不得对本产品以及与之关联的商业授权进行发布、出租、销售、分销、抵押、转让、许可或发放子许可证。 |
| | | 4. 本产品商业授权版可能包含一些独立功能或特性,这些功能只有在您购买商业授权后才可以使用。在未取得商业授权的情况下,您不得使用、尝试使用或复制这些授权版独立功能。 |
| | | 5. 若您的客户要求以源码方式交付软件,需缴纳企业版授权费用,否则本产品部分不得提供源码。 |
| | | |
| | | 四、 不得用于非法或禁止的用途: |
| | | 您在使用本产品或服务时,不得将本产品产品或服务用于任何非法用途或本协议条款、条件和声明禁止的用途。 |
| | | |
| | | 五、 免责说明: |
| | | 1. 本产品按“现状”授予许可,您须自行承担使用本产品的风险。BladeX团队不对此提供任何明示、暗示或任何其它形式的担保和表示。在任何情况下,对于因使用或无法使用本软件而导致的任何损失(包括但不仅限于商业利润损失、业务中断或业务信息丢失),BladeX团队无需向您或任何第三方负责,即使BladeX团队已被告知可能会造成此类损失。在任何情况下, BladeX团队均不就任何直接的、间接的、附带的、后果性的、特别的、惩戒性的和处罚性的损害赔偿承担任何责任,无论该主张是基于保证、合同、侵权(包括疏忽)或是基于其他原因作出。 |
| | | 2. 本产品可能内置有第三方服务,您应自行评估使用这些第三方服务的风险,由使用此类第三方服务而产生的纠纷,全部责任由您自行承担。 |
| | | 3. BladeX团队不对使用本产品构建的网站中任何信息内容以及导致的任何版权纠纷、法律争议和后果承担任何责任,全部责任由您自行承担。 |
| | | 4. BladeX团队可能会经常提供产品更新或升级,但BladeX团队没有为根据本协议许可的产品提供维护或更新的责任。 |
| | | 5. BladeX团队可能会按照官方制定的答疑规则为您进行答疑,但BladeX团队没有为根据本协议许可的产品提供技术支持的义务或责任。 |
| | | |
| | | 六、 权利和所有权的保留: |
| | | BladeX团队保留所有未在本协议中明确授予您的所有权利。BladeX团队保留随时更新本协议的权利,并只需公示于对应产品项目的LICENSE文件,无需征得您的事先同意且无需另行通知,更新后的内容应于公示即时生效。您可以随时访问产品地址并查阅最新版许可条款,在更新生效后您继续使用本产品则被视作您已接受了新的条款。 |
| | | |
| | | 七、 协议终止 |
| | | 1. 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款,在享有上述条款授予的许可权力同时,也受到相关的约束和限制,本协议许可范围以外的行为,将直接违反本协议并构成侵权。 |
| | | 2. 一旦您违反本协议的条款,BladeX团队随时可能终止本协议、收回许可和授权,并要求您承担相应法律和经济责任。 |
| | |
| | | ## Inspection-uniapp |
| | | ## 版权声明 |
| | | * BladeX是一个商业化软件,系列产品知识产权归**上海布雷德网络科技**独立所有 |
| | | * 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款 |
| | | * 更多详情请看:[BladeX商业授权许可协议](/LICENSE) |
| | | |
| | | 巡检系统app |
| | | ## 答疑流程 |
| | | >1. 遇到问题或Bug |
| | | >2. 业务型问题打断点调试尝试找出问题所在 |
| | | >3. 系统型问题通过百度、谷歌、社区查找解决方案 |
| | | >4. 未解决问题则进入技术社区进行发帖提问:[https://sns.bladex.vip/](https://sns.bladex.vip/) |
| | | >5. 将帖子地址发至商业群,特别简单三言两语就能描述清楚的也可在答疑时间内发至商业群提问 |
| | | >6. 发帖的时候一定要描述清楚,详细描述遇到问题的**重现步骤**、**报错详细信息**、**相关代码与逻辑**、**使用软件版本**以及**操作系统版本**,否则随意发帖提问将会提高我们的答疑难度。 |
| | | |
| | | ## 答疑时间 |
| | | * 工作日:9:00 ~ 17:00 提供答疑,周末、节假日休息,暂停答疑 |
| | | * 请勿**私聊提问**,以免被其他用户的消息覆盖从而无法获得答疑 |
| | | * 答疑时间外遇到问题可以将问题发帖至[技术社区](https://sns.bladex.vip/),我们后续会逐个回复 |
| | | |
| | | ## 授权范围 |
| | | * 专业版:只可用于**个人学习**及**个人私活**项目,不可用于公司或团队,不可泄露给任何第三方 |
| | | * 企业版:可用于**企业名下**的任何项目,企业版员工在**未购买**专业版授权前,只授权开发**所在授权企业名下**的项目,**不得将BladeX用于个人私活** |
| | | * 共同遵守:若甲方需要您提供项目源码,则需代为甲方购买BladeX企业授权,甲方购买后续的所有项目都无需再次购买授权 |
| | | |
| | | ## 商用权益 |
| | | * ✔️ 遵守[商业协议](/LICENSE)的前提下,将BladeX系列产品用于授权范围内的商用项目,并上线运营 |
| | | * ✔️ 遵守[商业协议](/LICENSE)的前提下,不限制项目数,不限制服务器数 |
| | | * ✔️ 遵守[商业协议](/LICENSE)的前提下,将自行编写的业务代码申请软件著作权 |
| | | |
| | | ## 何为侵权 |
| | | * ❌ 不遵守商业协议,私自销售商业源码 |
| | | * ❌ 以任何理由将BladeX源码用于申请软件著作权 |
| | | * ❌ 将商业源码以任何途径任何理由泄露给未授权的单位或个人 |
| | | * ❌ 开发完毕项目,没有为甲方购买企业授权,向甲方提供了BladeX代码 |
| | | * ❌ 基于BladeX拓展研发与BladeX有竞争关系的衍生框架,并将其开源或销售 |
| | | |
| | | ## 侵权后果 |
| | | * 情节较轻:第一次发现警告处理 |
| | | * 情节较重:封禁账号,踢出商业群,并保留追究法律责任的权利 |
| | | * 情节严重:与本地律师事务所合作,以公司名义起诉侵犯计算机软件著作权 |
| | | |
| | | ## 举报有奖 |
| | | * 向官方提供有用线索并成功捣毁盗版个人或窝点,将会看成果给予 500~10000 不等的现金奖励 |
| | | * 官方唯一指定QQ:1272154962 |
| New file |
| | |
| | | import http from '@/http/api.js' |
| | | |
| | | // 获取验证码 |
| | | const captcha = (data) => { |
| | | return http.request({ |
| | | url: '/blade-auth/oauth/captcha', |
| | | method: 'GET', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | export default { |
| | | captcha, |
| | | } |
| New file |
| | |
| | | // 获取部门数据 |
| | | export function fakePosition() { |
| | | return new Promise((resolute, reject) => { |
| | | try { |
| | | const detail = { |
| | | position: "研发部", |
| | | positionArr: ['研发部', '产品部'] |
| | | } |
| | | resolute(detail); |
| | | } catch (e) { |
| | | //模拟接口请求失败 |
| | | reject(e); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 获取公告数据 |
| | | export function fakeBannerList() { |
| | | return new Promise((resolute, reject) => { |
| | | try { |
| | | const list = [{ |
| | | id: '1', |
| | | img: '/static/images/home/banner.png', |
| | | url: '' |
| | | }, { |
| | | id: '2', |
| | | img: '/static/images/home/banner.png', |
| | | url: '' |
| | | }, { |
| | | id: '3', |
| | | img: '/static/images/home/banner.png', |
| | | url: '' |
| | | }]; |
| | | resolute(list); |
| | | } catch (e) { |
| | | //模拟接口请求失败 |
| | | reject(e); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 获取公告数据 |
| | | export function fakeNoticeList() { |
| | | return new Promise((resolute, reject) => { |
| | | try { |
| | | const list = [{ |
| | | id: '1', |
| | | img: '/static/images/home/focus.png', |
| | | title: '神舟十二号载人发射任务取得圆满成功' |
| | | }, { |
| | | id: '2', |
| | | img: '/static/images/home/service.png', |
| | | title: '全国累计报告接种新冠疫苗超9亿剂次' |
| | | }]; |
| | | resolute(list); |
| | | } catch (e) { |
| | | //模拟接口请求失败 |
| | | reject(e); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 获取顶部按钮数据 |
| | | export function fakeNavButton() { |
| | | return new Promise((resolute, reject) => { |
| | | try { |
| | | const list = [{ |
| | | name: '公积金', |
| | | img: '/static/images/home/n1.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '社保', |
| | | img: '/static/images/home/n2.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '医保', |
| | | img: '/static/images/home/n3.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '企业', |
| | | img: '/static/images/home/n4.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '教育', |
| | | img: '/static/images/home/n5.png', |
| | | url: '' |
| | | } |
| | | ]; |
| | | resolute(list); |
| | | } catch (e) { |
| | | //模拟接口请求失败 |
| | | reject(e); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 获取服务按钮数据 |
| | | export function fakeServiceButton() { |
| | | return new Promise((resolute, reject) => { |
| | | try { |
| | | const list = [{ |
| | | name: '人口登记', |
| | | img: '/static/images/home/s1.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '工商年报', |
| | | img: '/static/images/home/s2.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '税务登记', |
| | | img: '/static/images/home/s3.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '健康码', |
| | | img: '/static/images/home/s4.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '便民服务', |
| | | img: '/static/images/home/s5.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '通办大厅', |
| | | img: '/static/images/home/s6.png', |
| | | url: '' |
| | | }, |
| | | { |
| | | name: '智能问答', |
| | | img: '/static/images/home/s7.png', |
| | | url: '' |
| | | } |
| | | ]; |
| | | resolute(list); |
| | | } catch (e) { |
| | | //模拟接口请求失败 |
| | | reject(e); |
| | | } |
| | | }) |
| | | } |
| New file |
| | |
| | | // 获取服务数据 |
| | | export function fakeSubscribeList() { |
| | | return new Promise((resolute, reject) => { |
| | | try { |
| | | const list = [{ |
| | | img: '/static/images/service/s1.png', |
| | | name: '违章查询', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/s2.png', |
| | | name: '限行信息', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/s3.png', |
| | | name: '油价查询', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/s4.png', |
| | | name: '消费记录', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/s5.png', |
| | | name: '保养记录', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/s6.png', |
| | | name: '油耗记录', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/s7.png', |
| | | name: '周边查询', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/s8.png', |
| | | name: '教育咨询', |
| | | url: '' |
| | | } |
| | | ]; |
| | | resolute(list); |
| | | } catch (e) { |
| | | //模拟接口请求失败 |
| | | reject(e); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export function fakeServiceList() { |
| | | return new Promise((resolute, reject) => { |
| | | try { |
| | | const list = [{ |
| | | name: '交通出行', |
| | | list: [{ |
| | | img: '/static/images/service/t1.png', |
| | | name: '地铁', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/t2.png', |
| | | name: '公交', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/t3.png', |
| | | name: '自驾', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/t4.png', |
| | | name: '飞机', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/t5.png', |
| | | name: '动车', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/t6.png', |
| | | name: '火车', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/t7.png', |
| | | name: '摩托车', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/t8.png', |
| | | name: '自行车', |
| | | url: '' |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | name: '社会保险', |
| | | list: [{ |
| | | img: '/static/images/service/b1.png', |
| | | name: '人身保险', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b2.png', |
| | | name: '财产保险', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b3.png', |
| | | name: '火灾保险', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b4.png', |
| | | name: '运输保险', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b5.png', |
| | | name: '工程保险', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b6.png', |
| | | name: '盗窃保险', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b7.png', |
| | | name: '农业保险', |
| | | url: '' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b8.png', |
| | | name: '信用保险', |
| | | url: '' |
| | | } |
| | | ] |
| | | }, |
| | | ] |
| | | resolute(list); |
| | | } catch (e) { |
| | | //模拟接口请求失败 |
| | | reject(e); |
| | | } |
| | | }) |
| | | } |
| New file |
| | |
| | | import http from '@/http/api.js' |
| | | |
| | | export const getList = (current, size, params) => { |
| | | return http.request({ |
| | | url: '/taskInfo/taskinfo/page', |
| | | method: 'get', |
| | | params: { |
| | | ...params, |
| | | current, |
| | | size, |
| | | } |
| | | }) |
| | | } |
| New file |
| | |
| | | import http from '@/http/api.js' |
| | | |
| | | // 获取token |
| | | const token = (tenantId, username, password, type) => { |
| | | return http.request({ |
| | | url: '/blade-auth/oauth/token', |
| | | method: 'POST', |
| | | header: { |
| | | 'Tenant-Id': tenantId |
| | | }, |
| | | params: { |
| | | tenantId, |
| | | username, |
| | | password, |
| | | grant_type: "password", |
| | | scope: "all", |
| | | type |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 获取用户信息 |
| | | const userInfo = () => { |
| | | return http.request({ |
| | | url: '/blade-user/info', |
| | | method: 'GET', |
| | | }) |
| | | } |
| | | |
| | | export default { |
| | | token, |
| | | userInfo |
| | | } |
| New file |
| | |
| | | /** |
| | | * 全局变量配置 |
| | | */ |
| | | module.exports = { |
| | | // 应用名 |
| | | name: 'Rider', |
| | | // 应用logo,支持本地路径和网络路径 |
| | | logo: '/static/images/logo.png', |
| | | // 版本号 |
| | | version: '1.0.0', |
| | | // 开发环境接口Url |
| | | devUrl: 'http://localhost:80', |
| | | // 线上环境接口Url |
| | | prodUrl: 'https://api.bladex.vip', |
| | | // 后端数据的接收方式application/json;charset=UTF-8或者application/x-www-form-urlencoded;charset=UTF-8 |
| | | contentType: 'application/json;charset=UTF-8', |
| | | // 后端返回状态码 |
| | | codeName: 'code', |
| | | // 操作正常code |
| | | successCode: 200, |
| | | // 登录失效code |
| | | invalidCode: 401, |
| | | // 客户端ID |
| | | clientId: 'rider', |
| | | // 客户端密钥 |
| | | clientSecret: 'rider_secret' |
| | | } |
| New file |
| | |
| | | <template> |
| | | <view class="nav-wrap"> |
| | | <view class="nav-title"> |
| | | <image class="logo" src="/static/images/logo.png" mode="widthFix"></image> |
| | | <view class="nav-info"> |
| | | <view class="nav-title__text"> |
| | | {{title}} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="nav-desc"> |
| | | {{desc}} |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | props: { |
| | | desc: String, |
| | | title: String, |
| | | }, |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .nav-wrap { |
| | | margin-top: 15px; |
| | | padding: 15px; |
| | | position: relative; |
| | | } |
| | | |
| | | .lang { |
| | | position: absolute; |
| | | top: 15px; |
| | | right: 15px; |
| | | } |
| | | |
| | | .nav-title { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | align-items: center; |
| | | } |
| | | |
| | | .nav-info { |
| | | margin-left: 15px; |
| | | } |
| | | |
| | | .nav-title__text { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | color: $u-main-color; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .logo { |
| | | width: 100px; |
| | | /* #ifndef APP-NVUE */ |
| | | height: auto; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .nav-slogan { |
| | | color: $u-tips-color; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .nav-desc { |
| | | margin-top: 10px; |
| | | font-size: 14px; |
| | | color: $u-content-color; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <picker |
| | | mode="multiSelector" |
| | | :value="multiIndex" |
| | | :range="multiArray" |
| | | @change="handleValueChange" |
| | | @columnchange="handleColumnChange" |
| | | > |
| | | <slot></slot> |
| | | </picker> |
| | | </template> |
| | | |
| | | <script> |
| | | const CHINA_REGIONS = require('./regions.json'); |
| | | export default { |
| | | props: { |
| | | defaultRegions: { |
| | | type: Array, |
| | | default() { |
| | | return []; |
| | | } |
| | | }, |
| | | defaultRegionCode: { |
| | | type: String |
| | | }, |
| | | defaultRegion: [String, Array], |
| | | value: [String, Array] |
| | | }, |
| | | data() { |
| | | return { |
| | | cityArr: CHINA_REGIONS[0].childs, |
| | | districtArr: CHINA_REGIONS[0].childs[0].childs, |
| | | multiIndex: [0, 0, 0], |
| | | isInitMultiArray: true |
| | | }; |
| | | }, |
| | | watch: { |
| | | defaultRegion: { |
| | | handler(region, oldRegion) { |
| | | if (Array.isArray(region)) { |
| | | // 避免传的是字面量的时候重复触发 |
| | | oldRegion = oldRegion || []; |
| | | if (region.join('') !== oldRegion.join('')) { |
| | | this.handleDefaultRegion(region); |
| | | } |
| | | } else if (region && region.length == 6) { |
| | | this.handleDefaultRegion(region); |
| | | } else { |
| | | console.warn('defaultRegion非有效格式'); |
| | | } |
| | | }, |
| | | immediate: true |
| | | } |
| | | }, |
| | | computed: { |
| | | multiArray() { |
| | | return this.pickedArr.map(arr => arr.map(item => item.name)); |
| | | }, |
| | | pickedArr() { |
| | | // 进行初始化 |
| | | if (this.isInitMultiArray) { |
| | | return [ |
| | | CHINA_REGIONS, |
| | | CHINA_REGIONS[0].childs, |
| | | CHINA_REGIONS[0].childs[0].childs |
| | | ]; |
| | | } |
| | | return [CHINA_REGIONS, this.cityArr, this.districtArr]; |
| | | } |
| | | }, |
| | | methods: { |
| | | handleColumnChange(e) { |
| | | // console.log(e); |
| | | this.isInitMultiArray = false; |
| | | const that = this; |
| | | let col = e.detail.column; |
| | | let row = e.detail.value; |
| | | that.multiIndex[col] = row; |
| | | try { |
| | | switch (col) { |
| | | case 0: |
| | | if (CHINA_REGIONS[that.multiIndex[0]].childs.length == 0) { |
| | | that.cityArr = that.districtArr = [ |
| | | CHINA_REGIONS[that.multiIndex[0]] |
| | | ]; |
| | | break; |
| | | } |
| | | that.cityArr = CHINA_REGIONS[that.multiIndex[0]].childs; |
| | | that.districtArr = |
| | | CHINA_REGIONS[that.multiIndex[0]].childs[ |
| | | that.multiIndex[1] |
| | | ].childs; |
| | | break; |
| | | case 1: |
| | | that.districtArr = |
| | | CHINA_REGIONS[that.multiIndex[0]].childs[ |
| | | that.multiIndex[1] |
| | | ].childs; |
| | | break; |
| | | case 2: |
| | | break; |
| | | } |
| | | } catch (e) { |
| | | // console.log(e); |
| | | that.districtArr = CHINA_REGIONS[that.multiIndex[0]].childs[0].childs; |
| | | } |
| | | }, |
| | | handleValueChange(e) { |
| | | // 结构赋值 |
| | | let [index0, index1, index2] = e.detail.value; |
| | | let [arr0, arr1, arr2] = this.pickedArr; |
| | | let address = [arr0[index0], arr1[index1], arr2[index2]]; |
| | | // console.log(address); |
| | | this.$emit('getRegion', address); |
| | | this.$emit( |
| | | 'input', |
| | | `${address[0].name}${address[1].name}${address[2].name}` |
| | | ); |
| | | }, |
| | | handleDefaultRegion(region) { |
| | | const isCode = !Array.isArray(region); |
| | | this.isInitMultiArray = false; |
| | | let children = CHINA_REGIONS; |
| | | for (let i = 0; i < 3; i++) { |
| | | for (let j = 0; j < children.length; j++) { |
| | | let condition = isCode |
| | | ? children[j].code == region.slice(0, (i + 1) * 2) |
| | | : children[j].name.includes(region[i]); |
| | | if (condition) { |
| | | // 匹配成功进行赋值 |
| | | // console.log(i,j,children.length-1); |
| | | children = children[j].childs; |
| | | if (i == 0) { |
| | | this.cityArr = children; |
| | | } else if (i == 1) { |
| | | this.districtArr = children; |
| | | } |
| | | this.$set(this.multiIndex, i, j); |
| | | // console.log(this.multiIndex); |
| | | break; |
| | | } else { |
| | | // 首次匹配失败就用默认的初始化 |
| | | // console.log(i,j,children.length-1); |
| | | if (i == 0 && j == children.length - 1) { |
| | | this.isInitMultiArray = true; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| New file |
| | |
| | | [{"code":"11","name":"北京市","childs":[{"code":"1101","name":"市辖区","childs":[{"code":"110101","name":"东城区"},{"code":"110102","name":"西城区"},{"code":"110105","name":"朝阳区"},{"code":"110106","name":"丰台区"},{"code":"110107","name":"石景山区"},{"code":"110108","name":"海淀区"},{"code":"110109","name":"门头沟区"},{"code":"110111","name":"房山区"},{"code":"110112","name":"通州区"},{"code":"110113","name":"顺义区"},{"code":"110114","name":"昌平区"},{"code":"110115","name":"大兴区"},{"code":"110116","name":"怀柔区"},{"code":"110117","name":"平谷区"},{"code":"110118","name":"密云区"},{"code":"110119","name":"延庆区"}]}]},{"code":"12","name":"天津市","childs":[{"code":"1201","name":"市辖区","childs":[{"code":"120101","name":"和平区"},{"code":"120102","name":"河东区"},{"code":"120103","name":"河西区"},{"code":"120104","name":"南开区"},{"code":"120105","name":"河北区"},{"code":"120106","name":"红桥区"},{"code":"120110","name":"东丽区"},{"code":"120111","name":"西青区"},{"code":"120112","name":"津南区"},{"code":"120113","name":"北辰区"},{"code":"120114","name":"武清区"},{"code":"120115","name":"宝坻区"},{"code":"120116","name":"滨海新区"},{"code":"120117","name":"宁河区"},{"code":"120118","name":"静海区"},{"code":"120119","name":"蓟州区"}]}]},{"code":"13","name":"河北省","childs":[{"code":"1301","name":"石家庄市","childs":[{"code":"130102","name":"长安区"},{"code":"130104","name":"桥西区"},{"code":"130105","name":"新华区"},{"code":"130107","name":"井陉矿区"},{"code":"130108","name":"裕华区"},{"code":"130109","name":"藁城区"},{"code":"130110","name":"鹿泉区"},{"code":"130111","name":"栾城区"},{"code":"130121","name":"井陉县"},{"code":"130123","name":"正定县"},{"code":"130125","name":"行唐县"},{"code":"130126","name":"灵寿县"},{"code":"130127","name":"高邑县"},{"code":"130128","name":"深泽县"},{"code":"130129","name":"赞皇县"},{"code":"130130","name":"无极县"},{"code":"130131","name":"平山县"},{"code":"130132","name":"元氏县"},{"code":"130133","name":"赵县"},{"code":"130183","name":"晋州市"},{"code":"130184","name":"新乐市"}]},{"code":"1302","name":"唐山市","childs":[{"code":"130202","name":"路南区"},{"code":"130203","name":"路北区"},{"code":"130204","name":"古冶区"},{"code":"130205","name":"开平区"},{"code":"130207","name":"丰南区"},{"code":"130208","name":"丰润区"},{"code":"130209","name":"曹妃甸区"},{"code":"130223","name":"滦县"},{"code":"130224","name":"滦南县"},{"code":"130225","name":"乐亭县"},{"code":"130227","name":"迁西县"},{"code":"130229","name":"玉田县"},{"code":"130281","name":"遵化市"},{"code":"130283","name":"迁安市"}]},{"code":"1303","name":"秦皇岛市","childs":[{"code":"130302","name":"海港区"},{"code":"130303","name":"山海关区"},{"code":"130304","name":"北戴河区"},{"code":"130306","name":"抚宁区"},{"code":"130321","name":"青龙满族自治县"},{"code":"130322","name":"昌黎县"},{"code":"130324","name":"卢龙县"}]},{"code":"1304","name":"邯郸市","childs":[{"code":"130402","name":"邯山区"},{"code":"130403","name":"丛台区"},{"code":"130404","name":"复兴区"},{"code":"130406","name":"峰峰矿区"},{"code":"130421","name":"邯郸县"},{"code":"130423","name":"临漳县"},{"code":"130424","name":"成安县"},{"code":"130425","name":"大名县"},{"code":"130426","name":"涉县"},{"code":"130427","name":"磁县"},{"code":"130428","name":"肥乡县"},{"code":"130429","name":"永年县"},{"code":"130430","name":"邱县"},{"code":"130431","name":"鸡泽县"},{"code":"130432","name":"广平县"},{"code":"130433","name":"馆陶县"},{"code":"130434","name":"魏县"},{"code":"130435","name":"曲周县"},{"code":"130481","name":"武安市"}]},{"code":"1305","name":"邢台市","childs":[{"code":"130502","name":"桥东区"},{"code":"130503","name":"桥西区"},{"code":"130521","name":"邢台县"},{"code":"130522","name":"临城县"},{"code":"130523","name":"内丘县"},{"code":"130524","name":"柏乡县"},{"code":"130525","name":"隆尧县"},{"code":"130526","name":"任县"},{"code":"130527","name":"南和县"},{"code":"130528","name":"宁晋县"},{"code":"130529","name":"巨鹿县"},{"code":"130530","name":"新河县"},{"code":"130531","name":"广宗县"},{"code":"130532","name":"平乡县"},{"code":"130533","name":"威县"},{"code":"130534","name":"清河县"},{"code":"130535","name":"临西县"},{"code":"130581","name":"南宫市"},{"code":"130582","name":"沙河市"}]},{"code":"1306","name":"保定市","childs":[{"code":"130602","name":"竞秀区"},{"code":"130606","name":"莲池区"},{"code":"130607","name":"满城区"},{"code":"130608","name":"清苑区"},{"code":"130609","name":"徐水区"},{"code":"130623","name":"涞水县"},{"code":"130624","name":"阜平县"},{"code":"130626","name":"定兴县"},{"code":"130627","name":"唐县"},{"code":"130628","name":"高阳县"},{"code":"130629","name":"容城县"},{"code":"130630","name":"涞源县"},{"code":"130631","name":"望都县"},{"code":"130632","name":"安新县"},{"code":"130633","name":"易县"},{"code":"130634","name":"曲阳县"},{"code":"130635","name":"蠡县"},{"code":"130636","name":"顺平县"},{"code":"130637","name":"博野县"},{"code":"130638","name":"雄县"},{"code":"130681","name":"涿州市"},{"code":"130683","name":"安国市"},{"code":"130684","name":"高碑店市"}]},{"code":"1307","name":"张家口市","childs":[{"code":"130702","name":"桥东区"},{"code":"130703","name":"桥西区"},{"code":"130705","name":"宣化区"},{"code":"130706","name":"下花园区"},{"code":"130708","name":"万全区"},{"code":"130709","name":"崇礼区"},{"code":"130722","name":"张北县"},{"code":"130723","name":"康保县"},{"code":"130724","name":"沽源县"},{"code":"130725","name":"尚义县"},{"code":"130726","name":"蔚县"},{"code":"130727","name":"阳原县"},{"code":"130728","name":"怀安县"},{"code":"130730","name":"怀来县"},{"code":"130731","name":"涿鹿县"},{"code":"130732","name":"赤城县"}]},{"code":"1308","name":"承德市","childs":[{"code":"130802","name":"双桥区"},{"code":"130803","name":"双滦区"},{"code":"130804","name":"鹰手营子矿区"},{"code":"130821","name":"承德县"},{"code":"130822","name":"兴隆县"},{"code":"130823","name":"平泉县"},{"code":"130824","name":"滦平县"},{"code":"130825","name":"隆化县"},{"code":"130826","name":"丰宁满族自治县"},{"code":"130827","name":"宽城满族自治县"},{"code":"130828","name":"围场满族蒙古族自治县"}]},{"code":"1309","name":"沧州市","childs":[{"code":"130902","name":"新华区"},{"code":"130903","name":"运河区"},{"code":"130921","name":"沧县"},{"code":"130922","name":"青县"},{"code":"130923","name":"东光县"},{"code":"130924","name":"海兴县"},{"code":"130925","name":"盐山县"},{"code":"130926","name":"肃宁县"},{"code":"130927","name":"南皮县"},{"code":"130928","name":"吴桥县"},{"code":"130929","name":"献县"},{"code":"130930","name":"孟村回族自治县"},{"code":"130981","name":"泊头市"},{"code":"130982","name":"任丘市"},{"code":"130983","name":"黄骅市"},{"code":"130984","name":"河间市"}]},{"code":"1310","name":"廊坊市","childs":[{"code":"131002","name":"安次区"},{"code":"131003","name":"广阳区"},{"code":"131022","name":"固安县"},{"code":"131023","name":"永清县"},{"code":"131024","name":"香河县"},{"code":"131025","name":"大城县"},{"code":"131026","name":"文安县"},{"code":"131028","name":"大厂回族自治县"},{"code":"131081","name":"霸州市"},{"code":"131082","name":"三河市"}]},{"code":"1311","name":"衡水市","childs":[{"code":"131102","name":"桃城区"},{"code":"131103","name":"冀州区"},{"code":"131121","name":"枣强县"},{"code":"131122","name":"武邑县"},{"code":"131123","name":"武强县"},{"code":"131124","name":"饶阳县"},{"code":"131125","name":"安平县"},{"code":"131126","name":"故城县"},{"code":"131127","name":"景县"},{"code":"131128","name":"阜城县"},{"code":"131182","name":"深州市"}]},{"code":"1390","name":"省直辖县级行政区划","childs":[{"code":"139001","name":"定州市"},{"code":"139002","name":"辛集市"}]}]},{"code":"14","name":"山西省","childs":[{"code":"1401","name":"太原市","childs":[{"code":"140105","name":"小店区"},{"code":"140106","name":"迎泽区"},{"code":"140107","name":"杏花岭区"},{"code":"140108","name":"尖草坪区"},{"code":"140109","name":"万柏林区"},{"code":"140110","name":"晋源区"},{"code":"140121","name":"清徐县"},{"code":"140122","name":"阳曲县"},{"code":"140123","name":"娄烦县"},{"code":"140181","name":"古交市"}]},{"code":"1402","name":"大同市","childs":[{"code":"140202","name":"城区"},{"code":"140203","name":"矿区"},{"code":"140211","name":"南郊区"},{"code":"140212","name":"新荣区"},{"code":"140221","name":"阳高县"},{"code":"140222","name":"天镇县"},{"code":"140223","name":"广灵县"},{"code":"140224","name":"灵丘县"},{"code":"140225","name":"浑源县"},{"code":"140226","name":"左云县"},{"code":"140227","name":"大同县"}]},{"code":"1403","name":"阳泉市","childs":[{"code":"140302","name":"城区"},{"code":"140303","name":"矿区"},{"code":"140311","name":"郊区"},{"code":"140321","name":"平定县"},{"code":"140322","name":"盂县"}]},{"code":"1404","name":"长治市","childs":[{"code":"140402","name":"城区"},{"code":"140411","name":"郊区"},{"code":"140421","name":"长治县"},{"code":"140423","name":"襄垣县"},{"code":"140424","name":"屯留县"},{"code":"140425","name":"平顺县"},{"code":"140426","name":"黎城县"},{"code":"140427","name":"壶关县"},{"code":"140428","name":"长子县"},{"code":"140429","name":"武乡县"},{"code":"140430","name":"沁县"},{"code":"140431","name":"沁源县"},{"code":"140481","name":"潞城市"}]},{"code":"1405","name":"晋城市","childs":[{"code":"140502","name":"城区"},{"code":"140521","name":"沁水县"},{"code":"140522","name":"阳城县"},{"code":"140524","name":"陵川县"},{"code":"140525","name":"泽州县"},{"code":"140581","name":"高平市"}]},{"code":"1406","name":"朔州市","childs":[{"code":"140602","name":"朔城区"},{"code":"140603","name":"平鲁区"},{"code":"140621","name":"山阴县"},{"code":"140622","name":"应县"},{"code":"140623","name":"右玉县"},{"code":"140624","name":"怀仁县"}]},{"code":"1407","name":"晋中市","childs":[{"code":"140702","name":"榆次区"},{"code":"140721","name":"榆社县"},{"code":"140722","name":"左权县"},{"code":"140723","name":"和顺县"},{"code":"140724","name":"昔阳县"},{"code":"140725","name":"寿阳县"},{"code":"140726","name":"太谷县"},{"code":"140727","name":"祁县"},{"code":"140728","name":"平遥县"},{"code":"140729","name":"灵石县"},{"code":"140781","name":"介休市"}]},{"code":"1408","name":"运城市","childs":[{"code":"140802","name":"盐湖区"},{"code":"140821","name":"临猗县"},{"code":"140822","name":"万荣县"},{"code":"140823","name":"闻喜县"},{"code":"140824","name":"稷山县"},{"code":"140825","name":"新绛县"},{"code":"140826","name":"绛县"},{"code":"140827","name":"垣曲县"},{"code":"140828","name":"夏县"},{"code":"140829","name":"平陆县"},{"code":"140830","name":"芮城县"},{"code":"140881","name":"永济市"},{"code":"140882","name":"河津市"}]},{"code":"1409","name":"忻州市","childs":[{"code":"140902","name":"忻府区"},{"code":"140921","name":"定襄县"},{"code":"140922","name":"五台县"},{"code":"140923","name":"代县"},{"code":"140924","name":"繁峙县"},{"code":"140925","name":"宁武县"},{"code":"140926","name":"静乐县"},{"code":"140927","name":"神池县"},{"code":"140928","name":"五寨县"},{"code":"140929","name":"岢岚县"},{"code":"140930","name":"河曲县"},{"code":"140931","name":"保德县"},{"code":"140932","name":"偏关县"},{"code":"140981","name":"原平市"}]},{"code":"1410","name":"临汾市","childs":[{"code":"141002","name":"尧都区"},{"code":"141021","name":"曲沃县"},{"code":"141022","name":"翼城县"},{"code":"141023","name":"襄汾县"},{"code":"141024","name":"洪洞县"},{"code":"141025","name":"古县"},{"code":"141026","name":"安泽县"},{"code":"141027","name":"浮山县"},{"code":"141028","name":"吉县"},{"code":"141029","name":"乡宁县"},{"code":"141030","name":"大宁县"},{"code":"141031","name":"隰县"},{"code":"141032","name":"永和县"},{"code":"141033","name":"蒲县"},{"code":"141034","name":"汾西县"},{"code":"141081","name":"侯马市"},{"code":"141082","name":"霍州市"}]},{"code":"1411","name":"吕梁市","childs":[{"code":"141102","name":"离石区"},{"code":"141121","name":"文水县"},{"code":"141122","name":"交城县"},{"code":"141123","name":"兴县"},{"code":"141124","name":"临县"},{"code":"141125","name":"柳林县"},{"code":"141126","name":"石楼县"},{"code":"141127","name":"岚县"},{"code":"141128","name":"方山县"},{"code":"141129","name":"中阳县"},{"code":"141130","name":"交口县"},{"code":"141181","name":"孝义市"},{"code":"141182","name":"汾阳市"}]}]},{"code":"15","name":"内蒙古自治区","childs":[{"code":"1501","name":"呼和浩特市","childs":[{"code":"150102","name":"新城区"},{"code":"150103","name":"回民区"},{"code":"150104","name":"玉泉区"},{"code":"150105","name":"赛罕区"},{"code":"150121","name":"土默特左旗"},{"code":"150122","name":"托克托县"},{"code":"150123","name":"和林格尔县"},{"code":"150124","name":"清水河县"},{"code":"150125","name":"武川县"}]},{"code":"1502","name":"包头市","childs":[{"code":"150202","name":"东河区"},{"code":"150203","name":"昆都仑区"},{"code":"150204","name":"青山区"},{"code":"150205","name":"石拐区"},{"code":"150206","name":"白云鄂博矿区"},{"code":"150207","name":"九原区"},{"code":"150221","name":"土默特右旗"},{"code":"150222","name":"固阳县"},{"code":"150223","name":"达尔罕茂明安联合旗"}]},{"code":"1503","name":"乌海市","childs":[{"code":"150302","name":"海勃湾区"},{"code":"150303","name":"海南区"},{"code":"150304","name":"乌达区"}]},{"code":"1504","name":"赤峰市","childs":[{"code":"150402","name":"红山区"},{"code":"150403","name":"元宝山区"},{"code":"150404","name":"松山区"},{"code":"150421","name":"阿鲁科尔沁旗"},{"code":"150422","name":"巴林左旗"},{"code":"150423","name":"巴林右旗"},{"code":"150424","name":"林西县"},{"code":"150425","name":"克什克腾旗"},{"code":"150426","name":"翁牛特旗"},{"code":"150428","name":"喀喇沁旗"},{"code":"150429","name":"宁城县"},{"code":"150430","name":"敖汉旗"}]},{"code":"1505","name":"通辽市","childs":[{"code":"150502","name":"科尔沁区"},{"code":"150521","name":"科尔沁左翼中旗"},{"code":"150522","name":"科尔沁左翼后旗"},{"code":"150523","name":"开鲁县"},{"code":"150524","name":"库伦旗"},{"code":"150525","name":"奈曼旗"},{"code":"150526","name":"扎鲁特旗"},{"code":"150581","name":"霍林郭勒市"}]},{"code":"1506","name":"鄂尔多斯市","childs":[{"code":"150602","name":"东胜区"},{"code":"150603","name":"康巴什区"},{"code":"150621","name":"达拉特旗"},{"code":"150622","name":"准格尔旗"},{"code":"150623","name":"鄂托克前旗"},{"code":"150624","name":"鄂托克旗"},{"code":"150625","name":"杭锦旗"},{"code":"150626","name":"乌审旗"},{"code":"150627","name":"伊金霍洛旗"}]},{"code":"1507","name":"呼伦贝尔市","childs":[{"code":"150702","name":"海拉尔区"},{"code":"150703","name":"扎赉诺尔区"},{"code":"150721","name":"阿荣旗"},{"code":"150722","name":"莫力达瓦达斡尔族自治旗"},{"code":"150723","name":"鄂伦春自治旗"},{"code":"150724","name":"鄂温克族自治旗"},{"code":"150725","name":"陈巴尔虎旗"},{"code":"150726","name":"新巴尔虎左旗"},{"code":"150727","name":"新巴尔虎右旗"},{"code":"150781","name":"满洲里市"},{"code":"150782","name":"牙克石市"},{"code":"150783","name":"扎兰屯市"},{"code":"150784","name":"额尔古纳市"},{"code":"150785","name":"根河市"}]},{"code":"1508","name":"巴彦淖尔市","childs":[{"code":"150802","name":"临河区"},{"code":"150821","name":"五原县"},{"code":"150822","name":"磴口县"},{"code":"150823","name":"乌拉特前旗"},{"code":"150824","name":"乌拉特中旗"},{"code":"150825","name":"乌拉特后旗"},{"code":"150826","name":"杭锦后旗"}]},{"code":"1509","name":"乌兰察布市","childs":[{"code":"150902","name":"集宁区"},{"code":"150921","name":"卓资县"},{"code":"150922","name":"化德县"},{"code":"150923","name":"商都县"},{"code":"150924","name":"兴和县"},{"code":"150925","name":"凉城县"},{"code":"150926","name":"察哈尔右翼前旗"},{"code":"150927","name":"察哈尔右翼中旗"},{"code":"150928","name":"察哈尔右翼后旗"},{"code":"150929","name":"四子王旗"},{"code":"150981","name":"丰镇市"}]},{"code":"1522","name":"兴安盟","childs":[{"code":"152201","name":"乌兰浩特市"},{"code":"152202","name":"阿尔山市"},{"code":"152221","name":"科尔沁右翼前旗"},{"code":"152222","name":"科尔沁右翼中旗"},{"code":"152223","name":"扎赉特旗"},{"code":"152224","name":"突泉县"}]},{"code":"1525","name":"锡林郭勒盟","childs":[{"code":"152501","name":"二连浩特市"},{"code":"152502","name":"锡林浩特市"},{"code":"152522","name":"阿巴嘎旗"},{"code":"152523","name":"苏尼特左旗"},{"code":"152524","name":"苏尼特右旗"},{"code":"152525","name":"东乌珠穆沁旗"},{"code":"152526","name":"西乌珠穆沁旗"},{"code":"152527","name":"太仆寺旗"},{"code":"152528","name":"镶黄旗"},{"code":"152529","name":"正镶白旗"},{"code":"152530","name":"正蓝旗"},{"code":"152531","name":"多伦县"}]},{"code":"1529","name":"阿拉善盟","childs":[{"code":"152921","name":"阿拉善左旗"},{"code":"152922","name":"阿拉善右旗"},{"code":"152923","name":"额济纳旗"}]}]},{"code":"21","name":"辽宁省","childs":[{"code":"2101","name":"沈阳市","childs":[{"code":"210102","name":"和平区"},{"code":"210103","name":"沈河区"},{"code":"210104","name":"大东区"},{"code":"210105","name":"皇姑区"},{"code":"210106","name":"铁西区"},{"code":"210111","name":"苏家屯区"},{"code":"210112","name":"浑南区"},{"code":"210113","name":"沈北新区"},{"code":"210114","name":"于洪区"},{"code":"210115","name":"辽中区"},{"code":"210123","name":"康平县"},{"code":"210124","name":"法库县"},{"code":"210181","name":"新民市"}]},{"code":"2102","name":"大连市","childs":[{"code":"210202","name":"中山区"},{"code":"210203","name":"西岗区"},{"code":"210204","name":"沙河口区"},{"code":"210211","name":"甘井子区"},{"code":"210212","name":"旅顺口区"},{"code":"210213","name":"金州区"},{"code":"210214","name":"普兰店区"},{"code":"210224","name":"长海县"},{"code":"210281","name":"瓦房店市"},{"code":"210283","name":"庄河市"}]},{"code":"2103","name":"鞍山市","childs":[{"code":"210302","name":"铁东区"},{"code":"210303","name":"铁西区"},{"code":"210304","name":"立山区"},{"code":"210311","name":"千山区"},{"code":"210321","name":"台安县"},{"code":"210323","name":"岫岩满族自治县"},{"code":"210381","name":"海城市"}]},{"code":"2104","name":"抚顺市","childs":[{"code":"210402","name":"新抚区"},{"code":"210403","name":"东洲区"},{"code":"210404","name":"望花区"},{"code":"210411","name":"顺城区"},{"code":"210421","name":"抚顺县"},{"code":"210422","name":"新宾满族自治县"},{"code":"210423","name":"清原满族自治县"}]},{"code":"2105","name":"本溪市","childs":[{"code":"210502","name":"平山区"},{"code":"210503","name":"溪湖区"},{"code":"210504","name":"明山区"},{"code":"210505","name":"南芬区"},{"code":"210521","name":"本溪满族自治县"},{"code":"210522","name":"桓仁满族自治县"}]},{"code":"2106","name":"丹东市","childs":[{"code":"210602","name":"元宝区"},{"code":"210603","name":"振兴区"},{"code":"210604","name":"振安区"},{"code":"210624","name":"宽甸满族自治县"},{"code":"210681","name":"东港市"},{"code":"210682","name":"凤城市"}]},{"code":"2107","name":"锦州市","childs":[{"code":"210702","name":"古塔区"},{"code":"210703","name":"凌河区"},{"code":"210711","name":"太和区"},{"code":"210726","name":"黑山县"},{"code":"210727","name":"义县"},{"code":"210781","name":"凌海市"},{"code":"210782","name":"北镇市"}]},{"code":"2108","name":"营口市","childs":[{"code":"210802","name":"站前区"},{"code":"210803","name":"西市区"},{"code":"210804","name":"鲅鱼圈区"},{"code":"210811","name":"老边区"},{"code":"210881","name":"盖州市"},{"code":"210882","name":"大石桥市"}]},{"code":"2109","name":"阜新市","childs":[{"code":"210902","name":"海州区"},{"code":"210903","name":"新邱区"},{"code":"210904","name":"太平区"},{"code":"210905","name":"清河门区"},{"code":"210911","name":"细河区"},{"code":"210921","name":"阜新蒙古族自治县"},{"code":"210922","name":"彰武县"}]},{"code":"2110","name":"辽阳市","childs":[{"code":"211002","name":"白塔区"},{"code":"211003","name":"文圣区"},{"code":"211004","name":"宏伟区"},{"code":"211005","name":"弓长岭区"},{"code":"211011","name":"太子河区"},{"code":"211021","name":"辽阳县"},{"code":"211081","name":"灯塔市"}]},{"code":"2111","name":"盘锦市","childs":[{"code":"211102","name":"双台子区"},{"code":"211103","name":"兴隆台区"},{"code":"211104","name":"大洼区"},{"code":"211122","name":"盘山县"}]},{"code":"2112","name":"铁岭市","childs":[{"code":"211202","name":"银州区"},{"code":"211204","name":"清河区"},{"code":"211221","name":"铁岭县"},{"code":"211223","name":"西丰县"},{"code":"211224","name":"昌图县"},{"code":"211281","name":"调兵山市"},{"code":"211282","name":"开原市"}]},{"code":"2113","name":"朝阳市","childs":[{"code":"211302","name":"双塔区"},{"code":"211303","name":"龙城区"},{"code":"211321","name":"朝阳县"},{"code":"211322","name":"建平县"},{"code":"211324","name":"喀喇沁左翼蒙古族自治县"},{"code":"211381","name":"北票市"},{"code":"211382","name":"凌源市"}]},{"code":"2114","name":"葫芦岛市","childs":[{"code":"211402","name":"连山区"},{"code":"211403","name":"龙港区"},{"code":"211404","name":"南票区"},{"code":"211421","name":"绥中县"},{"code":"211422","name":"建昌县"},{"code":"211481","name":"兴城市"}]}]},{"code":"22","name":"吉林省","childs":[{"code":"2201","name":"长春市","childs":[{"code":"220102","name":"南关区"},{"code":"220103","name":"宽城区"},{"code":"220104","name":"朝阳区"},{"code":"220105","name":"二道区"},{"code":"220106","name":"绿园区"},{"code":"220112","name":"双阳区"},{"code":"220113","name":"九台区"},{"code":"220122","name":"农安县"},{"code":"220182","name":"榆树市"},{"code":"220183","name":"德惠市"}]},{"code":"2202","name":"吉林市","childs":[{"code":"220202","name":"昌邑区"},{"code":"220203","name":"龙潭区"},{"code":"220204","name":"船营区"},{"code":"220211","name":"丰满区"},{"code":"220221","name":"永吉县"},{"code":"220281","name":"蛟河市"},{"code":"220282","name":"桦甸市"},{"code":"220283","name":"舒兰市"},{"code":"220284","name":"磐石市"}]},{"code":"2203","name":"四平市","childs":[{"code":"220302","name":"铁西区"},{"code":"220303","name":"铁东区"},{"code":"220322","name":"梨树县"},{"code":"220323","name":"伊通满族自治县"},{"code":"220381","name":"公主岭市"},{"code":"220382","name":"双辽市"}]},{"code":"2204","name":"辽源市","childs":[{"code":"220402","name":"龙山区"},{"code":"220403","name":"西安区"},{"code":"220421","name":"东丰县"},{"code":"220422","name":"东辽县"}]},{"code":"2205","name":"通化市","childs":[{"code":"220502","name":"东昌区"},{"code":"220503","name":"二道江区"},{"code":"220521","name":"通化县"},{"code":"220523","name":"辉南县"},{"code":"220524","name":"柳河县"},{"code":"220581","name":"梅河口市"},{"code":"220582","name":"集安市"}]},{"code":"2206","name":"白山市","childs":[{"code":"220602","name":"浑江区"},{"code":"220605","name":"江源区"},{"code":"220621","name":"抚松县"},{"code":"220622","name":"靖宇县"},{"code":"220623","name":"长白朝鲜族自治县"},{"code":"220681","name":"临江市"}]},{"code":"2207","name":"松原市","childs":[{"code":"220702","name":"宁江区"},{"code":"220721","name":"前郭尔罗斯蒙古族自治县"},{"code":"220722","name":"长岭县"},{"code":"220723","name":"乾安县"},{"code":"220781","name":"扶余市"}]},{"code":"2208","name":"白城市","childs":[{"code":"220802","name":"洮北区"},{"code":"220821","name":"镇赉县"},{"code":"220822","name":"通榆县"},{"code":"220881","name":"洮南市"},{"code":"220882","name":"大安市"}]},{"code":"2224","name":"延边朝鲜族自治州","childs":[{"code":"222401","name":"延吉市"},{"code":"222402","name":"图们市"},{"code":"222403","name":"敦化市"},{"code":"222404","name":"珲春市"},{"code":"222405","name":"龙井市"},{"code":"222406","name":"和龙市"},{"code":"222424","name":"汪清县"},{"code":"222426","name":"安图县"}]}]},{"code":"23","name":"黑龙江省","childs":[{"code":"2301","name":"哈尔滨市","childs":[{"code":"230102","name":"道里区"},{"code":"230103","name":"南岗区"},{"code":"230104","name":"道外区"},{"code":"230108","name":"平房区"},{"code":"230109","name":"松北区"},{"code":"230110","name":"香坊区"},{"code":"230111","name":"呼兰区"},{"code":"230112","name":"阿城区"},{"code":"230113","name":"双城区"},{"code":"230123","name":"依兰县"},{"code":"230124","name":"方正县"},{"code":"230125","name":"宾县"},{"code":"230126","name":"巴彦县"},{"code":"230127","name":"木兰县"},{"code":"230128","name":"通河县"},{"code":"230129","name":"延寿县"},{"code":"230183","name":"尚志市"},{"code":"230184","name":"五常市"}]},{"code":"2302","name":"齐齐哈尔市","childs":[{"code":"230202","name":"龙沙区"},{"code":"230203","name":"建华区"},{"code":"230204","name":"铁锋区"},{"code":"230205","name":"昂昂溪区"},{"code":"230206","name":"富拉尔基区"},{"code":"230207","name":"碾子山区"},{"code":"230208","name":"梅里斯达斡尔族区"},{"code":"230221","name":"龙江县"},{"code":"230223","name":"依安县"},{"code":"230224","name":"泰来县"},{"code":"230225","name":"甘南县"},{"code":"230227","name":"富裕县"},{"code":"230229","name":"克山县"},{"code":"230230","name":"克东县"},{"code":"230231","name":"拜泉县"},{"code":"230281","name":"讷河市"}]},{"code":"2303","name":"鸡西市","childs":[{"code":"230302","name":"鸡冠区"},{"code":"230303","name":"恒山区"},{"code":"230304","name":"滴道区"},{"code":"230305","name":"梨树区"},{"code":"230306","name":"城子河区"},{"code":"230307","name":"麻山区"},{"code":"230321","name":"鸡东县"},{"code":"230381","name":"虎林市"},{"code":"230382","name":"密山市"}]},{"code":"2304","name":"鹤岗市","childs":[{"code":"230402","name":"向阳区"},{"code":"230403","name":"工农区"},{"code":"230404","name":"南山区"},{"code":"230405","name":"兴安区"},{"code":"230406","name":"东山区"},{"code":"230407","name":"兴山区"},{"code":"230421","name":"萝北县"},{"code":"230422","name":"绥滨县"}]},{"code":"2305","name":"双鸭山市","childs":[{"code":"230502","name":"尖山区"},{"code":"230503","name":"岭东区"},{"code":"230505","name":"四方台区"},{"code":"230506","name":"宝山区"},{"code":"230521","name":"集贤县"},{"code":"230522","name":"友谊县"},{"code":"230523","name":"宝清县"},{"code":"230524","name":"饶河县"}]},{"code":"2306","name":"大庆市","childs":[{"code":"230602","name":"萨尔图区"},{"code":"230603","name":"龙凤区"},{"code":"230604","name":"让胡路区"},{"code":"230605","name":"红岗区"},{"code":"230606","name":"大同区"},{"code":"230621","name":"肇州县"},{"code":"230622","name":"肇源县"},{"code":"230623","name":"林甸县"},{"code":"230624","name":"杜尔伯特蒙古族自治县"}]},{"code":"2307","name":"伊春市","childs":[{"code":"230702","name":"伊春区"},{"code":"230703","name":"南岔区"},{"code":"230704","name":"友好区"},{"code":"230705","name":"西林区"},{"code":"230706","name":"翠峦区"},{"code":"230707","name":"新青区"},{"code":"230708","name":"美溪区"},{"code":"230709","name":"金山屯区"},{"code":"230710","name":"五营区"},{"code":"230711","name":"乌马河区"},{"code":"230712","name":"汤旺河区"},{"code":"230713","name":"带岭区"},{"code":"230714","name":"乌伊岭区"},{"code":"230715","name":"红星区"},{"code":"230716","name":"上甘岭区"},{"code":"230722","name":"嘉荫县"},{"code":"230781","name":"铁力市"}]},{"code":"2308","name":"佳木斯市","childs":[{"code":"230803","name":"向阳区"},{"code":"230804","name":"前进区"},{"code":"230805","name":"东风区"},{"code":"230811","name":"郊区"},{"code":"230822","name":"桦南县"},{"code":"230826","name":"桦川县"},{"code":"230828","name":"汤原县"},{"code":"230881","name":"同江市"},{"code":"230882","name":"富锦市"},{"code":"230883","name":"抚远市"}]},{"code":"2309","name":"七台河市","childs":[{"code":"230902","name":"新兴区"},{"code":"230903","name":"桃山区"},{"code":"230904","name":"茄子河区"},{"code":"230921","name":"勃利县"}]},{"code":"2310","name":"牡丹江市","childs":[{"code":"231002","name":"东安区"},{"code":"231003","name":"阳明区"},{"code":"231004","name":"爱民区"},{"code":"231005","name":"西安区"},{"code":"231025","name":"林口县"},{"code":"231081","name":"绥芬河市"},{"code":"231083","name":"海林市"},{"code":"231084","name":"宁安市"},{"code":"231085","name":"穆棱市"},{"code":"231086","name":"东宁市"}]},{"code":"2311","name":"黑河市","childs":[{"code":"231102","name":"爱辉区"},{"code":"231121","name":"嫩江县"},{"code":"231123","name":"逊克县"},{"code":"231124","name":"孙吴县"},{"code":"231181","name":"北安市"},{"code":"231182","name":"五大连池市"}]},{"code":"2312","name":"绥化市","childs":[{"code":"231202","name":"北林区"},{"code":"231221","name":"望奎县"},{"code":"231222","name":"兰西县"},{"code":"231223","name":"青冈县"},{"code":"231224","name":"庆安县"},{"code":"231225","name":"明水县"},{"code":"231226","name":"绥棱县"},{"code":"231281","name":"安达市"},{"code":"231282","name":"肇东市"},{"code":"231283","name":"海伦市"}]},{"code":"2327","name":"大兴安岭地区","childs":[{"code":"232721","name":"呼玛县"},{"code":"232722","name":"塔河县"},{"code":"232723","name":"漠河县"}]}]},{"code":"31","name":"上海市","childs":[{"code":"3101","name":"市辖区","childs":[{"code":"310101","name":"黄浦区"},{"code":"310104","name":"徐汇区"},{"code":"310105","name":"长宁区"},{"code":"310106","name":"静安区"},{"code":"310107","name":"普陀区"},{"code":"310109","name":"虹口区"},{"code":"310110","name":"杨浦区"},{"code":"310112","name":"闵行区"},{"code":"310113","name":"宝山区"},{"code":"310114","name":"嘉定区"},{"code":"310115","name":"浦东新区"},{"code":"310116","name":"金山区"},{"code":"310117","name":"松江区"},{"code":"310118","name":"青浦区"},{"code":"310120","name":"奉贤区"},{"code":"310151","name":"崇明区"}]}]},{"code":"32","name":"江苏省","childs":[{"code":"3201","name":"南京市","childs":[{"code":"320102","name":"玄武区"},{"code":"320104","name":"秦淮区"},{"code":"320105","name":"建邺区"},{"code":"320106","name":"鼓楼区"},{"code":"320111","name":"浦口区"},{"code":"320113","name":"栖霞区"},{"code":"320114","name":"雨花台区"},{"code":"320115","name":"江宁区"},{"code":"320116","name":"六合区"},{"code":"320117","name":"溧水区"},{"code":"320118","name":"高淳区"}]},{"code":"3202","name":"无锡市","childs":[{"code":"320205","name":"锡山区"},{"code":"320206","name":"惠山区"},{"code":"320211","name":"滨湖区"},{"code":"320213","name":"梁溪区"},{"code":"320214","name":"新吴区"},{"code":"320281","name":"江阴市"},{"code":"320282","name":"宜兴市"}]},{"code":"3203","name":"徐州市","childs":[{"code":"320302","name":"鼓楼区"},{"code":"320303","name":"云龙区"},{"code":"320305","name":"贾汪区"},{"code":"320311","name":"泉山区"},{"code":"320312","name":"铜山区"},{"code":"320321","name":"丰县"},{"code":"320322","name":"沛县"},{"code":"320324","name":"睢宁县"},{"code":"320381","name":"新沂市"},{"code":"320382","name":"邳州市"}]},{"code":"3204","name":"常州市","childs":[{"code":"320402","name":"天宁区"},{"code":"320404","name":"钟楼区"},{"code":"320411","name":"新北区"},{"code":"320412","name":"武进区"},{"code":"320413","name":"金坛区"},{"code":"320481","name":"溧阳市"}]},{"code":"3205","name":"苏州市","childs":[{"code":"320505","name":"虎丘区"},{"code":"320506","name":"吴中区"},{"code":"320507","name":"相城区"},{"code":"320508","name":"姑苏区"},{"code":"320509","name":"吴江区"},{"code":"320581","name":"常熟市"},{"code":"320582","name":"张家港市"},{"code":"320583","name":"昆山市"},{"code":"320585","name":"太仓市"}]},{"code":"3206","name":"南通市","childs":[{"code":"320602","name":"崇川区"},{"code":"320611","name":"港闸区"},{"code":"320612","name":"通州区"},{"code":"320621","name":"海安县"},{"code":"320623","name":"如东县"},{"code":"320681","name":"启东市"},{"code":"320682","name":"如皋市"},{"code":"320684","name":"海门市"}]},{"code":"3207","name":"连云港市","childs":[{"code":"320703","name":"连云区"},{"code":"320706","name":"海州区"},{"code":"320707","name":"赣榆区"},{"code":"320722","name":"东海县"},{"code":"320723","name":"灌云县"},{"code":"320724","name":"灌南县"}]},{"code":"3208","name":"淮安市","childs":[{"code":"320803","name":"淮安区"},{"code":"320804","name":"淮阴区"},{"code":"320812","name":"清江浦区"},{"code":"320813","name":"洪泽区"},{"code":"320826","name":"涟水县"},{"code":"320830","name":"盱眙县"},{"code":"320831","name":"金湖县"}]},{"code":"3209","name":"盐城市","childs":[{"code":"320902","name":"亭湖区"},{"code":"320903","name":"盐都区"},{"code":"320904","name":"大丰区"},{"code":"320921","name":"响水县"},{"code":"320922","name":"滨海县"},{"code":"320923","name":"阜宁县"},{"code":"320924","name":"射阳县"},{"code":"320925","name":"建湖县"},{"code":"320981","name":"东台市"}]},{"code":"3210","name":"扬州市","childs":[{"code":"321002","name":"广陵区"},{"code":"321003","name":"邗江区"},{"code":"321012","name":"江都区"},{"code":"321023","name":"宝应县"},{"code":"321081","name":"仪征市"},{"code":"321084","name":"高邮市"}]},{"code":"3211","name":"镇江市","childs":[{"code":"321102","name":"京口区"},{"code":"321111","name":"润州区"},{"code":"321112","name":"丹徒区"},{"code":"321181","name":"丹阳市"},{"code":"321182","name":"扬中市"},{"code":"321183","name":"句容市"}]},{"code":"3212","name":"泰州市","childs":[{"code":"321202","name":"海陵区"},{"code":"321203","name":"高港区"},{"code":"321204","name":"姜堰区"},{"code":"321281","name":"兴化市"},{"code":"321282","name":"靖江市"},{"code":"321283","name":"泰兴市"}]},{"code":"3213","name":"宿迁市","childs":[{"code":"321302","name":"宿城区"},{"code":"321311","name":"宿豫区"},{"code":"321322","name":"沭阳县"},{"code":"321323","name":"泗阳县"},{"code":"321324","name":"泗洪县"}]}]},{"code":"33","name":"浙江省","childs":[{"code":"3301","name":"杭州市","childs":[{"code":"330102","name":"上城区"},{"code":"330103","name":"下城区"},{"code":"330104","name":"江干区"},{"code":"330105","name":"拱墅区"},{"code":"330106","name":"西湖区"},{"code":"330108","name":"滨江区"},{"code":"330109","name":"萧山区"},{"code":"330110","name":"余杭区"},{"code":"330111","name":"富阳区"},{"code":"330122","name":"桐庐县"},{"code":"330127","name":"淳安县"},{"code":"330182","name":"建德市"},{"code":"330185","name":"临安市"}]},{"code":"3302","name":"宁波市","childs":[{"code":"330203","name":"海曙区"},{"code":"330204","name":"江东区"},{"code":"330205","name":"江北区"},{"code":"330206","name":"北仑区"},{"code":"330211","name":"镇海区"},{"code":"330212","name":"鄞州区"},{"code":"330225","name":"象山县"},{"code":"330226","name":"宁海县"},{"code":"330281","name":"余姚市"},{"code":"330282","name":"慈溪市"},{"code":"330283","name":"奉化市"}]},{"code":"3303","name":"温州市","childs":[{"code":"330302","name":"鹿城区"},{"code":"330303","name":"龙湾区"},{"code":"330304","name":"瓯海区"},{"code":"330305","name":"洞头区"},{"code":"330324","name":"永嘉县"},{"code":"330326","name":"平阳县"},{"code":"330327","name":"苍南县"},{"code":"330328","name":"文成县"},{"code":"330329","name":"泰顺县"},{"code":"330381","name":"瑞安市"},{"code":"330382","name":"乐清市"}]},{"code":"3304","name":"嘉兴市","childs":[{"code":"330402","name":"南湖区"},{"code":"330411","name":"秀洲区"},{"code":"330421","name":"嘉善县"},{"code":"330424","name":"海盐县"},{"code":"330481","name":"海宁市"},{"code":"330482","name":"平湖市"},{"code":"330483","name":"桐乡市"}]},{"code":"3305","name":"湖州市","childs":[{"code":"330502","name":"吴兴区"},{"code":"330503","name":"南浔区"},{"code":"330521","name":"德清县"},{"code":"330522","name":"长兴县"},{"code":"330523","name":"安吉县"}]},{"code":"3306","name":"绍兴市","childs":[{"code":"330602","name":"越城区"},{"code":"330603","name":"柯桥区"},{"code":"330604","name":"上虞区"},{"code":"330624","name":"新昌县"},{"code":"330681","name":"诸暨市"},{"code":"330683","name":"嵊州市"}]},{"code":"3307","name":"金华市","childs":[{"code":"330702","name":"婺城区"},{"code":"330703","name":"金东区"},{"code":"330723","name":"武义县"},{"code":"330726","name":"浦江县"},{"code":"330727","name":"磐安县"},{"code":"330781","name":"兰溪市"},{"code":"330782","name":"义乌市"},{"code":"330783","name":"东阳市"},{"code":"330784","name":"永康市"}]},{"code":"3308","name":"衢州市","childs":[{"code":"330802","name":"柯城区"},{"code":"330803","name":"衢江区"},{"code":"330822","name":"常山县"},{"code":"330824","name":"开化县"},{"code":"330825","name":"龙游县"},{"code":"330881","name":"江山市"}]},{"code":"3309","name":"舟山市","childs":[{"code":"330902","name":"定海区"},{"code":"330903","name":"普陀区"},{"code":"330921","name":"岱山县"},{"code":"330922","name":"嵊泗县"}]},{"code":"3310","name":"台州市","childs":[{"code":"331002","name":"椒江区"},{"code":"331003","name":"黄岩区"},{"code":"331004","name":"路桥区"},{"code":"331021","name":"玉环县"},{"code":"331022","name":"三门县"},{"code":"331023","name":"天台县"},{"code":"331024","name":"仙居县"},{"code":"331081","name":"温岭市"},{"code":"331082","name":"临海市"}]},{"code":"3311","name":"丽水市","childs":[{"code":"331102","name":"莲都区"},{"code":"331121","name":"青田县"},{"code":"331122","name":"缙云县"},{"code":"331123","name":"遂昌县"},{"code":"331124","name":"松阳县"},{"code":"331125","name":"云和县"},{"code":"331126","name":"庆元县"},{"code":"331127","name":"景宁畲族自治县"},{"code":"331181","name":"龙泉市"}]}]},{"code":"34","name":"安徽省","childs":[{"code":"3401","name":"合肥市","childs":[{"code":"340102","name":"瑶海区"},{"code":"340103","name":"庐阳区"},{"code":"340104","name":"蜀山区"},{"code":"340111","name":"包河区"},{"code":"340121","name":"长丰县"},{"code":"340122","name":"肥东县"},{"code":"340123","name":"肥西县"},{"code":"340124","name":"庐江县"},{"code":"340181","name":"巢湖市"}]},{"code":"3402","name":"芜湖市","childs":[{"code":"340202","name":"镜湖区"},{"code":"340203","name":"弋江区"},{"code":"340207","name":"鸠江区"},{"code":"340208","name":"三山区"},{"code":"340221","name":"芜湖县"},{"code":"340222","name":"繁昌县"},{"code":"340223","name":"南陵县"},{"code":"340225","name":"无为县"}]},{"code":"3403","name":"蚌埠市","childs":[{"code":"340302","name":"龙子湖区"},{"code":"340303","name":"蚌山区"},{"code":"340304","name":"禹会区"},{"code":"340311","name":"淮上区"},{"code":"340321","name":"怀远县"},{"code":"340322","name":"五河县"},{"code":"340323","name":"固镇县"}]},{"code":"3404","name":"淮南市","childs":[{"code":"340402","name":"大通区"},{"code":"340403","name":"田家庵区"},{"code":"340404","name":"谢家集区"},{"code":"340405","name":"八公山区"},{"code":"340406","name":"潘集区"},{"code":"340421","name":"凤台县"},{"code":"340422","name":"寿县"}]},{"code":"3405","name":"马鞍山市","childs":[{"code":"340503","name":"花山区"},{"code":"340504","name":"雨山区"},{"code":"340506","name":"博望区"},{"code":"340521","name":"当涂县"},{"code":"340522","name":"含山县"},{"code":"340523","name":"和县"}]},{"code":"3406","name":"淮北市","childs":[{"code":"340602","name":"杜集区"},{"code":"340603","name":"相山区"},{"code":"340604","name":"烈山区"},{"code":"340621","name":"濉溪县"}]},{"code":"3407","name":"铜陵市","childs":[{"code":"340705","name":"铜官区"},{"code":"340706","name":"义安区"},{"code":"340711","name":"郊区"},{"code":"340722","name":"枞阳县"}]},{"code":"3408","name":"安庆市","childs":[{"code":"340802","name":"迎江区"},{"code":"340803","name":"大观区"},{"code":"340811","name":"宜秀区"},{"code":"340822","name":"怀宁县"},{"code":"340824","name":"潜山县"},{"code":"340825","name":"太湖县"},{"code":"340826","name":"宿松县"},{"code":"340827","name":"望江县"},{"code":"340828","name":"岳西县"},{"code":"340881","name":"桐城市"}]},{"code":"3410","name":"黄山市","childs":[{"code":"341002","name":"屯溪区"},{"code":"341003","name":"黄山区"},{"code":"341004","name":"徽州区"},{"code":"341021","name":"歙县"},{"code":"341022","name":"休宁县"},{"code":"341023","name":"黟县"},{"code":"341024","name":"祁门县"}]},{"code":"3411","name":"滁州市","childs":[{"code":"341102","name":"琅琊区"},{"code":"341103","name":"南谯区"},{"code":"341122","name":"来安县"},{"code":"341124","name":"全椒县"},{"code":"341125","name":"定远县"},{"code":"341126","name":"凤阳县"},{"code":"341181","name":"天长市"},{"code":"341182","name":"明光市"}]},{"code":"3412","name":"阜阳市","childs":[{"code":"341202","name":"颍州区"},{"code":"341203","name":"颍东区"},{"code":"341204","name":"颍泉区"},{"code":"341221","name":"临泉县"},{"code":"341222","name":"太和县"},{"code":"341225","name":"阜南县"},{"code":"341226","name":"颍上县"},{"code":"341282","name":"界首市"}]},{"code":"3413","name":"宿州市","childs":[{"code":"341302","name":"埇桥区"},{"code":"341321","name":"砀山县"},{"code":"341322","name":"萧县"},{"code":"341323","name":"灵璧县"},{"code":"341324","name":"泗县"}]},{"code":"3415","name":"六安市","childs":[{"code":"341502","name":"金安区"},{"code":"341503","name":"裕安区"},{"code":"341504","name":"叶集区"},{"code":"341522","name":"霍邱县"},{"code":"341523","name":"舒城县"},{"code":"341524","name":"金寨县"},{"code":"341525","name":"霍山县"}]},{"code":"3416","name":"亳州市","childs":[{"code":"341602","name":"谯城区"},{"code":"341621","name":"涡阳县"},{"code":"341622","name":"蒙城县"},{"code":"341623","name":"利辛县"}]},{"code":"3417","name":"池州市","childs":[{"code":"341702","name":"贵池区"},{"code":"341721","name":"东至县"},{"code":"341722","name":"石台县"},{"code":"341723","name":"青阳县"}]},{"code":"3418","name":"宣城市","childs":[{"code":"341802","name":"宣州区"},{"code":"341821","name":"郎溪县"},{"code":"341822","name":"广德县"},{"code":"341823","name":"泾县"},{"code":"341824","name":"绩溪县"},{"code":"341825","name":"旌德县"},{"code":"341881","name":"宁国市"}]}]},{"code":"35","name":"福建省","childs":[{"code":"3501","name":"福州市","childs":[{"code":"350102","name":"鼓楼区"},{"code":"350103","name":"台江区"},{"code":"350104","name":"仓山区"},{"code":"350105","name":"马尾区"},{"code":"350111","name":"晋安区"},{"code":"350121","name":"闽侯县"},{"code":"350122","name":"连江县"},{"code":"350123","name":"罗源县"},{"code":"350124","name":"闽清县"},{"code":"350125","name":"永泰县"},{"code":"350128","name":"平潭县"},{"code":"350181","name":"福清市"},{"code":"350182","name":"长乐市"}]},{"code":"3502","name":"厦门市","childs":[{"code":"350203","name":"思明区"},{"code":"350205","name":"海沧区"},{"code":"350206","name":"湖里区"},{"code":"350211","name":"集美区"},{"code":"350212","name":"同安区"},{"code":"350213","name":"翔安区"}]},{"code":"3503","name":"莆田市","childs":[{"code":"350302","name":"城厢区"},{"code":"350303","name":"涵江区"},{"code":"350304","name":"荔城区"},{"code":"350305","name":"秀屿区"},{"code":"350322","name":"仙游县"}]},{"code":"3504","name":"三明市","childs":[{"code":"350402","name":"梅列区"},{"code":"350403","name":"三元区"},{"code":"350421","name":"明溪县"},{"code":"350423","name":"清流县"},{"code":"350424","name":"宁化县"},{"code":"350425","name":"大田县"},{"code":"350426","name":"尤溪县"},{"code":"350427","name":"沙县"},{"code":"350428","name":"将乐县"},{"code":"350429","name":"泰宁县"},{"code":"350430","name":"建宁县"},{"code":"350481","name":"永安市"}]},{"code":"3505","name":"泉州市","childs":[{"code":"350502","name":"鲤城区"},{"code":"350503","name":"丰泽区"},{"code":"350504","name":"洛江区"},{"code":"350505","name":"泉港区"},{"code":"350521","name":"惠安县"},{"code":"350524","name":"安溪县"},{"code":"350525","name":"永春县"},{"code":"350526","name":"德化县"},{"code":"350527","name":"金门县"},{"code":"350581","name":"石狮市"},{"code":"350582","name":"晋江市"},{"code":"350583","name":"南安市"}]},{"code":"3506","name":"漳州市","childs":[{"code":"350602","name":"芗城区"},{"code":"350603","name":"龙文区"},{"code":"350622","name":"云霄县"},{"code":"350623","name":"漳浦县"},{"code":"350624","name":"诏安县"},{"code":"350625","name":"长泰县"},{"code":"350626","name":"东山县"},{"code":"350627","name":"南靖县"},{"code":"350628","name":"平和县"},{"code":"350629","name":"华安县"},{"code":"350681","name":"龙海市"}]},{"code":"3507","name":"南平市","childs":[{"code":"350702","name":"延平区"},{"code":"350703","name":"建阳区"},{"code":"350721","name":"顺昌县"},{"code":"350722","name":"浦城县"},{"code":"350723","name":"光泽县"},{"code":"350724","name":"松溪县"},{"code":"350725","name":"政和县"},{"code":"350781","name":"邵武市"},{"code":"350782","name":"武夷山市"},{"code":"350783","name":"建瓯市"}]},{"code":"3508","name":"龙岩市","childs":[{"code":"350802","name":"新罗区"},{"code":"350803","name":"永定区"},{"code":"350821","name":"长汀县"},{"code":"350823","name":"上杭县"},{"code":"350824","name":"武平县"},{"code":"350825","name":"连城县"},{"code":"350881","name":"漳平市"}]},{"code":"3509","name":"宁德市","childs":[{"code":"350902","name":"蕉城区"},{"code":"350921","name":"霞浦县"},{"code":"350922","name":"古田县"},{"code":"350923","name":"屏南县"},{"code":"350924","name":"寿宁县"},{"code":"350925","name":"周宁县"},{"code":"350926","name":"柘荣县"},{"code":"350981","name":"福安市"},{"code":"350982","name":"福鼎市"}]}]},{"code":"36","name":"江西省","childs":[{"code":"3601","name":"南昌市","childs":[{"code":"360102","name":"东湖区"},{"code":"360103","name":"西湖区"},{"code":"360104","name":"青云谱区"},{"code":"360105","name":"湾里区"},{"code":"360111","name":"青山湖区"},{"code":"360112","name":"新建区"},{"code":"360121","name":"南昌县"},{"code":"360123","name":"安义县"},{"code":"360124","name":"进贤县"}]},{"code":"3602","name":"景德镇市","childs":[{"code":"360202","name":"昌江区"},{"code":"360203","name":"珠山区"},{"code":"360222","name":"浮梁县"},{"code":"360281","name":"乐平市"}]},{"code":"3603","name":"萍乡市","childs":[{"code":"360302","name":"安源区"},{"code":"360313","name":"湘东区"},{"code":"360321","name":"莲花县"},{"code":"360322","name":"上栗县"},{"code":"360323","name":"芦溪县"}]},{"code":"3604","name":"九江市","childs":[{"code":"360402","name":"濂溪区"},{"code":"360403","name":"浔阳区"},{"code":"360421","name":"九江县"},{"code":"360423","name":"武宁县"},{"code":"360424","name":"修水县"},{"code":"360425","name":"永修县"},{"code":"360426","name":"德安县"},{"code":"360428","name":"都昌县"},{"code":"360429","name":"湖口县"},{"code":"360430","name":"彭泽县"},{"code":"360481","name":"瑞昌市"},{"code":"360482","name":"共青城市"},{"code":"360483","name":"庐山市"}]},{"code":"3605","name":"新余市","childs":[{"code":"360502","name":"渝水区"},{"code":"360521","name":"分宜县"}]},{"code":"3606","name":"鹰潭市","childs":[{"code":"360602","name":"月湖区"},{"code":"360622","name":"余江县"},{"code":"360681","name":"贵溪市"}]},{"code":"3607","name":"赣州市","childs":[{"code":"360702","name":"章贡区"},{"code":"360703","name":"南康区"},{"code":"360721","name":"赣县"},{"code":"360722","name":"信丰县"},{"code":"360723","name":"大余县"},{"code":"360724","name":"上犹县"},{"code":"360725","name":"崇义县"},{"code":"360726","name":"安远县"},{"code":"360727","name":"龙南县"},{"code":"360728","name":"定南县"},{"code":"360729","name":"全南县"},{"code":"360730","name":"宁都县"},{"code":"360731","name":"于都县"},{"code":"360732","name":"兴国县"},{"code":"360733","name":"会昌县"},{"code":"360734","name":"寻乌县"},{"code":"360735","name":"石城县"},{"code":"360781","name":"瑞金市"}]},{"code":"3608","name":"吉安市","childs":[{"code":"360802","name":"吉州区"},{"code":"360803","name":"青原区"},{"code":"360821","name":"吉安县"},{"code":"360822","name":"吉水县"},{"code":"360823","name":"峡江县"},{"code":"360824","name":"新干县"},{"code":"360825","name":"永丰县"},{"code":"360826","name":"泰和县"},{"code":"360827","name":"遂川县"},{"code":"360828","name":"万安县"},{"code":"360829","name":"安福县"},{"code":"360830","name":"永新县"},{"code":"360881","name":"井冈山市"}]},{"code":"3609","name":"宜春市","childs":[{"code":"360902","name":"袁州区"},{"code":"360921","name":"奉新县"},{"code":"360922","name":"万载县"},{"code":"360923","name":"上高县"},{"code":"360924","name":"宜丰县"},{"code":"360925","name":"靖安县"},{"code":"360926","name":"铜鼓县"},{"code":"360981","name":"丰城市"},{"code":"360982","name":"樟树市"},{"code":"360983","name":"高安市"}]},{"code":"3610","name":"抚州市","childs":[{"code":"361002","name":"临川区"},{"code":"361021","name":"南城县"},{"code":"361022","name":"黎川县"},{"code":"361023","name":"南丰县"},{"code":"361024","name":"崇仁县"},{"code":"361025","name":"乐安县"},{"code":"361026","name":"宜黄县"},{"code":"361027","name":"金溪县"},{"code":"361028","name":"资溪县"},{"code":"361029","name":"东乡县"},{"code":"361030","name":"广昌县"}]},{"code":"3611","name":"上饶市","childs":[{"code":"361102","name":"信州区"},{"code":"361103","name":"广丰区"},{"code":"361121","name":"上饶县"},{"code":"361123","name":"玉山县"},{"code":"361124","name":"铅山县"},{"code":"361125","name":"横峰县"},{"code":"361126","name":"弋阳县"},{"code":"361127","name":"余干县"},{"code":"361128","name":"鄱阳县"},{"code":"361129","name":"万年县"},{"code":"361130","name":"婺源县"},{"code":"361181","name":"德兴市"}]}]},{"code":"37","name":"山东省","childs":[{"code":"3701","name":"济南市","childs":[{"code":"370102","name":"历下区"},{"code":"370103","name":"市中区"},{"code":"370104","name":"槐荫区"},{"code":"370105","name":"天桥区"},{"code":"370112","name":"历城区"},{"code":"370113","name":"长清区"},{"code":"370124","name":"平阴县"},{"code":"370125","name":"济阳县"},{"code":"370126","name":"商河县"},{"code":"370181","name":"章丘市"}]},{"code":"3702","name":"青岛市","childs":[{"code":"370202","name":"市南区"},{"code":"370203","name":"市北区"},{"code":"370211","name":"黄岛区"},{"code":"370212","name":"崂山区"},{"code":"370213","name":"李沧区"},{"code":"370214","name":"城阳区"},{"code":"370281","name":"胶州市"},{"code":"370282","name":"即墨市"},{"code":"370283","name":"平度市"},{"code":"370285","name":"莱西市"}]},{"code":"3703","name":"淄博市","childs":[{"code":"370302","name":"淄川区"},{"code":"370303","name":"张店区"},{"code":"370304","name":"博山区"},{"code":"370305","name":"临淄区"},{"code":"370306","name":"周村区"},{"code":"370321","name":"桓台县"},{"code":"370322","name":"高青县"},{"code":"370323","name":"沂源县"}]},{"code":"3704","name":"枣庄市","childs":[{"code":"370402","name":"市中区"},{"code":"370403","name":"薛城区"},{"code":"370404","name":"峄城区"},{"code":"370405","name":"台儿庄区"},{"code":"370406","name":"山亭区"},{"code":"370481","name":"滕州市"}]},{"code":"3705","name":"东营市","childs":[{"code":"370502","name":"东营区"},{"code":"370503","name":"河口区"},{"code":"370505","name":"垦利区"},{"code":"370522","name":"利津县"},{"code":"370523","name":"广饶县"}]},{"code":"3706","name":"烟台市","childs":[{"code":"370602","name":"芝罘区"},{"code":"370611","name":"福山区"},{"code":"370612","name":"牟平区"},{"code":"370613","name":"莱山区"},{"code":"370634","name":"长岛县"},{"code":"370681","name":"龙口市"},{"code":"370682","name":"莱阳市"},{"code":"370683","name":"莱州市"},{"code":"370684","name":"蓬莱市"},{"code":"370685","name":"招远市"},{"code":"370686","name":"栖霞市"},{"code":"370687","name":"海阳市"}]},{"code":"3707","name":"潍坊市","childs":[{"code":"370702","name":"潍城区"},{"code":"370703","name":"寒亭区"},{"code":"370704","name":"坊子区"},{"code":"370705","name":"奎文区"},{"code":"370724","name":"临朐县"},{"code":"370725","name":"昌乐县"},{"code":"370781","name":"青州市"},{"code":"370782","name":"诸城市"},{"code":"370783","name":"寿光市"},{"code":"370784","name":"安丘市"},{"code":"370785","name":"高密市"},{"code":"370786","name":"昌邑市"}]},{"code":"3708","name":"济宁市","childs":[{"code":"370811","name":"任城区"},{"code":"370812","name":"兖州区"},{"code":"370826","name":"微山县"},{"code":"370827","name":"鱼台县"},{"code":"370828","name":"金乡县"},{"code":"370829","name":"嘉祥县"},{"code":"370830","name":"汶上县"},{"code":"370831","name":"泗水县"},{"code":"370832","name":"梁山县"},{"code":"370881","name":"曲阜市"},{"code":"370883","name":"邹城市"}]},{"code":"3709","name":"泰安市","childs":[{"code":"370902","name":"泰山区"},{"code":"370911","name":"岱岳区"},{"code":"370921","name":"宁阳县"},{"code":"370923","name":"东平县"},{"code":"370982","name":"新泰市"},{"code":"370983","name":"肥城市"}]},{"code":"3710","name":"威海市","childs":[{"code":"371002","name":"环翠区"},{"code":"371003","name":"文登区"},{"code":"371082","name":"荣成市"},{"code":"371083","name":"乳山市"}]},{"code":"3711","name":"日照市","childs":[{"code":"371102","name":"东港区"},{"code":"371103","name":"岚山区"},{"code":"371121","name":"五莲县"},{"code":"371122","name":"莒县"}]},{"code":"3712","name":"莱芜市","childs":[{"code":"371202","name":"莱城区"},{"code":"371203","name":"钢城区"}]},{"code":"3713","name":"临沂市","childs":[{"code":"371302","name":"兰山区"},{"code":"371311","name":"罗庄区"},{"code":"371312","name":"河东区"},{"code":"371321","name":"沂南县"},{"code":"371322","name":"郯城县"},{"code":"371323","name":"沂水县"},{"code":"371324","name":"兰陵县"},{"code":"371325","name":"费县"},{"code":"371326","name":"平邑县"},{"code":"371327","name":"莒南县"},{"code":"371328","name":"蒙阴县"},{"code":"371329","name":"临沭县"}]},{"code":"3714","name":"德州市","childs":[{"code":"371402","name":"德城区"},{"code":"371403","name":"陵城区"},{"code":"371422","name":"宁津县"},{"code":"371423","name":"庆云县"},{"code":"371424","name":"临邑县"},{"code":"371425","name":"齐河县"},{"code":"371426","name":"平原县"},{"code":"371427","name":"夏津县"},{"code":"371428","name":"武城县"},{"code":"371481","name":"乐陵市"},{"code":"371482","name":"禹城市"}]},{"code":"3715","name":"聊城市","childs":[{"code":"371502","name":"东昌府区"},{"code":"371521","name":"阳谷县"},{"code":"371522","name":"莘县"},{"code":"371523","name":"茌平县"},{"code":"371524","name":"东阿县"},{"code":"371525","name":"冠县"},{"code":"371526","name":"高唐县"},{"code":"371581","name":"临清市"}]},{"code":"3716","name":"滨州市","childs":[{"code":"371602","name":"滨城区"},{"code":"371603","name":"沾化区"},{"code":"371621","name":"惠民县"},{"code":"371622","name":"阳信县"},{"code":"371623","name":"无棣县"},{"code":"371625","name":"博兴县"},{"code":"371626","name":"邹平县"}]},{"code":"3717","name":"菏泽市","childs":[{"code":"371702","name":"牡丹区"},{"code":"371703","name":"定陶区"},{"code":"371721","name":"曹县"},{"code":"371722","name":"单县"},{"code":"371723","name":"成武县"},{"code":"371724","name":"巨野县"},{"code":"371725","name":"郓城县"},{"code":"371726","name":"鄄城县"},{"code":"371728","name":"东明县"}]}]},{"code":"41","name":"河南省","childs":[{"code":"4101","name":"郑州市","childs":[{"code":"410102","name":"中原区"},{"code":"410103","name":"二七区"},{"code":"410104","name":"管城回族区"},{"code":"410105","name":"金水区"},{"code":"410106","name":"上街区"},{"code":"410108","name":"惠济区"},{"code":"410122","name":"中牟县"},{"code":"410181","name":"巩义市"},{"code":"410182","name":"荥阳市"},{"code":"410183","name":"新密市"},{"code":"410184","name":"新郑市"},{"code":"410185","name":"登封市"}]},{"code":"4102","name":"开封市","childs":[{"code":"410202","name":"龙亭区"},{"code":"410203","name":"顺河回族区"},{"code":"410204","name":"鼓楼区"},{"code":"410205","name":"禹王台区"},{"code":"410211","name":"金明区"},{"code":"410212","name":"祥符区"},{"code":"410221","name":"杞县"},{"code":"410222","name":"通许县"},{"code":"410223","name":"尉氏县"},{"code":"410225","name":"兰考县"}]},{"code":"4103","name":"洛阳市","childs":[{"code":"410302","name":"老城区"},{"code":"410303","name":"西工区"},{"code":"410304","name":"瀍河回族区"},{"code":"410305","name":"涧西区"},{"code":"410306","name":"吉利区"},{"code":"410311","name":"洛龙区"},{"code":"410322","name":"孟津县"},{"code":"410323","name":"新安县"},{"code":"410324","name":"栾川县"},{"code":"410325","name":"嵩县"},{"code":"410326","name":"汝阳县"},{"code":"410327","name":"宜阳县"},{"code":"410328","name":"洛宁县"},{"code":"410329","name":"伊川县"},{"code":"410381","name":"偃师市"}]},{"code":"4104","name":"平顶山市","childs":[{"code":"410402","name":"新华区"},{"code":"410403","name":"卫东区"},{"code":"410404","name":"石龙区"},{"code":"410411","name":"湛河区"},{"code":"410421","name":"宝丰县"},{"code":"410422","name":"叶县"},{"code":"410423","name":"鲁山县"},{"code":"410425","name":"郏县"},{"code":"410481","name":"舞钢市"},{"code":"410482","name":"汝州市"}]},{"code":"4105","name":"安阳市","childs":[{"code":"410502","name":"文峰区"},{"code":"410503","name":"北关区"},{"code":"410505","name":"殷都区"},{"code":"410506","name":"龙安区"},{"code":"410522","name":"安阳县"},{"code":"410523","name":"汤阴县"},{"code":"410526","name":"滑县"},{"code":"410527","name":"内黄县"},{"code":"410581","name":"林州市"}]},{"code":"4106","name":"鹤壁市","childs":[{"code":"410602","name":"鹤山区"},{"code":"410603","name":"山城区"},{"code":"410611","name":"淇滨区"},{"code":"410621","name":"浚县"},{"code":"410622","name":"淇县"}]},{"code":"4107","name":"新乡市","childs":[{"code":"410702","name":"红旗区"},{"code":"410703","name":"卫滨区"},{"code":"410704","name":"凤泉区"},{"code":"410711","name":"牧野区"},{"code":"410721","name":"新乡县"},{"code":"410724","name":"获嘉县"},{"code":"410725","name":"原阳县"},{"code":"410726","name":"延津县"},{"code":"410727","name":"封丘县"},{"code":"410728","name":"长垣县"},{"code":"410781","name":"卫辉市"},{"code":"410782","name":"辉县市"}]},{"code":"4108","name":"焦作市","childs":[{"code":"410802","name":"解放区"},{"code":"410803","name":"中站区"},{"code":"410804","name":"马村区"},{"code":"410811","name":"山阳区"},{"code":"410821","name":"修武县"},{"code":"410822","name":"博爱县"},{"code":"410823","name":"武陟县"},{"code":"410825","name":"温县"},{"code":"410882","name":"沁阳市"},{"code":"410883","name":"孟州市"}]},{"code":"4109","name":"濮阳市","childs":[{"code":"410902","name":"华龙区"},{"code":"410922","name":"清丰县"},{"code":"410923","name":"南乐县"},{"code":"410926","name":"范县"},{"code":"410927","name":"台前县"},{"code":"410928","name":"濮阳县"}]},{"code":"4110","name":"许昌市","childs":[{"code":"411002","name":"魏都区"},{"code":"411023","name":"许昌县"},{"code":"411024","name":"鄢陵县"},{"code":"411025","name":"襄城县"},{"code":"411081","name":"禹州市"},{"code":"411082","name":"长葛市"}]},{"code":"4111","name":"漯河市","childs":[{"code":"411102","name":"源汇区"},{"code":"411103","name":"郾城区"},{"code":"411104","name":"召陵区"},{"code":"411121","name":"舞阳县"},{"code":"411122","name":"临颍县"}]},{"code":"4112","name":"三门峡市","childs":[{"code":"411202","name":"湖滨区"},{"code":"411203","name":"陕州区"},{"code":"411221","name":"渑池县"},{"code":"411224","name":"卢氏县"},{"code":"411281","name":"义马市"},{"code":"411282","name":"灵宝市"}]},{"code":"4113","name":"南阳市","childs":[{"code":"411302","name":"宛城区"},{"code":"411303","name":"卧龙区"},{"code":"411321","name":"南召县"},{"code":"411322","name":"方城县"},{"code":"411323","name":"西峡县"},{"code":"411324","name":"镇平县"},{"code":"411325","name":"内乡县"},{"code":"411326","name":"淅川县"},{"code":"411327","name":"社旗县"},{"code":"411328","name":"唐河县"},{"code":"411329","name":"新野县"},{"code":"411330","name":"桐柏县"},{"code":"411381","name":"邓州市"}]},{"code":"4114","name":"商丘市","childs":[{"code":"411402","name":"梁园区"},{"code":"411403","name":"睢阳区"},{"code":"411421","name":"民权县"},{"code":"411422","name":"睢县"},{"code":"411423","name":"宁陵县"},{"code":"411424","name":"柘城县"},{"code":"411425","name":"虞城县"},{"code":"411426","name":"夏邑县"},{"code":"411481","name":"永城市"}]},{"code":"4115","name":"信阳市","childs":[{"code":"411502","name":"浉河区"},{"code":"411503","name":"平桥区"},{"code":"411521","name":"罗山县"},{"code":"411522","name":"光山县"},{"code":"411523","name":"新县"},{"code":"411524","name":"商城县"},{"code":"411525","name":"固始县"},{"code":"411526","name":"潢川县"},{"code":"411527","name":"淮滨县"},{"code":"411528","name":"息县"}]},{"code":"4116","name":"周口市","childs":[{"code":"411602","name":"川汇区"},{"code":"411621","name":"扶沟县"},{"code":"411622","name":"西华县"},{"code":"411623","name":"商水县"},{"code":"411624","name":"沈丘县"},{"code":"411625","name":"郸城县"},{"code":"411626","name":"淮阳县"},{"code":"411627","name":"太康县"},{"code":"411628","name":"鹿邑县"},{"code":"411681","name":"项城市"}]},{"code":"4117","name":"驻马店市","childs":[{"code":"411702","name":"驿城区"},{"code":"411721","name":"西平县"},{"code":"411722","name":"上蔡县"},{"code":"411723","name":"平舆县"},{"code":"411724","name":"正阳县"},{"code":"411725","name":"确山县"},{"code":"411726","name":"泌阳县"},{"code":"411727","name":"汝南县"},{"code":"411728","name":"遂平县"},{"code":"411729","name":"新蔡县"}]},{"code":"4190","name":"省直辖县级行政区划","childs":[{"code":"419001","name":"济源市"}]}]},{"code":"42","name":"湖北省","childs":[{"code":"4201","name":"武汉市","childs":[{"code":"420102","name":"江岸区"},{"code":"420103","name":"江汉区"},{"code":"420104","name":"硚口区"},{"code":"420105","name":"汉阳区"},{"code":"420106","name":"武昌区"},{"code":"420107","name":"青山区"},{"code":"420111","name":"洪山区"},{"code":"420112","name":"东西湖区"},{"code":"420113","name":"汉南区"},{"code":"420114","name":"蔡甸区"},{"code":"420115","name":"江夏区"},{"code":"420116","name":"黄陂区"},{"code":"420117","name":"新洲区"}]},{"code":"4202","name":"黄石市","childs":[{"code":"420202","name":"黄石港区"},{"code":"420203","name":"西塞山区"},{"code":"420204","name":"下陆区"},{"code":"420205","name":"铁山区"},{"code":"420222","name":"阳新县"},{"code":"420281","name":"大冶市"}]},{"code":"4203","name":"十堰市","childs":[{"code":"420302","name":"茅箭区"},{"code":"420303","name":"张湾区"},{"code":"420304","name":"郧阳区"},{"code":"420322","name":"郧西县"},{"code":"420323","name":"竹山县"},{"code":"420324","name":"竹溪县"},{"code":"420325","name":"房县"},{"code":"420381","name":"丹江口市"}]},{"code":"4205","name":"宜昌市","childs":[{"code":"420502","name":"西陵区"},{"code":"420503","name":"伍家岗区"},{"code":"420504","name":"点军区"},{"code":"420505","name":"猇亭区"},{"code":"420506","name":"夷陵区"},{"code":"420525","name":"远安县"},{"code":"420526","name":"兴山县"},{"code":"420527","name":"秭归县"},{"code":"420528","name":"长阳土家族自治县"},{"code":"420529","name":"五峰土家族自治县"},{"code":"420581","name":"宜都市"},{"code":"420582","name":"当阳市"},{"code":"420583","name":"枝江市"}]},{"code":"4206","name":"襄阳市","childs":[{"code":"420602","name":"襄城区"},{"code":"420606","name":"樊城区"},{"code":"420607","name":"襄州区"},{"code":"420624","name":"南漳县"},{"code":"420625","name":"谷城县"},{"code":"420626","name":"保康县"},{"code":"420682","name":"老河口市"},{"code":"420683","name":"枣阳市"},{"code":"420684","name":"宜城市"}]},{"code":"4207","name":"鄂州市","childs":[{"code":"420702","name":"梁子湖区"},{"code":"420703","name":"华容区"},{"code":"420704","name":"鄂城区"}]},{"code":"4208","name":"荆门市","childs":[{"code":"420802","name":"东宝区"},{"code":"420804","name":"掇刀区"},{"code":"420821","name":"京山县"},{"code":"420822","name":"沙洋县"},{"code":"420881","name":"钟祥市"}]},{"code":"4209","name":"孝感市","childs":[{"code":"420902","name":"孝南区"},{"code":"420921","name":"孝昌县"},{"code":"420922","name":"大悟县"},{"code":"420923","name":"云梦县"},{"code":"420981","name":"应城市"},{"code":"420982","name":"安陆市"},{"code":"420984","name":"汉川市"}]},{"code":"4210","name":"荆州市","childs":[{"code":"421002","name":"沙市区"},{"code":"421003","name":"荆州区"},{"code":"421022","name":"公安县"},{"code":"421023","name":"监利县"},{"code":"421024","name":"江陵县"},{"code":"421081","name":"石首市"},{"code":"421083","name":"洪湖市"},{"code":"421087","name":"松滋市"}]},{"code":"4211","name":"黄冈市","childs":[{"code":"421102","name":"黄州区"},{"code":"421121","name":"团风县"},{"code":"421122","name":"红安县"},{"code":"421123","name":"罗田县"},{"code":"421124","name":"英山县"},{"code":"421125","name":"浠水县"},{"code":"421126","name":"蕲春县"},{"code":"421127","name":"黄梅县"},{"code":"421181","name":"麻城市"},{"code":"421182","name":"武穴市"}]},{"code":"4212","name":"咸宁市","childs":[{"code":"421202","name":"咸安区"},{"code":"421221","name":"嘉鱼县"},{"code":"421222","name":"通城县"},{"code":"421223","name":"崇阳县"},{"code":"421224","name":"通山县"},{"code":"421281","name":"赤壁市"}]},{"code":"4213","name":"随州市","childs":[{"code":"421303","name":"曾都区"},{"code":"421321","name":"随县"},{"code":"421381","name":"广水市"}]},{"code":"4228","name":"恩施土家族苗族自治州","childs":[{"code":"422801","name":"恩施市"},{"code":"422802","name":"利川市"},{"code":"422822","name":"建始县"},{"code":"422823","name":"巴东县"},{"code":"422825","name":"宣恩县"},{"code":"422826","name":"咸丰县"},{"code":"422827","name":"来凤县"},{"code":"422828","name":"鹤峰县"}]},{"code":"4290","name":"省直辖县级行政区划","childs":[{"code":"429004","name":"仙桃市"},{"code":"429005","name":"潜江市"},{"code":"429006","name":"天门市"},{"code":"429021","name":"神农架林区"}]}]},{"code":"43","name":"湖南省","childs":[{"code":"4301","name":"长沙市","childs":[{"code":"430102","name":"芙蓉区"},{"code":"430103","name":"天心区"},{"code":"430104","name":"岳麓区"},{"code":"430105","name":"开福区"},{"code":"430111","name":"雨花区"},{"code":"430112","name":"望城区"},{"code":"430121","name":"长沙县"},{"code":"430124","name":"宁乡县"},{"code":"430181","name":"浏阳市"}]},{"code":"4302","name":"株洲市","childs":[{"code":"430202","name":"荷塘区"},{"code":"430203","name":"芦淞区"},{"code":"430204","name":"石峰区"},{"code":"430211","name":"天元区"},{"code":"430221","name":"株洲县"},{"code":"430223","name":"攸县"},{"code":"430224","name":"茶陵县"},{"code":"430225","name":"炎陵县"},{"code":"430281","name":"醴陵市"}]},{"code":"4303","name":"湘潭市","childs":[{"code":"430302","name":"雨湖区"},{"code":"430304","name":"岳塘区"},{"code":"430321","name":"湘潭县"},{"code":"430381","name":"湘乡市"},{"code":"430382","name":"韶山市"}]},{"code":"4304","name":"衡阳市","childs":[{"code":"430405","name":"珠晖区"},{"code":"430406","name":"雁峰区"},{"code":"430407","name":"石鼓区"},{"code":"430408","name":"蒸湘区"},{"code":"430412","name":"南岳区"},{"code":"430421","name":"衡阳县"},{"code":"430422","name":"衡南县"},{"code":"430423","name":"衡山县"},{"code":"430424","name":"衡东县"},{"code":"430426","name":"祁东县"},{"code":"430481","name":"耒阳市"},{"code":"430482","name":"常宁市"}]},{"code":"4305","name":"邵阳市","childs":[{"code":"430502","name":"双清区"},{"code":"430503","name":"大祥区"},{"code":"430511","name":"北塔区"},{"code":"430521","name":"邵东县"},{"code":"430522","name":"新邵县"},{"code":"430523","name":"邵阳县"},{"code":"430524","name":"隆回县"},{"code":"430525","name":"洞口县"},{"code":"430527","name":"绥宁县"},{"code":"430528","name":"新宁县"},{"code":"430529","name":"城步苗族自治县"},{"code":"430581","name":"武冈市"}]},{"code":"4306","name":"岳阳市","childs":[{"code":"430602","name":"岳阳楼区"},{"code":"430603","name":"云溪区"},{"code":"430611","name":"君山区"},{"code":"430621","name":"岳阳县"},{"code":"430623","name":"华容县"},{"code":"430624","name":"湘阴县"},{"code":"430626","name":"平江县"},{"code":"430681","name":"汨罗市"},{"code":"430682","name":"临湘市"}]},{"code":"4307","name":"常德市","childs":[{"code":"430702","name":"武陵区"},{"code":"430703","name":"鼎城区"},{"code":"430721","name":"安乡县"},{"code":"430722","name":"汉寿县"},{"code":"430723","name":"澧县"},{"code":"430724","name":"临澧县"},{"code":"430725","name":"桃源县"},{"code":"430726","name":"石门县"},{"code":"430781","name":"津市市"}]},{"code":"4308","name":"张家界市","childs":[{"code":"430802","name":"永定区"},{"code":"430811","name":"武陵源区"},{"code":"430821","name":"慈利县"},{"code":"430822","name":"桑植县"}]},{"code":"4309","name":"益阳市","childs":[{"code":"430902","name":"资阳区"},{"code":"430903","name":"赫山区"},{"code":"430921","name":"南县"},{"code":"430922","name":"桃江县"},{"code":"430923","name":"安化县"},{"code":"430981","name":"沅江市"}]},{"code":"4310","name":"郴州市","childs":[{"code":"431002","name":"北湖区"},{"code":"431003","name":"苏仙区"},{"code":"431021","name":"桂阳县"},{"code":"431022","name":"宜章县"},{"code":"431023","name":"永兴县"},{"code":"431024","name":"嘉禾县"},{"code":"431025","name":"临武县"},{"code":"431026","name":"汝城县"},{"code":"431027","name":"桂东县"},{"code":"431028","name":"安仁县"},{"code":"431081","name":"资兴市"}]},{"code":"4311","name":"永州市","childs":[{"code":"431102","name":"零陵区"},{"code":"431103","name":"冷水滩区"},{"code":"431121","name":"祁阳县"},{"code":"431122","name":"东安县"},{"code":"431123","name":"双牌县"},{"code":"431124","name":"道县"},{"code":"431125","name":"江永县"},{"code":"431126","name":"宁远县"},{"code":"431127","name":"蓝山县"},{"code":"431128","name":"新田县"},{"code":"431129","name":"江华瑶族自治县"}]},{"code":"4312","name":"怀化市","childs":[{"code":"431202","name":"鹤城区"},{"code":"431221","name":"中方县"},{"code":"431222","name":"沅陵县"},{"code":"431223","name":"辰溪县"},{"code":"431224","name":"溆浦县"},{"code":"431225","name":"会同县"},{"code":"431226","name":"麻阳苗族自治县"},{"code":"431227","name":"新晃侗族自治县"},{"code":"431228","name":"芷江侗族自治县"},{"code":"431229","name":"靖州苗族侗族自治县"},{"code":"431230","name":"通道侗族自治县"},{"code":"431281","name":"洪江市"}]},{"code":"4313","name":"娄底市","childs":[{"code":"431302","name":"娄星区"},{"code":"431321","name":"双峰县"},{"code":"431322","name":"新化县"},{"code":"431381","name":"冷水江市"},{"code":"431382","name":"涟源市"}]},{"code":"4331","name":"湘西土家族苗族自治州","childs":[{"code":"433101","name":"吉首市"},{"code":"433122","name":"泸溪县"},{"code":"433123","name":"凤凰县"},{"code":"433124","name":"花垣县"},{"code":"433125","name":"保靖县"},{"code":"433126","name":"古丈县"},{"code":"433127","name":"永顺县"},{"code":"433130","name":"龙山县"}]}]},{"code":"44","name":"广东省","childs":[{"code":"4401","name":"广州市","childs":[{"code":"440103","name":"荔湾区"},{"code":"440104","name":"越秀区"},{"code":"440105","name":"海珠区"},{"code":"440106","name":"天河区"},{"code":"440111","name":"白云区"},{"code":"440112","name":"黄埔区"},{"code":"440113","name":"番禺区"},{"code":"440114","name":"花都区"},{"code":"440115","name":"南沙区"},{"code":"440117","name":"从化区"},{"code":"440118","name":"增城区"}]},{"code":"4402","name":"韶关市","childs":[{"code":"440203","name":"武江区"},{"code":"440204","name":"浈江区"},{"code":"440205","name":"曲江区"},{"code":"440222","name":"始兴县"},{"code":"440224","name":"仁化县"},{"code":"440229","name":"翁源县"},{"code":"440232","name":"乳源瑶族自治县"},{"code":"440233","name":"新丰县"},{"code":"440281","name":"乐昌市"},{"code":"440282","name":"南雄市"}]},{"code":"4403","name":"深圳市","childs":[{"code":"440303","name":"罗湖区"},{"code":"440304","name":"福田区"},{"code":"440305","name":"南山区"},{"code":"440306","name":"宝安区"},{"code":"440307","name":"龙岗区"},{"code":"440308","name":"盐田区"}]},{"code":"4404","name":"珠海市","childs":[{"code":"440402","name":"香洲区"},{"code":"440403","name":"斗门区"},{"code":"440404","name":"金湾区"}]},{"code":"4405","name":"汕头市","childs":[{"code":"440507","name":"龙湖区"},{"code":"440511","name":"金平区"},{"code":"440512","name":"濠江区"},{"code":"440513","name":"潮阳区"},{"code":"440514","name":"潮南区"},{"code":"440515","name":"澄海区"},{"code":"440523","name":"南澳县"}]},{"code":"4406","name":"佛山市","childs":[{"code":"440604","name":"禅城区"},{"code":"440605","name":"南海区"},{"code":"440606","name":"顺德区"},{"code":"440607","name":"三水区"},{"code":"440608","name":"高明区"}]},{"code":"4407","name":"江门市","childs":[{"code":"440703","name":"蓬江区"},{"code":"440704","name":"江海区"},{"code":"440705","name":"新会区"},{"code":"440781","name":"台山市"},{"code":"440783","name":"开平市"},{"code":"440784","name":"鹤山市"},{"code":"440785","name":"恩平市"}]},{"code":"4408","name":"湛江市","childs":[{"code":"440802","name":"赤坎区"},{"code":"440803","name":"霞山区"},{"code":"440804","name":"坡头区"},{"code":"440811","name":"麻章区"},{"code":"440823","name":"遂溪县"},{"code":"440825","name":"徐闻县"},{"code":"440881","name":"廉江市"},{"code":"440882","name":"雷州市"},{"code":"440883","name":"吴川市"}]},{"code":"4409","name":"茂名市","childs":[{"code":"440902","name":"茂南区"},{"code":"440904","name":"电白区"},{"code":"440981","name":"高州市"},{"code":"440982","name":"化州市"},{"code":"440983","name":"信宜市"}]},{"code":"4412","name":"肇庆市","childs":[{"code":"441202","name":"端州区"},{"code":"441203","name":"鼎湖区"},{"code":"441204","name":"高要区"},{"code":"441223","name":"广宁县"},{"code":"441224","name":"怀集县"},{"code":"441225","name":"封开县"},{"code":"441226","name":"德庆县"},{"code":"441284","name":"四会市"}]},{"code":"4413","name":"惠州市","childs":[{"code":"441302","name":"惠城区"},{"code":"441303","name":"惠阳区"},{"code":"441322","name":"博罗县"},{"code":"441323","name":"惠东县"},{"code":"441324","name":"龙门县"}]},{"code":"4414","name":"梅州市","childs":[{"code":"441402","name":"梅江区"},{"code":"441403","name":"梅县区"},{"code":"441422","name":"大埔县"},{"code":"441423","name":"丰顺县"},{"code":"441424","name":"五华县"},{"code":"441426","name":"平远县"},{"code":"441427","name":"蕉岭县"},{"code":"441481","name":"兴宁市"}]},{"code":"4415","name":"汕尾市","childs":[{"code":"441502","name":"城区"},{"code":"441521","name":"海丰县"},{"code":"441523","name":"陆河县"},{"code":"441581","name":"陆丰市"}]},{"code":"4416","name":"河源市","childs":[{"code":"441602","name":"源城区"},{"code":"441621","name":"紫金县"},{"code":"441622","name":"龙川县"},{"code":"441623","name":"连平县"},{"code":"441624","name":"和平县"},{"code":"441625","name":"东源县"}]},{"code":"4417","name":"阳江市","childs":[{"code":"441702","name":"江城区"},{"code":"441704","name":"阳东区"},{"code":"441721","name":"阳西县"},{"code":"441781","name":"阳春市"}]},{"code":"4418","name":"清远市","childs":[{"code":"441802","name":"清城区"},{"code":"441803","name":"清新区"},{"code":"441821","name":"佛冈县"},{"code":"441823","name":"阳山县"},{"code":"441825","name":"连山壮族瑶族自治县"},{"code":"441826","name":"连南瑶族自治县"},{"code":"441881","name":"英德市"},{"code":"441882","name":"连州市"}]},{"code":"441900","name":"东莞市","childs":[{"code":"441900003","name":"东城街道办事处"},{"code":"441900004","name":"南城街道办事处"},{"code":"441900005","name":"万江街道办事处"},{"code":"441900006","name":"莞城街道办事处"},{"code":"441900101","name":"石碣镇"},{"code":"441900102","name":"石龙镇"},{"code":"441900103","name":"茶山镇"},{"code":"441900104","name":"石排镇"},{"code":"441900105","name":"企石镇"},{"code":"441900106","name":"横沥镇"},{"code":"441900107","name":"桥头镇"},{"code":"441900108","name":"谢岗镇"},{"code":"441900109","name":"东坑镇"},{"code":"441900110","name":"常平镇"},{"code":"441900111","name":"寮步镇"},{"code":"441900112","name":"樟木头镇"},{"code":"441900113","name":"大朗镇"},{"code":"441900114","name":"黄江镇"},{"code":"441900115","name":"清溪镇"},{"code":"441900116","name":"塘厦镇"},{"code":"441900117","name":"凤岗镇"},{"code":"441900118","name":"大岭山镇"},{"code":"441900119","name":"长安镇"},{"code":"441900121","name":"虎门镇"},{"code":"441900122","name":"厚街镇"},{"code":"441900123","name":"沙田镇"},{"code":"441900124","name":"道滘镇"},{"code":"441900125","name":"洪梅镇"},{"code":"441900126","name":"麻涌镇"},{"code":"441900127","name":"望牛墩镇"},{"code":"441900128","name":"中堂镇"},{"code":"441900129","name":"高埗镇"},{"code":"441900401","name":"松山湖管委会"},{"code":"441900402","name":"虎门港管委会"},{"code":"441900403","name":"东莞生态园"}]},{"code":"442000","name":"中山市","childs":[{"code":"442000001","name":"石岐区街道办事处"},{"code":"442000002","name":"东区街道办事处"},{"code":"442000003","name":"火炬开发区街道办事处"},{"code":"442000004","name":"西区街道办事处"},{"code":"442000005","name":"南区街道办事处"},{"code":"442000006","name":"五桂山街道办事处"},{"code":"442000100","name":"小榄镇"},{"code":"442000101","name":"黄圃镇"},{"code":"442000102","name":"民众镇"},{"code":"442000103","name":"东凤镇"},{"code":"442000104","name":"东升镇"},{"code":"442000105","name":"古镇镇"},{"code":"442000106","name":"沙溪镇"},{"code":"442000107","name":"坦洲镇"},{"code":"442000108","name":"港口镇"},{"code":"442000109","name":"三角镇"},{"code":"442000110","name":"横栏镇"},{"code":"442000111","name":"南头镇"},{"code":"442000112","name":"阜沙镇"},{"code":"442000113","name":"南朗镇"},{"code":"442000114","name":"三乡镇"},{"code":"442000115","name":"板芙镇"},{"code":"442000116","name":"大涌镇"},{"code":"442000117","name":"神湾镇"}]},{"code":"4451","name":"潮州市","childs":[{"code":"445102","name":"湘桥区"},{"code":"445103","name":"潮安区"},{"code":"445122","name":"饶平县"}]},{"code":"4452","name":"揭阳市","childs":[{"code":"445202","name":"榕城区"},{"code":"445203","name":"揭东区"},{"code":"445222","name":"揭西县"},{"code":"445224","name":"惠来县"},{"code":"445281","name":"普宁市"}]},{"code":"4453","name":"云浮市","childs":[{"code":"445302","name":"云城区"},{"code":"445303","name":"云安区"},{"code":"445321","name":"新兴县"},{"code":"445322","name":"郁南县"},{"code":"445381","name":"罗定市"}]}]},{"code":"45","name":"广西壮族自治区","childs":[{"code":"4501","name":"南宁市","childs":[{"code":"450102","name":"兴宁区"},{"code":"450103","name":"青秀区"},{"code":"450105","name":"江南区"},{"code":"450107","name":"西乡塘区"},{"code":"450108","name":"良庆区"},{"code":"450109","name":"邕宁区"},{"code":"450110","name":"武鸣区"},{"code":"450123","name":"隆安县"},{"code":"450124","name":"马山县"},{"code":"450125","name":"上林县"},{"code":"450126","name":"宾阳县"},{"code":"450127","name":"横县"}]},{"code":"4502","name":"柳州市","childs":[{"code":"450202","name":"城中区"},{"code":"450203","name":"鱼峰区"},{"code":"450204","name":"柳南区"},{"code":"450205","name":"柳北区"},{"code":"450206","name":"柳江区"},{"code":"450222","name":"柳城县"},{"code":"450223","name":"鹿寨县"},{"code":"450224","name":"融安县"},{"code":"450225","name":"融水苗族自治县"},{"code":"450226","name":"三江侗族自治县"}]},{"code":"4503","name":"桂林市","childs":[{"code":"450302","name":"秀峰区"},{"code":"450303","name":"叠彩区"},{"code":"450304","name":"象山区"},{"code":"450305","name":"七星区"},{"code":"450311","name":"雁山区"},{"code":"450312","name":"临桂区"},{"code":"450321","name":"阳朔县"},{"code":"450323","name":"灵川县"},{"code":"450324","name":"全州县"},{"code":"450325","name":"兴安县"},{"code":"450326","name":"永福县"},{"code":"450327","name":"灌阳县"},{"code":"450328","name":"龙胜各族自治县"},{"code":"450329","name":"资源县"},{"code":"450330","name":"平乐县"},{"code":"450331","name":"荔浦县"},{"code":"450332","name":"恭城瑶族自治县"}]},{"code":"4504","name":"梧州市","childs":[{"code":"450403","name":"万秀区"},{"code":"450405","name":"长洲区"},{"code":"450406","name":"龙圩区"},{"code":"450421","name":"苍梧县"},{"code":"450422","name":"藤县"},{"code":"450423","name":"蒙山县"},{"code":"450481","name":"岑溪市"}]},{"code":"4505","name":"北海市","childs":[{"code":"450502","name":"海城区"},{"code":"450503","name":"银海区"},{"code":"450512","name":"铁山港区"},{"code":"450521","name":"合浦县"}]},{"code":"4506","name":"防城港市","childs":[{"code":"450602","name":"港口区"},{"code":"450603","name":"防城区"},{"code":"450621","name":"上思县"},{"code":"450681","name":"东兴市"}]},{"code":"4507","name":"钦州市","childs":[{"code":"450702","name":"钦南区"},{"code":"450703","name":"钦北区"},{"code":"450721","name":"灵山县"},{"code":"450722","name":"浦北县"}]},{"code":"4508","name":"贵港市","childs":[{"code":"450802","name":"港北区"},{"code":"450803","name":"港南区"},{"code":"450804","name":"覃塘区"},{"code":"450821","name":"平南县"},{"code":"450881","name":"桂平市"}]},{"code":"4509","name":"玉林市","childs":[{"code":"450902","name":"玉州区"},{"code":"450903","name":"福绵区"},{"code":"450921","name":"容县"},{"code":"450922","name":"陆川县"},{"code":"450923","name":"博白县"},{"code":"450924","name":"兴业县"},{"code":"450981","name":"北流市"}]},{"code":"4510","name":"百色市","childs":[{"code":"451002","name":"右江区"},{"code":"451021","name":"田阳县"},{"code":"451022","name":"田东县"},{"code":"451023","name":"平果县"},{"code":"451024","name":"德保县"},{"code":"451026","name":"那坡县"},{"code":"451027","name":"凌云县"},{"code":"451028","name":"乐业县"},{"code":"451029","name":"田林县"},{"code":"451030","name":"西林县"},{"code":"451031","name":"隆林各族自治县"},{"code":"451081","name":"靖西市"}]},{"code":"4511","name":"贺州市","childs":[{"code":"451102","name":"八步区"},{"code":"451103","name":"平桂区"},{"code":"451121","name":"昭平县"},{"code":"451122","name":"钟山县"},{"code":"451123","name":"富川瑶族自治县"}]},{"code":"4512","name":"河池市","childs":[{"code":"451202","name":"金城江区"},{"code":"451221","name":"南丹县"},{"code":"451222","name":"天峨县"},{"code":"451223","name":"凤山县"},{"code":"451224","name":"东兰县"},{"code":"451225","name":"罗城仫佬族自治县"},{"code":"451226","name":"环江毛南族自治县"},{"code":"451227","name":"巴马瑶族自治县"},{"code":"451228","name":"都安瑶族自治县"},{"code":"451229","name":"大化瑶族自治县"},{"code":"451281","name":"宜州市"}]},{"code":"4513","name":"来宾市","childs":[{"code":"451302","name":"兴宾区"},{"code":"451321","name":"忻城县"},{"code":"451322","name":"象州县"},{"code":"451323","name":"武宣县"},{"code":"451324","name":"金秀瑶族自治县"},{"code":"451381","name":"合山市"}]},{"code":"4514","name":"崇左市","childs":[{"code":"451402","name":"江州区"},{"code":"451421","name":"扶绥县"},{"code":"451422","name":"宁明县"},{"code":"451423","name":"龙州县"},{"code":"451424","name":"大新县"},{"code":"451425","name":"天等县"},{"code":"451481","name":"凭祥市"}]}]},{"code":"46","name":"海南省","childs":[{"code":"4601","name":"海口市","childs":[{"code":"460105","name":"秀英区"},{"code":"460106","name":"龙华区"},{"code":"460107","name":"琼山区"},{"code":"460108","name":"美兰区"}]},{"code":"4602","name":"三亚市","childs":[{"code":"460202","name":"海棠区"},{"code":"460203","name":"吉阳区"},{"code":"460204","name":"天涯区"},{"code":"460205","name":"崖州区"}]},{"code":"4603","name":"三沙市","childs":[{"code":"460321","name":"西沙群岛"},{"code":"460322","name":"南沙群岛"},{"code":"460323","name":"中沙群岛的岛礁及其海域"}]},{"code":"460400","name":"儋州市","childs":[{"code":"460400100","name":"那大镇"},{"code":"460400101","name":"和庆镇"},{"code":"460400102","name":"南丰镇"},{"code":"460400103","name":"大成镇"},{"code":"460400104","name":"雅星镇"},{"code":"460400105","name":"兰洋镇"},{"code":"460400106","name":"光村镇"},{"code":"460400107","name":"木棠镇"},{"code":"460400108","name":"海头镇"},{"code":"460400109","name":"峨蔓镇"},{"code":"460400110","name":"三都镇"},{"code":"460400111","name":"王五镇"},{"code":"460400112","name":"白马井镇"},{"code":"460400113","name":"中和镇"},{"code":"460400114","name":"排浦镇"},{"code":"460400115","name":"东成镇"},{"code":"460400116","name":"新州镇"},{"code":"460400400","name":"国营西培农场"},{"code":"460400404","name":"国营西联农场"},{"code":"460400405","name":"国营蓝洋农场"},{"code":"460400407","name":"国营八一农场"},{"code":"460400499","name":"洋浦经济开发区"},{"code":"460400500","name":"华南热作学院"}]},{"code":"4690","name":"省直辖县级行政区划","childs":[{"code":"469001","name":"五指山市"},{"code":"469002","name":"琼海市"},{"code":"469005","name":"文昌市"},{"code":"469006","name":"万宁市"},{"code":"469007","name":"东方市"},{"code":"469021","name":"定安县"},{"code":"469022","name":"屯昌县"},{"code":"469023","name":"澄迈县"},{"code":"469024","name":"临高县"},{"code":"469025","name":"白沙黎族自治县"},{"code":"469026","name":"昌江黎族自治县"},{"code":"469027","name":"乐东黎族自治县"},{"code":"469028","name":"陵水黎族自治县"},{"code":"469029","name":"保亭黎族苗族自治县"},{"code":"469030","name":"琼中黎族苗族自治县"}]}]},{"code":"50","name":"重庆市","childs":[{"code":"5001","name":"市辖区","childs":[{"code":"500101","name":"万州区"},{"code":"500102","name":"涪陵区"},{"code":"500103","name":"渝中区"},{"code":"500104","name":"大渡口区"},{"code":"500105","name":"江北区"},{"code":"500106","name":"沙坪坝区"},{"code":"500107","name":"九龙坡区"},{"code":"500108","name":"南岸区"},{"code":"500109","name":"北碚区"},{"code":"500110","name":"綦江区"},{"code":"500111","name":"大足区"},{"code":"500112","name":"渝北区"},{"code":"500113","name":"巴南区"},{"code":"500114","name":"黔江区"},{"code":"500115","name":"长寿区"},{"code":"500116","name":"江津区"},{"code":"500117","name":"合川区"},{"code":"500118","name":"永川区"},{"code":"500119","name":"南川区"},{"code":"500120","name":"璧山区"},{"code":"500151","name":"铜梁区"},{"code":"500152","name":"潼南区"},{"code":"500153","name":"荣昌区"},{"code":"500154","name":"开州区"}]},{"code":"5002","name":"县","childs":[{"code":"500228","name":"梁平县"},{"code":"500229","name":"城口县"},{"code":"500230","name":"丰都县"},{"code":"500231","name":"垫江县"},{"code":"500232","name":"武隆县"},{"code":"500233","name":"忠县"},{"code":"500235","name":"云阳县"},{"code":"500236","name":"奉节县"},{"code":"500237","name":"巫山县"},{"code":"500238","name":"巫溪县"},{"code":"500240","name":"石柱土家族自治县"},{"code":"500241","name":"秀山土家族苗族自治县"},{"code":"500242","name":"酉阳土家族苗族自治县"},{"code":"500243","name":"彭水苗族土家族自治县"}]}]},{"code":"51","name":"四川省","childs":[{"code":"5101","name":"成都市","childs":[{"code":"510104","name":"锦江区"},{"code":"510105","name":"青羊区"},{"code":"510106","name":"金牛区"},{"code":"510107","name":"武侯区"},{"code":"510108","name":"成华区"},{"code":"510112","name":"龙泉驿区"},{"code":"510113","name":"青白江区"},{"code":"510114","name":"新都区"},{"code":"510115","name":"温江区"},{"code":"510116","name":"双流区"},{"code":"510121","name":"金堂县"},{"code":"510124","name":"郫县"},{"code":"510129","name":"大邑县"},{"code":"510131","name":"蒲江县"},{"code":"510132","name":"新津县"},{"code":"510181","name":"都江堰市"},{"code":"510182","name":"彭州市"},{"code":"510183","name":"邛崃市"},{"code":"510184","name":"崇州市"},{"code":"510185","name":"简阳市"}]},{"code":"5103","name":"自贡市","childs":[{"code":"510302","name":"自流井区"},{"code":"510303","name":"贡井区"},{"code":"510304","name":"大安区"},{"code":"510311","name":"沿滩区"},{"code":"510321","name":"荣县"},{"code":"510322","name":"富顺县"}]},{"code":"5104","name":"攀枝花市","childs":[{"code":"510402","name":"东区"},{"code":"510403","name":"西区"},{"code":"510411","name":"仁和区"},{"code":"510421","name":"米易县"},{"code":"510422","name":"盐边县"}]},{"code":"5105","name":"泸州市","childs":[{"code":"510502","name":"江阳区"},{"code":"510503","name":"纳溪区"},{"code":"510504","name":"龙马潭区"},{"code":"510521","name":"泸县"},{"code":"510522","name":"合江县"},{"code":"510524","name":"叙永县"},{"code":"510525","name":"古蔺县"}]},{"code":"5106","name":"德阳市","childs":[{"code":"510603","name":"旌阳区"},{"code":"510623","name":"中江县"},{"code":"510626","name":"罗江县"},{"code":"510681","name":"广汉市"},{"code":"510682","name":"什邡市"},{"code":"510683","name":"绵竹市"}]},{"code":"5107","name":"绵阳市","childs":[{"code":"510703","name":"涪城区"},{"code":"510704","name":"游仙区"},{"code":"510705","name":"安州区"},{"code":"510722","name":"三台县"},{"code":"510723","name":"盐亭县"},{"code":"510725","name":"梓潼县"},{"code":"510726","name":"北川羌族自治县"},{"code":"510727","name":"平武县"},{"code":"510781","name":"江油市"}]},{"code":"5108","name":"广元市","childs":[{"code":"510802","name":"利州区"},{"code":"510811","name":"昭化区"},{"code":"510812","name":"朝天区"},{"code":"510821","name":"旺苍县"},{"code":"510822","name":"青川县"},{"code":"510823","name":"剑阁县"},{"code":"510824","name":"苍溪县"}]},{"code":"5109","name":"遂宁市","childs":[{"code":"510903","name":"船山区"},{"code":"510904","name":"安居区"},{"code":"510921","name":"蓬溪县"},{"code":"510922","name":"射洪县"},{"code":"510923","name":"大英县"}]},{"code":"5110","name":"内江市","childs":[{"code":"511002","name":"市中区"},{"code":"511011","name":"东兴区"},{"code":"511024","name":"威远县"},{"code":"511025","name":"资中县"},{"code":"511028","name":"隆昌县"}]},{"code":"5111","name":"乐山市","childs":[{"code":"511102","name":"市中区"},{"code":"511111","name":"沙湾区"},{"code":"511112","name":"五通桥区"},{"code":"511113","name":"金口河区"},{"code":"511123","name":"犍为县"},{"code":"511124","name":"井研县"},{"code":"511126","name":"夹江县"},{"code":"511129","name":"沐川县"},{"code":"511132","name":"峨边彝族自治县"},{"code":"511133","name":"马边彝族自治县"},{"code":"511181","name":"峨眉山市"}]},{"code":"5113","name":"南充市","childs":[{"code":"511302","name":"顺庆区"},{"code":"511303","name":"高坪区"},{"code":"511304","name":"嘉陵区"},{"code":"511321","name":"南部县"},{"code":"511322","name":"营山县"},{"code":"511323","name":"蓬安县"},{"code":"511324","name":"仪陇县"},{"code":"511325","name":"西充县"},{"code":"511381","name":"阆中市"}]},{"code":"5114","name":"眉山市","childs":[{"code":"511402","name":"东坡区"},{"code":"511403","name":"彭山区"},{"code":"511421","name":"仁寿县"},{"code":"511423","name":"洪雅县"},{"code":"511424","name":"丹棱县"},{"code":"511425","name":"青神县"}]},{"code":"5115","name":"宜宾市","childs":[{"code":"511502","name":"翠屏区"},{"code":"511503","name":"南溪区"},{"code":"511521","name":"宜宾县"},{"code":"511523","name":"江安县"},{"code":"511524","name":"长宁县"},{"code":"511525","name":"高县"},{"code":"511526","name":"珙县"},{"code":"511527","name":"筠连县"},{"code":"511528","name":"兴文县"},{"code":"511529","name":"屏山县"}]},{"code":"5116","name":"广安市","childs":[{"code":"511602","name":"广安区"},{"code":"511603","name":"前锋区"},{"code":"511621","name":"岳池县"},{"code":"511622","name":"武胜县"},{"code":"511623","name":"邻水县"},{"code":"511681","name":"华蓥市"}]},{"code":"5117","name":"达州市","childs":[{"code":"511702","name":"通川区"},{"code":"511703","name":"达川区"},{"code":"511722","name":"宣汉县"},{"code":"511723","name":"开江县"},{"code":"511724","name":"大竹县"},{"code":"511725","name":"渠县"},{"code":"511781","name":"万源市"}]},{"code":"5118","name":"雅安市","childs":[{"code":"511802","name":"雨城区"},{"code":"511803","name":"名山区"},{"code":"511822","name":"荥经县"},{"code":"511823","name":"汉源县"},{"code":"511824","name":"石棉县"},{"code":"511825","name":"天全县"},{"code":"511826","name":"芦山县"},{"code":"511827","name":"宝兴县"}]},{"code":"5119","name":"巴中市","childs":[{"code":"511902","name":"巴州区"},{"code":"511903","name":"恩阳区"},{"code":"511921","name":"通江县"},{"code":"511922","name":"南江县"},{"code":"511923","name":"平昌县"}]},{"code":"5120","name":"资阳市","childs":[{"code":"512002","name":"雁江区"},{"code":"512021","name":"安岳县"},{"code":"512022","name":"乐至县"}]},{"code":"5132","name":"阿坝藏族羌族自治州","childs":[{"code":"513201","name":"马尔康市"},{"code":"513221","name":"汶川县"},{"code":"513222","name":"理县"},{"code":"513223","name":"茂县"},{"code":"513224","name":"松潘县"},{"code":"513225","name":"九寨沟县"},{"code":"513226","name":"金川县"},{"code":"513227","name":"小金县"},{"code":"513228","name":"黑水县"},{"code":"513230","name":"壤塘县"},{"code":"513231","name":"阿坝县"},{"code":"513232","name":"若尔盖县"},{"code":"513233","name":"红原县"}]},{"code":"5133","name":"甘孜藏族自治州","childs":[{"code":"513301","name":"康定市"},{"code":"513322","name":"泸定县"},{"code":"513323","name":"丹巴县"},{"code":"513324","name":"九龙县"},{"code":"513325","name":"雅江县"},{"code":"513326","name":"道孚县"},{"code":"513327","name":"炉霍县"},{"code":"513328","name":"甘孜县"},{"code":"513329","name":"新龙县"},{"code":"513330","name":"德格县"},{"code":"513331","name":"白玉县"},{"code":"513332","name":"石渠县"},{"code":"513333","name":"色达县"},{"code":"513334","name":"理塘县"},{"code":"513335","name":"巴塘县"},{"code":"513336","name":"乡城县"},{"code":"513337","name":"稻城县"},{"code":"513338","name":"得荣县"}]},{"code":"5134","name":"凉山彝族自治州","childs":[{"code":"513401","name":"西昌市"},{"code":"513422","name":"木里藏族自治县"},{"code":"513423","name":"盐源县"},{"code":"513424","name":"德昌县"},{"code":"513425","name":"会理县"},{"code":"513426","name":"会东县"},{"code":"513427","name":"宁南县"},{"code":"513428","name":"普格县"},{"code":"513429","name":"布拖县"},{"code":"513430","name":"金阳县"},{"code":"513431","name":"昭觉县"},{"code":"513432","name":"喜德县"},{"code":"513433","name":"冕宁县"},{"code":"513434","name":"越西县"},{"code":"513435","name":"甘洛县"},{"code":"513436","name":"美姑县"},{"code":"513437","name":"雷波县"}]}]},{"code":"52","name":"贵州省","childs":[{"code":"5201","name":"贵阳市","childs":[{"code":"520102","name":"南明区"},{"code":"520103","name":"云岩区"},{"code":"520111","name":"花溪区"},{"code":"520112","name":"乌当区"},{"code":"520113","name":"白云区"},{"code":"520115","name":"观山湖区"},{"code":"520121","name":"开阳县"},{"code":"520122","name":"息烽县"},{"code":"520123","name":"修文县"},{"code":"520181","name":"清镇市"}]},{"code":"5202","name":"六盘水市","childs":[{"code":"520201","name":"钟山区"},{"code":"520203","name":"六枝特区"},{"code":"520221","name":"水城县"},{"code":"520222","name":"盘县"}]},{"code":"5203","name":"遵义市","childs":[{"code":"520302","name":"红花岗区"},{"code":"520303","name":"汇川区"},{"code":"520304","name":"播州区"},{"code":"520322","name":"桐梓县"},{"code":"520323","name":"绥阳县"},{"code":"520324","name":"正安县"},{"code":"520325","name":"道真仡佬族苗族自治县"},{"code":"520326","name":"务川仡佬族苗族自治县"},{"code":"520327","name":"凤冈县"},{"code":"520328","name":"湄潭县"},{"code":"520329","name":"余庆县"},{"code":"520330","name":"习水县"},{"code":"520381","name":"赤水市"},{"code":"520382","name":"仁怀市"}]},{"code":"5204","name":"安顺市","childs":[{"code":"520402","name":"西秀区"},{"code":"520403","name":"平坝区"},{"code":"520422","name":"普定县"},{"code":"520423","name":"镇宁布依族苗族自治县"},{"code":"520424","name":"关岭布依族苗族自治县"},{"code":"520425","name":"紫云苗族布依族自治县"}]},{"code":"5205","name":"毕节市","childs":[{"code":"520502","name":"七星关区"},{"code":"520521","name":"大方县"},{"code":"520522","name":"黔西县"},{"code":"520523","name":"金沙县"},{"code":"520524","name":"织金县"},{"code":"520525","name":"纳雍县"},{"code":"520526","name":"威宁彝族回族苗族自治县"},{"code":"520527","name":"赫章县"}]},{"code":"5206","name":"铜仁市","childs":[{"code":"520602","name":"碧江区"},{"code":"520603","name":"万山区"},{"code":"520621","name":"江口县"},{"code":"520622","name":"玉屏侗族自治县"},{"code":"520623","name":"石阡县"},{"code":"520624","name":"思南县"},{"code":"520625","name":"印江土家族苗族自治县"},{"code":"520626","name":"德江县"},{"code":"520627","name":"沿河土家族自治县"},{"code":"520628","name":"松桃苗族自治县"}]},{"code":"5223","name":"黔西南布依族苗族自治州","childs":[{"code":"522301","name":"兴义市"},{"code":"522322","name":"兴仁县"},{"code":"522323","name":"普安县"},{"code":"522324","name":"晴隆县"},{"code":"522325","name":"贞丰县"},{"code":"522326","name":"望谟县"},{"code":"522327","name":"册亨县"},{"code":"522328","name":"安龙县"}]},{"code":"5226","name":"黔东南苗族侗族自治州","childs":[{"code":"522601","name":"凯里市"},{"code":"522622","name":"黄平县"},{"code":"522623","name":"施秉县"},{"code":"522624","name":"三穗县"},{"code":"522625","name":"镇远县"},{"code":"522626","name":"岑巩县"},{"code":"522627","name":"天柱县"},{"code":"522628","name":"锦屏县"},{"code":"522629","name":"剑河县"},{"code":"522630","name":"台江县"},{"code":"522631","name":"黎平县"},{"code":"522632","name":"榕江县"},{"code":"522633","name":"从江县"},{"code":"522634","name":"雷山县"},{"code":"522635","name":"麻江县"},{"code":"522636","name":"丹寨县"}]},{"code":"5227","name":"黔南布依族苗族自治州","childs":[{"code":"522701","name":"都匀市"},{"code":"522702","name":"福泉市"},{"code":"522722","name":"荔波县"},{"code":"522723","name":"贵定县"},{"code":"522725","name":"瓮安县"},{"code":"522726","name":"独山县"},{"code":"522727","name":"平塘县"},{"code":"522728","name":"罗甸县"},{"code":"522729","name":"长顺县"},{"code":"522730","name":"龙里县"},{"code":"522731","name":"惠水县"},{"code":"522732","name":"三都水族自治县"}]}]},{"code":"53","name":"云南省","childs":[{"code":"5301","name":"昆明市","childs":[{"code":"530102","name":"五华区"},{"code":"530103","name":"盘龙区"},{"code":"530111","name":"官渡区"},{"code":"530112","name":"西山区"},{"code":"530113","name":"东川区"},{"code":"530114","name":"呈贡区"},{"code":"530122","name":"晋宁县"},{"code":"530124","name":"富民县"},{"code":"530125","name":"宜良县"},{"code":"530126","name":"石林彝族自治县"},{"code":"530127","name":"嵩明县"},{"code":"530128","name":"禄劝彝族苗族自治县"},{"code":"530129","name":"寻甸回族彝族自治县"},{"code":"530181","name":"安宁市"}]},{"code":"5303","name":"曲靖市","childs":[{"code":"530302","name":"麒麟区"},{"code":"530303","name":"沾益区"},{"code":"530321","name":"马龙县"},{"code":"530322","name":"陆良县"},{"code":"530323","name":"师宗县"},{"code":"530324","name":"罗平县"},{"code":"530325","name":"富源县"},{"code":"530326","name":"会泽县"},{"code":"530381","name":"宣威市"}]},{"code":"5304","name":"玉溪市","childs":[{"code":"530402","name":"红塔区"},{"code":"530403","name":"江川区"},{"code":"530422","name":"澄江县"},{"code":"530423","name":"通海县"},{"code":"530424","name":"华宁县"},{"code":"530425","name":"易门县"},{"code":"530426","name":"峨山彝族自治县"},{"code":"530427","name":"新平彝族傣族自治县"},{"code":"530428","name":"元江哈尼族彝族傣族自治县"}]},{"code":"5305","name":"保山市","childs":[{"code":"530502","name":"隆阳区"},{"code":"530521","name":"施甸县"},{"code":"530523","name":"龙陵县"},{"code":"530524","name":"昌宁县"},{"code":"530581","name":"腾冲市"}]},{"code":"5306","name":"昭通市","childs":[{"code":"530602","name":"昭阳区"},{"code":"530621","name":"鲁甸县"},{"code":"530622","name":"巧家县"},{"code":"530623","name":"盐津县"},{"code":"530624","name":"大关县"},{"code":"530625","name":"永善县"},{"code":"530626","name":"绥江县"},{"code":"530627","name":"镇雄县"},{"code":"530628","name":"彝良县"},{"code":"530629","name":"威信县"},{"code":"530630","name":"水富县"}]},{"code":"5307","name":"丽江市","childs":[{"code":"530702","name":"古城区"},{"code":"530721","name":"玉龙纳西族自治县"},{"code":"530722","name":"永胜县"},{"code":"530723","name":"华坪县"},{"code":"530724","name":"宁蒗彝族自治县"}]},{"code":"5308","name":"普洱市","childs":[{"code":"530802","name":"思茅区"},{"code":"530821","name":"宁洱哈尼族彝族自治县"},{"code":"530822","name":"墨江哈尼族自治县"},{"code":"530823","name":"景东彝族自治县"},{"code":"530824","name":"景谷傣族彝族自治县"},{"code":"530825","name":"镇沅彝族哈尼族拉祜族自治县"},{"code":"530826","name":"江城哈尼族彝族自治县"},{"code":"530827","name":"孟连傣族拉祜族佤族自治县"},{"code":"530828","name":"澜沧拉祜族自治县"},{"code":"530829","name":"西盟佤族自治县"}]},{"code":"5309","name":"临沧市","childs":[{"code":"530902","name":"临翔区"},{"code":"530921","name":"凤庆县"},{"code":"530922","name":"云县"},{"code":"530923","name":"永德县"},{"code":"530924","name":"镇康县"},{"code":"530925","name":"双江拉祜族佤族布朗族傣族自治县"},{"code":"530926","name":"耿马傣族佤族自治县"},{"code":"530927","name":"沧源佤族自治县"}]},{"code":"5323","name":"楚雄彝族自治州","childs":[{"code":"532301","name":"楚雄市"},{"code":"532322","name":"双柏县"},{"code":"532323","name":"牟定县"},{"code":"532324","name":"南华县"},{"code":"532325","name":"姚安县"},{"code":"532326","name":"大姚县"},{"code":"532327","name":"永仁县"},{"code":"532328","name":"元谋县"},{"code":"532329","name":"武定县"},{"code":"532331","name":"禄丰县"}]},{"code":"5325","name":"红河哈尼族彝族自治州","childs":[{"code":"532501","name":"个旧市"},{"code":"532502","name":"开远市"},{"code":"532503","name":"蒙自市"},{"code":"532504","name":"弥勒市"},{"code":"532523","name":"屏边苗族自治县"},{"code":"532524","name":"建水县"},{"code":"532525","name":"石屏县"},{"code":"532527","name":"泸西县"},{"code":"532528","name":"元阳县"},{"code":"532529","name":"红河县"},{"code":"532530","name":"金平苗族瑶族傣族自治县"},{"code":"532531","name":"绿春县"},{"code":"532532","name":"河口瑶族自治县"}]},{"code":"5326","name":"文山壮族苗族自治州","childs":[{"code":"532601","name":"文山市"},{"code":"532622","name":"砚山县"},{"code":"532623","name":"西畴县"},{"code":"532624","name":"麻栗坡县"},{"code":"532625","name":"马关县"},{"code":"532626","name":"丘北县"},{"code":"532627","name":"广南县"},{"code":"532628","name":"富宁县"}]},{"code":"5328","name":"西双版纳傣族自治州","childs":[{"code":"532801","name":"景洪市"},{"code":"532822","name":"勐海县"},{"code":"532823","name":"勐腊县"}]},{"code":"5329","name":"大理白族自治州","childs":[{"code":"532901","name":"大理市"},{"code":"532922","name":"漾濞彝族自治县"},{"code":"532923","name":"祥云县"},{"code":"532924","name":"宾川县"},{"code":"532925","name":"弥渡县"},{"code":"532926","name":"南涧彝族自治县"},{"code":"532927","name":"巍山彝族回族自治县"},{"code":"532928","name":"永平县"},{"code":"532929","name":"云龙县"},{"code":"532930","name":"洱源县"},{"code":"532931","name":"剑川县"},{"code":"532932","name":"鹤庆县"}]},{"code":"5331","name":"德宏傣族景颇族自治州","childs":[{"code":"533102","name":"瑞丽市"},{"code":"533103","name":"芒市"},{"code":"533122","name":"梁河县"},{"code":"533123","name":"盈江县"},{"code":"533124","name":"陇川县"}]},{"code":"5333","name":"怒江傈僳族自治州","childs":[{"code":"533301","name":"泸水市"},{"code":"533323","name":"福贡县"},{"code":"533324","name":"贡山独龙族怒族自治县"},{"code":"533325","name":"兰坪白族普米族自治县"}]},{"code":"5334","name":"迪庆藏族自治州","childs":[{"code":"533401","name":"香格里拉市"},{"code":"533422","name":"德钦县"},{"code":"533423","name":"维西傈僳族自治县"}]}]},{"code":"54","name":"西藏自治区","childs":[{"code":"5401","name":"拉萨市","childs":[{"code":"540102","name":"城关区"},{"code":"540103","name":"堆龙德庆区"},{"code":"540121","name":"林周县"},{"code":"540122","name":"当雄县"},{"code":"540123","name":"尼木县"},{"code":"540124","name":"曲水县"},{"code":"540126","name":"达孜县"},{"code":"540127","name":"墨竹工卡县"}]},{"code":"5402","name":"日喀则市","childs":[{"code":"540202","name":"桑珠孜区"},{"code":"540221","name":"南木林县"},{"code":"540222","name":"江孜县"},{"code":"540223","name":"定日县"},{"code":"540224","name":"萨迦县"},{"code":"540225","name":"拉孜县"},{"code":"540226","name":"昂仁县"},{"code":"540227","name":"谢通门县"},{"code":"540228","name":"白朗县"},{"code":"540229","name":"仁布县"},{"code":"540230","name":"康马县"},{"code":"540231","name":"定结县"},{"code":"540232","name":"仲巴县"},{"code":"540233","name":"亚东县"},{"code":"540234","name":"吉隆县"},{"code":"540235","name":"聂拉木县"},{"code":"540236","name":"萨嘎县"},{"code":"540237","name":"岗巴县"}]},{"code":"5403","name":"昌都市","childs":[{"code":"540302","name":"卡若区"},{"code":"540321","name":"江达县"},{"code":"540322","name":"贡觉县"},{"code":"540323","name":"类乌齐县"},{"code":"540324","name":"丁青县"},{"code":"540325","name":"察雅县"},{"code":"540326","name":"八宿县"},{"code":"540327","name":"左贡县"},{"code":"540328","name":"芒康县"},{"code":"540329","name":"洛隆县"},{"code":"540330","name":"边坝县"}]},{"code":"5404","name":"林芝市","childs":[{"code":"540402","name":"巴宜区"},{"code":"540421","name":"工布江达县"},{"code":"540422","name":"米林县"},{"code":"540423","name":"墨脱县"},{"code":"540424","name":"波密县"},{"code":"540425","name":"察隅县"},{"code":"540426","name":"朗县"}]},{"code":"5405","name":"山南市","childs":[{"code":"540502","name":"乃东区"},{"code":"540521","name":"扎囊县"},{"code":"540522","name":"贡嘎县"},{"code":"540523","name":"桑日县"},{"code":"540524","name":"琼结县"},{"code":"540525","name":"曲松县"},{"code":"540526","name":"措美县"},{"code":"540527","name":"洛扎县"},{"code":"540528","name":"加查县"},{"code":"540529","name":"隆子县"},{"code":"540530","name":"错那县"},{"code":"540531","name":"浪卡子县"}]},{"code":"5424","name":"那曲地区","childs":[{"code":"542421","name":"那曲县"},{"code":"542422","name":"嘉黎县"},{"code":"542423","name":"比如县"},{"code":"542424","name":"聂荣县"},{"code":"542425","name":"安多县"},{"code":"542426","name":"申扎县"},{"code":"542427","name":"索县"},{"code":"542428","name":"班戈县"},{"code":"542429","name":"巴青县"},{"code":"542430","name":"尼玛县"},{"code":"542431","name":"双湖县"}]},{"code":"5425","name":"阿里地区","childs":[{"code":"542521","name":"普兰县"},{"code":"542522","name":"札达县"},{"code":"542523","name":"噶尔县"},{"code":"542524","name":"日土县"},{"code":"542525","name":"革吉县"},{"code":"542526","name":"改则县"},{"code":"542527","name":"措勤县"}]}]},{"code":"61","name":"陕西省","childs":[{"code":"6101","name":"西安市","childs":[{"code":"610102","name":"新城区"},{"code":"610103","name":"碑林区"},{"code":"610104","name":"莲湖区"},{"code":"610111","name":"灞桥区"},{"code":"610112","name":"未央区"},{"code":"610113","name":"雁塔区"},{"code":"610114","name":"阎良区"},{"code":"610115","name":"临潼区"},{"code":"610116","name":"长安区"},{"code":"610117","name":"高陵区"},{"code":"610122","name":"蓝田县"},{"code":"610124","name":"周至县"},{"code":"610125","name":"户县"}]},{"code":"6102","name":"铜川市","childs":[{"code":"610202","name":"王益区"},{"code":"610203","name":"印台区"},{"code":"610204","name":"耀州区"},{"code":"610222","name":"宜君县"}]},{"code":"6103","name":"宝鸡市","childs":[{"code":"610302","name":"渭滨区"},{"code":"610303","name":"金台区"},{"code":"610304","name":"陈仓区"},{"code":"610322","name":"凤翔县"},{"code":"610323","name":"岐山县"},{"code":"610324","name":"扶风县"},{"code":"610326","name":"眉县"},{"code":"610327","name":"陇县"},{"code":"610328","name":"千阳县"},{"code":"610329","name":"麟游县"},{"code":"610330","name":"凤县"},{"code":"610331","name":"太白县"}]},{"code":"6104","name":"咸阳市","childs":[{"code":"610402","name":"秦都区"},{"code":"610403","name":"杨陵区"},{"code":"610404","name":"渭城区"},{"code":"610422","name":"三原县"},{"code":"610423","name":"泾阳县"},{"code":"610424","name":"乾县"},{"code":"610425","name":"礼泉县"},{"code":"610426","name":"永寿县"},{"code":"610427","name":"彬县"},{"code":"610428","name":"长武县"},{"code":"610429","name":"旬邑县"},{"code":"610430","name":"淳化县"},{"code":"610431","name":"武功县"},{"code":"610481","name":"兴平市"}]},{"code":"6105","name":"渭南市","childs":[{"code":"610502","name":"临渭区"},{"code":"610503","name":"华州区"},{"code":"610522","name":"潼关县"},{"code":"610523","name":"大荔县"},{"code":"610524","name":"合阳县"},{"code":"610525","name":"澄城县"},{"code":"610526","name":"蒲城县"},{"code":"610527","name":"白水县"},{"code":"610528","name":"富平县"},{"code":"610581","name":"韩城市"},{"code":"610582","name":"华阴市"}]},{"code":"6106","name":"延安市","childs":[{"code":"610602","name":"宝塔区"},{"code":"610603","name":"安塞区"},{"code":"610621","name":"延长县"},{"code":"610622","name":"延川县"},{"code":"610623","name":"子长县"},{"code":"610625","name":"志丹县"},{"code":"610626","name":"吴起县"},{"code":"610627","name":"甘泉县"},{"code":"610628","name":"富县"},{"code":"610629","name":"洛川县"},{"code":"610630","name":"宜川县"},{"code":"610631","name":"黄龙县"},{"code":"610632","name":"黄陵县"}]},{"code":"6107","name":"汉中市","childs":[{"code":"610702","name":"汉台区"},{"code":"610721","name":"南郑县"},{"code":"610722","name":"城固县"},{"code":"610723","name":"洋县"},{"code":"610724","name":"西乡县"},{"code":"610725","name":"勉县"},{"code":"610726","name":"宁强县"},{"code":"610727","name":"略阳县"},{"code":"610728","name":"镇巴县"},{"code":"610729","name":"留坝县"},{"code":"610730","name":"佛坪县"}]},{"code":"6108","name":"榆林市","childs":[{"code":"610802","name":"榆阳区"},{"code":"610803","name":"横山区"},{"code":"610821","name":"神木县"},{"code":"610822","name":"府谷县"},{"code":"610824","name":"靖边县"},{"code":"610825","name":"定边县"},{"code":"610826","name":"绥德县"},{"code":"610827","name":"米脂县"},{"code":"610828","name":"佳县"},{"code":"610829","name":"吴堡县"},{"code":"610830","name":"清涧县"},{"code":"610831","name":"子洲县"}]},{"code":"6109","name":"安康市","childs":[{"code":"610902","name":"汉滨区"},{"code":"610921","name":"汉阴县"},{"code":"610922","name":"石泉县"},{"code":"610923","name":"宁陕县"},{"code":"610924","name":"紫阳县"},{"code":"610925","name":"岚皋县"},{"code":"610926","name":"平利县"},{"code":"610927","name":"镇坪县"},{"code":"610928","name":"旬阳县"},{"code":"610929","name":"白河县"}]},{"code":"6110","name":"商洛市","childs":[{"code":"611002","name":"商州区"},{"code":"611021","name":"洛南县"},{"code":"611022","name":"丹凤县"},{"code":"611023","name":"商南县"},{"code":"611024","name":"山阳县"},{"code":"611025","name":"镇安县"},{"code":"611026","name":"柞水县"}]}]},{"code":"62","name":"甘肃省","childs":[{"code":"6201","name":"兰州市","childs":[{"code":"620102","name":"城关区"},{"code":"620103","name":"七里河区"},{"code":"620104","name":"西固区"},{"code":"620105","name":"安宁区"},{"code":"620111","name":"红古区"},{"code":"620121","name":"永登县"},{"code":"620122","name":"皋兰县"},{"code":"620123","name":"榆中县"}]},{"code":"620201","name":"嘉峪关市","childs":[{"code":"620201100","name":"新城镇"},{"code":"620201101","name":"峪泉镇"},{"code":"620201102","name":"文殊镇"},{"code":"620201401","name":"雄关区"},{"code":"620201402","name":"镜铁区"},{"code":"620201403","name":"长城区"}]},{"code":"6203","name":"金昌市","childs":[{"code":"620302","name":"金川区"},{"code":"620321","name":"永昌县"}]},{"code":"6204","name":"白银市","childs":[{"code":"620402","name":"白银区"},{"code":"620403","name":"平川区"},{"code":"620421","name":"靖远县"},{"code":"620422","name":"会宁县"},{"code":"620423","name":"景泰县"}]},{"code":"6205","name":"天水市","childs":[{"code":"620502","name":"秦州区"},{"code":"620503","name":"麦积区"},{"code":"620521","name":"清水县"},{"code":"620522","name":"秦安县"},{"code":"620523","name":"甘谷县"},{"code":"620524","name":"武山县"},{"code":"620525","name":"张家川回族自治县"}]},{"code":"6206","name":"武威市","childs":[{"code":"620602","name":"凉州区"},{"code":"620621","name":"民勤县"},{"code":"620622","name":"古浪县"},{"code":"620623","name":"天祝藏族自治县"}]},{"code":"6207","name":"张掖市","childs":[{"code":"620702","name":"甘州区"},{"code":"620721","name":"肃南裕固族自治县"},{"code":"620722","name":"民乐县"},{"code":"620723","name":"临泽县"},{"code":"620724","name":"高台县"},{"code":"620725","name":"山丹县"}]},{"code":"6208","name":"平凉市","childs":[{"code":"620802","name":"崆峒区"},{"code":"620821","name":"泾川县"},{"code":"620822","name":"灵台县"},{"code":"620823","name":"崇信县"},{"code":"620824","name":"华亭县"},{"code":"620825","name":"庄浪县"},{"code":"620826","name":"静宁县"}]},{"code":"6209","name":"酒泉市","childs":[{"code":"620902","name":"肃州区"},{"code":"620921","name":"金塔县"},{"code":"620922","name":"瓜州县"},{"code":"620923","name":"肃北蒙古族自治县"},{"code":"620924","name":"阿克塞哈萨克族自治县"},{"code":"620981","name":"玉门市"},{"code":"620982","name":"敦煌市"}]},{"code":"6210","name":"庆阳市","childs":[{"code":"621002","name":"西峰区"},{"code":"621021","name":"庆城县"},{"code":"621022","name":"环县"},{"code":"621023","name":"华池县"},{"code":"621024","name":"合水县"},{"code":"621025","name":"正宁县"},{"code":"621026","name":"宁县"},{"code":"621027","name":"镇原县"}]},{"code":"6211","name":"定西市","childs":[{"code":"621102","name":"安定区"},{"code":"621121","name":"通渭县"},{"code":"621122","name":"陇西县"},{"code":"621123","name":"渭源县"},{"code":"621124","name":"临洮县"},{"code":"621125","name":"漳县"},{"code":"621126","name":"岷县"}]},{"code":"6212","name":"陇南市","childs":[{"code":"621202","name":"武都区"},{"code":"621221","name":"成县"},{"code":"621222","name":"文县"},{"code":"621223","name":"宕昌县"},{"code":"621224","name":"康县"},{"code":"621225","name":"西和县"},{"code":"621226","name":"礼县"},{"code":"621227","name":"徽县"},{"code":"621228","name":"两当县"}]},{"code":"6229","name":"临夏回族自治州","childs":[{"code":"622901","name":"临夏市"},{"code":"622921","name":"临夏县"},{"code":"622922","name":"康乐县"},{"code":"622923","name":"永靖县"},{"code":"622924","name":"广河县"},{"code":"622925","name":"和政县"},{"code":"622926","name":"东乡族自治县"},{"code":"622927","name":"积石山保安族东乡族撒拉族自治县"}]},{"code":"6230","name":"甘南藏族自治州","childs":[{"code":"623001","name":"合作市"},{"code":"623021","name":"临潭县"},{"code":"623022","name":"卓尼县"},{"code":"623023","name":"舟曲县"},{"code":"623024","name":"迭部县"},{"code":"623025","name":"玛曲县"},{"code":"623026","name":"碌曲县"},{"code":"623027","name":"夏河县"}]}]},{"code":"63","name":"青海省","childs":[{"code":"6301","name":"西宁市","childs":[{"code":"630102","name":"城东区"},{"code":"630103","name":"城中区"},{"code":"630104","name":"城西区"},{"code":"630105","name":"城北区"},{"code":"630121","name":"大通回族土族自治县"},{"code":"630122","name":"湟中县"},{"code":"630123","name":"湟源县"}]},{"code":"6302","name":"海东市","childs":[{"code":"630202","name":"乐都区"},{"code":"630203","name":"平安区"},{"code":"630222","name":"民和回族土族自治县"},{"code":"630223","name":"互助土族自治县"},{"code":"630224","name":"化隆回族自治县"},{"code":"630225","name":"循化撒拉族自治县"}]},{"code":"6322","name":"海北藏族自治州","childs":[{"code":"632221","name":"门源回族自治县"},{"code":"632222","name":"祁连县"},{"code":"632223","name":"海晏县"},{"code":"632224","name":"刚察县"}]},{"code":"6323","name":"黄南藏族自治州","childs":[{"code":"632321","name":"同仁县"},{"code":"632322","name":"尖扎县"},{"code":"632323","name":"泽库县"},{"code":"632324","name":"河南蒙古族自治县"}]},{"code":"6325","name":"海南藏族自治州","childs":[{"code":"632521","name":"共和县"},{"code":"632522","name":"同德县"},{"code":"632523","name":"贵德县"},{"code":"632524","name":"兴海县"},{"code":"632525","name":"贵南县"}]},{"code":"6326","name":"果洛藏族自治州","childs":[{"code":"632621","name":"玛沁县"},{"code":"632622","name":"班玛县"},{"code":"632623","name":"甘德县"},{"code":"632624","name":"达日县"},{"code":"632625","name":"久治县"},{"code":"632626","name":"玛多县"}]},{"code":"6327","name":"玉树藏族自治州","childs":[{"code":"632701","name":"玉树市"},{"code":"632722","name":"杂多县"},{"code":"632723","name":"称多县"},{"code":"632724","name":"治多县"},{"code":"632725","name":"囊谦县"},{"code":"632726","name":"曲麻莱县"}]},{"code":"6328","name":"海西蒙古族藏族自治州","childs":[{"code":"632801","name":"格尔木市"},{"code":"632802","name":"德令哈市"},{"code":"632821","name":"乌兰县"},{"code":"632822","name":"都兰县"},{"code":"632823","name":"天峻县"}]}]},{"code":"64","name":"宁夏回族自治区","childs":[{"code":"6401","name":"银川市","childs":[{"code":"640104","name":"兴庆区"},{"code":"640105","name":"西夏区"},{"code":"640106","name":"金凤区"},{"code":"640121","name":"永宁县"},{"code":"640122","name":"贺兰县"},{"code":"640181","name":"灵武市"}]},{"code":"6402","name":"石嘴山市","childs":[{"code":"640202","name":"大武口区"},{"code":"640205","name":"惠农区"},{"code":"640221","name":"平罗县"}]},{"code":"6403","name":"吴忠市","childs":[{"code":"640302","name":"利通区"},{"code":"640303","name":"红寺堡区"},{"code":"640323","name":"盐池县"},{"code":"640324","name":"同心县"},{"code":"640381","name":"青铜峡市"}]},{"code":"6404","name":"固原市","childs":[{"code":"640402","name":"原州区"},{"code":"640422","name":"西吉县"},{"code":"640423","name":"隆德县"},{"code":"640424","name":"泾源县"},{"code":"640425","name":"彭阳县"}]},{"code":"6405","name":"中卫市","childs":[{"code":"640502","name":"沙坡头区"},{"code":"640521","name":"中宁县"},{"code":"640522","name":"海原县"}]}]},{"code":"65","name":"新疆维吾尔自治区","childs":[{"code":"6501","name":"乌鲁木齐市","childs":[{"code":"650102","name":"天山区"},{"code":"650103","name":"沙依巴克区"},{"code":"650104","name":"新市区"},{"code":"650105","name":"水磨沟区"},{"code":"650106","name":"头屯河区"},{"code":"650107","name":"达坂城区"},{"code":"650109","name":"米东区"},{"code":"650121","name":"乌鲁木齐县"}]},{"code":"6502","name":"克拉玛依市","childs":[{"code":"650202","name":"独山子区"},{"code":"650203","name":"克拉玛依区"},{"code":"650204","name":"白碱滩区"},{"code":"650205","name":"乌尔禾区"}]},{"code":"6504","name":"吐鲁番市","childs":[{"code":"650402","name":"高昌区"},{"code":"650421","name":"鄯善县"},{"code":"650422","name":"托克逊县"}]},{"code":"6505","name":"哈密市","childs":[{"code":"650502","name":"伊州区"},{"code":"650521","name":"巴里坤哈萨克自治县"},{"code":"650522","name":"伊吾县"}]},{"code":"6523","name":"昌吉回族自治州","childs":[{"code":"652301","name":"昌吉市"},{"code":"652302","name":"阜康市"},{"code":"652323","name":"呼图壁县"},{"code":"652324","name":"玛纳斯县"},{"code":"652325","name":"奇台县"},{"code":"652327","name":"吉木萨尔县"},{"code":"652328","name":"木垒哈萨克自治县"}]},{"code":"6527","name":"博尔塔拉蒙古自治州","childs":[{"code":"652701","name":"博乐市"},{"code":"652702","name":"阿拉山口市"},{"code":"652722","name":"精河县"},{"code":"652723","name":"温泉县"}]},{"code":"6528","name":"巴音郭楞蒙古自治州","childs":[{"code":"652801","name":"库尔勒市"},{"code":"652822","name":"轮台县"},{"code":"652823","name":"尉犁县"},{"code":"652824","name":"若羌县"},{"code":"652825","name":"且末县"},{"code":"652826","name":"焉耆回族自治县"},{"code":"652827","name":"和静县"},{"code":"652828","name":"和硕县"},{"code":"652829","name":"博湖县"}]},{"code":"6529","name":"阿克苏地区","childs":[{"code":"652901","name":"阿克苏市"},{"code":"652922","name":"温宿县"},{"code":"652923","name":"库车县"},{"code":"652924","name":"沙雅县"},{"code":"652925","name":"新和县"},{"code":"652926","name":"拜城县"},{"code":"652927","name":"乌什县"},{"code":"652928","name":"阿瓦提县"},{"code":"652929","name":"柯坪县"}]},{"code":"6530","name":"克孜勒苏柯尔克孜自治州","childs":[{"code":"653001","name":"阿图什市"},{"code":"653022","name":"阿克陶县"},{"code":"653023","name":"阿合奇县"},{"code":"653024","name":"乌恰县"}]},{"code":"6531","name":"喀什地区","childs":[{"code":"653101","name":"喀什市"},{"code":"653121","name":"疏附县"},{"code":"653122","name":"疏勒县"},{"code":"653123","name":"英吉沙县"},{"code":"653124","name":"泽普县"},{"code":"653125","name":"莎车县"},{"code":"653126","name":"叶城县"},{"code":"653127","name":"麦盖提县"},{"code":"653128","name":"岳普湖县"},{"code":"653129","name":"伽师县"},{"code":"653130","name":"巴楚县"},{"code":"653131","name":"塔什库尔干塔吉克自治县"}]},{"code":"6532","name":"和田地区","childs":[{"code":"653201","name":"和田市"},{"code":"653221","name":"和田县"},{"code":"653222","name":"墨玉县"},{"code":"653223","name":"皮山县"},{"code":"653224","name":"洛浦县"},{"code":"653225","name":"策勒县"},{"code":"653226","name":"于田县"},{"code":"653227","name":"民丰县"}]},{"code":"6540","name":"伊犁哈萨克自治州","childs":[{"code":"654002","name":"伊宁市"},{"code":"654003","name":"奎屯市"},{"code":"654004","name":"霍尔果斯市"},{"code":"654021","name":"伊宁县"},{"code":"654022","name":"察布查尔锡伯自治县"},{"code":"654023","name":"霍城县"},{"code":"654024","name":"巩留县"},{"code":"654025","name":"新源县"},{"code":"654026","name":"昭苏县"},{"code":"654027","name":"特克斯县"},{"code":"654028","name":"尼勒克县"}]},{"code":"6542","name":"塔城地区","childs":[{"code":"654201","name":"塔城市"},{"code":"654202","name":"乌苏市"},{"code":"654221","name":"额敏县"},{"code":"654223","name":"沙湾县"},{"code":"654224","name":"托里县"},{"code":"654225","name":"裕民县"},{"code":"654226","name":"和布克赛尔蒙古自治县"}]},{"code":"6543","name":"阿勒泰地区","childs":[{"code":"654301","name":"阿勒泰市"},{"code":"654321","name":"布尔津县"},{"code":"654322","name":"富蕴县"},{"code":"654323","name":"福海县"},{"code":"654324","name":"哈巴河县"},{"code":"654325","name":"青河县"},{"code":"654326","name":"吉木乃县"}]},{"code":"6590","name":"自治区直辖县级行政区划","childs":[{"code":"659001","name":"石河子市"},{"code":"659002","name":"阿拉尔市"},{"code":"659003","name":"图木舒克市"},{"code":"659004","name":"五家渠市"},{"code":"659006","name":"铁门关市"}]}]},{"code":"71","name":"台湾省","childs":[]},{"code":"81","name":"香港特别行政区","childs":[]},{"code":"82","name":"澳门特别行政区","childs":[]}] |
| New file |
| | |
| | | [core] |
| | | bare = true |
| | | repositoryformatversion = 0 |
| | | filemode = false |
| | | symlinks = false |
| | | ignorecase = true |
| | | [remote "origin"] |
| | | url = http://192.168.0.105:10010/r/Inspection-uniapp.git |
| | | fetch = +refs/heads/*:refs/remotes/origin/* |
| New file |
| | |
| | | Unnamed repository; edit this file 'description' to name the repository. |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to check the commit log message taken by |
| | | # applypatch from an e-mail message. |
| | | # |
| | | # The hook should exit with non-zero status after issuing an |
| | | # appropriate message if it wants to stop the commit. The hook is |
| | | # allowed to edit the commit message file. |
| | | # |
| | | # To enable this hook, rename this file to "applypatch-msg". |
| | | |
| | | . git-sh-setup |
| | | commitmsg="$(git rev-parse --git-path hooks/commit-msg)" |
| | | test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} |
| | | : |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to check the commit log message. |
| | | # Called by "git commit" with one argument, the name of the file |
| | | # that has the commit message. The hook should exit with non-zero |
| | | # status after issuing an appropriate message if it wants to stop the |
| | | # commit. The hook is allowed to edit the commit message file. |
| | | # |
| | | # To enable this hook, rename this file to "commit-msg". |
| | | |
| | | # Uncomment the below to add a Signed-off-by line to the message. |
| | | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg |
| | | # hook is more suited to it. |
| | | # |
| | | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') |
| | | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" |
| | | |
| | | # This example catches duplicate Signed-off-by lines. |
| | | |
| | | test "" = "$(grep '^Signed-off-by: ' "$1" | |
| | | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { |
| | | echo >&2 Duplicate Signed-off-by lines. |
| | | exit 1 |
| | | } |
| New file |
| | |
| | | #!/usr/bin/perl |
| | | |
| | | use strict; |
| | | use warnings; |
| | | use IPC::Open2; |
| | | |
| | | # An example hook script to integrate Watchman |
| | | # (https://facebook.github.io/watchman/) with git to speed up detecting |
| | | # new and modified files. |
| | | # |
| | | # The hook is passed a version (currently 2) and last update token |
| | | # formatted as a string and outputs to stdout a new update token and |
| | | # all files that have been modified since the update token. Paths must |
| | | # be relative to the root of the working tree and separated by a single NUL. |
| | | # |
| | | # To enable this hook, rename this file to "query-watchman" and set |
| | | # 'git config core.fsmonitor .git/hooks/query-watchman' |
| | | # |
| | | my ($version, $last_update_token) = @ARGV; |
| | | |
| | | # Uncomment for debugging |
| | | # print STDERR "$0 $version $last_update_token\n"; |
| | | |
| | | # Check the hook interface version |
| | | if ($version ne 2) { |
| | | die "Unsupported query-fsmonitor hook version '$version'.\n" . |
| | | "Falling back to scanning...\n"; |
| | | } |
| | | |
| | | my $git_work_tree = get_working_dir(); |
| | | |
| | | my $retry = 1; |
| | | |
| | | my $json_pkg; |
| | | eval { |
| | | require JSON::XS; |
| | | $json_pkg = "JSON::XS"; |
| | | 1; |
| | | } or do { |
| | | require JSON::PP; |
| | | $json_pkg = "JSON::PP"; |
| | | }; |
| | | |
| | | launch_watchman(); |
| | | |
| | | sub launch_watchman { |
| | | my $o = watchman_query(); |
| | | if (is_work_tree_watched($o)) { |
| | | output_result($o->{clock}, @{$o->{files}}); |
| | | } |
| | | } |
| | | |
| | | sub output_result { |
| | | my ($clockid, @files) = @_; |
| | | |
| | | # Uncomment for debugging watchman output |
| | | # open (my $fh, ">", ".git/watchman-output.out"); |
| | | # binmode $fh, ":utf8"; |
| | | # print $fh "$clockid\n@files\n"; |
| | | # close $fh; |
| | | |
| | | binmode STDOUT, ":utf8"; |
| | | print $clockid; |
| | | print "\0"; |
| | | local $, = "\0"; |
| | | print @files; |
| | | } |
| | | |
| | | sub watchman_clock { |
| | | my $response = qx/watchman clock "$git_work_tree"/; |
| | | die "Failed to get clock id on '$git_work_tree'.\n" . |
| | | "Falling back to scanning...\n" if $? != 0; |
| | | |
| | | return $json_pkg->new->utf8->decode($response); |
| | | } |
| | | |
| | | sub watchman_query { |
| | | my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') |
| | | or die "open2() failed: $!\n" . |
| | | "Falling back to scanning...\n"; |
| | | |
| | | # In the query expression below we're asking for names of files that |
| | | # changed since $last_update_token but not from the .git folder. |
| | | # |
| | | # To accomplish this, we're using the "since" generator to use the |
| | | # recency index to select candidate nodes and "fields" to limit the |
| | | # output to file names only. Then we're using the "expression" term to |
| | | # further constrain the results. |
| | | my $last_update_line = ""; |
| | | if (substr($last_update_token, 0, 1) eq "c") { |
| | | $last_update_token = "\"$last_update_token\""; |
| | | $last_update_line = qq[\n"since": $last_update_token,]; |
| | | } |
| | | my $query = <<" END"; |
| | | ["query", "$git_work_tree", {$last_update_line |
| | | "fields": ["name"], |
| | | "expression": ["not", ["dirname", ".git"]] |
| | | }] |
| | | END |
| | | |
| | | # Uncomment for debugging the watchman query |
| | | # open (my $fh, ">", ".git/watchman-query.json"); |
| | | # print $fh $query; |
| | | # close $fh; |
| | | |
| | | print CHLD_IN $query; |
| | | close CHLD_IN; |
| | | my $response = do {local $/; <CHLD_OUT>}; |
| | | |
| | | # Uncomment for debugging the watch response |
| | | # open ($fh, ">", ".git/watchman-response.json"); |
| | | # print $fh $response; |
| | | # close $fh; |
| | | |
| | | die "Watchman: command returned no output.\n" . |
| | | "Falling back to scanning...\n" if $response eq ""; |
| | | die "Watchman: command returned invalid output: $response\n" . |
| | | "Falling back to scanning...\n" unless $response =~ /^\{/; |
| | | |
| | | return $json_pkg->new->utf8->decode($response); |
| | | } |
| | | |
| | | sub is_work_tree_watched { |
| | | my ($output) = @_; |
| | | my $error = $output->{error}; |
| | | if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { |
| | | $retry--; |
| | | my $response = qx/watchman watch "$git_work_tree"/; |
| | | die "Failed to make watchman watch '$git_work_tree'.\n" . |
| | | "Falling back to scanning...\n" if $? != 0; |
| | | $output = $json_pkg->new->utf8->decode($response); |
| | | $error = $output->{error}; |
| | | die "Watchman: $error.\n" . |
| | | "Falling back to scanning...\n" if $error; |
| | | |
| | | # Uncomment for debugging watchman output |
| | | # open (my $fh, ">", ".git/watchman-output.out"); |
| | | # close $fh; |
| | | |
| | | # Watchman will always return all files on the first query so |
| | | # return the fast "everything is dirty" flag to git and do the |
| | | # Watchman query just to get it over with now so we won't pay |
| | | # the cost in git to look up each individual file. |
| | | my $o = watchman_clock(); |
| | | $error = $output->{error}; |
| | | |
| | | die "Watchman: $error.\n" . |
| | | "Falling back to scanning...\n" if $error; |
| | | |
| | | output_result($o->{clock}, ("/")); |
| | | $last_update_token = $o->{clock}; |
| | | |
| | | eval { launch_watchman() }; |
| | | return 0; |
| | | } |
| | | |
| | | die "Watchman: $error.\n" . |
| | | "Falling back to scanning...\n" if $error; |
| | | |
| | | return 1; |
| | | } |
| | | |
| | | sub get_working_dir { |
| | | my $working_dir; |
| | | if ($^O =~ 'msys' || $^O =~ 'cygwin') { |
| | | $working_dir = Win32::GetCwd(); |
| | | $working_dir =~ tr/\\/\//; |
| | | } else { |
| | | require Cwd; |
| | | $working_dir = Cwd::cwd(); |
| | | } |
| | | |
| | | return $working_dir; |
| | | } |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to prepare a packed repository for use over |
| | | # dumb transports. |
| | | # |
| | | # To enable this hook, rename this file to "post-update". |
| | | |
| | | exec git update-server-info |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to verify what is about to be committed |
| | | # by applypatch from an e-mail message. |
| | | # |
| | | # The hook should exit with non-zero status after issuing an |
| | | # appropriate message if it wants to stop the commit. |
| | | # |
| | | # To enable this hook, rename this file to "pre-applypatch". |
| | | |
| | | . git-sh-setup |
| | | precommit="$(git rev-parse --git-path hooks/pre-commit)" |
| | | test -x "$precommit" && exec "$precommit" ${1+"$@"} |
| | | : |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to verify what is about to be committed. |
| | | # Called by "git commit" with no arguments. The hook should |
| | | # exit with non-zero status after issuing an appropriate message if |
| | | # it wants to stop the commit. |
| | | # |
| | | # To enable this hook, rename this file to "pre-commit". |
| | | |
| | | if git rev-parse --verify HEAD >/dev/null 2>&1 |
| | | then |
| | | against=HEAD |
| | | else |
| | | # Initial commit: diff against an empty tree object |
| | | against=$(git hash-object -t tree /dev/null) |
| | | fi |
| | | |
| | | # If you want to allow non-ASCII filenames set this variable to true. |
| | | allownonascii=$(git config --type=bool hooks.allownonascii) |
| | | |
| | | # Redirect output to stderr. |
| | | exec 1>&2 |
| | | |
| | | # Cross platform projects tend to avoid non-ASCII filenames; prevent |
| | | # them from being added to the repository. We exploit the fact that the |
| | | # printable range starts at the space character and ends with tilde. |
| | | if [ "$allownonascii" != "true" ] && |
| | | # Note that the use of brackets around a tr range is ok here, (it's |
| | | # even required, for portability to Solaris 10's /usr/bin/tr), since |
| | | # the square bracket bytes happen to fall in the designated range. |
| | | test $(git diff --cached --name-only --diff-filter=A -z $against | |
| | | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 |
| | | then |
| | | cat <<\EOF |
| | | Error: Attempt to add a non-ASCII file name. |
| | | |
| | | This can cause problems if you want to work with people on other platforms. |
| | | |
| | | To be portable it is advisable to rename the file. |
| | | |
| | | If you know what you are doing you can disable this check using: |
| | | |
| | | git config hooks.allownonascii true |
| | | EOF |
| | | exit 1 |
| | | fi |
| | | |
| | | # If there are whitespace errors, print the offending file names and fail. |
| | | exec git diff-index --check --cached $against -- |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to verify what is about to be committed. |
| | | # Called by "git merge" with no arguments. The hook should |
| | | # exit with non-zero status after issuing an appropriate message to |
| | | # stderr if it wants to stop the merge commit. |
| | | # |
| | | # To enable this hook, rename this file to "pre-merge-commit". |
| | | |
| | | . git-sh-setup |
| | | test -x "$GIT_DIR/hooks/pre-commit" && |
| | | exec "$GIT_DIR/hooks/pre-commit" |
| | | : |
| New file |
| | |
| | | #!/bin/sh |
| | | |
| | | # An example hook script to verify what is about to be pushed. Called by "git |
| | | # push" after it has checked the remote status, but before anything has been |
| | | # pushed. If this script exits with a non-zero status nothing will be pushed. |
| | | # |
| | | # This hook is called with the following parameters: |
| | | # |
| | | # $1 -- Name of the remote to which the push is being done |
| | | # $2 -- URL to which the push is being done |
| | | # |
| | | # If pushing without using a named remote those arguments will be equal. |
| | | # |
| | | # Information about the commits which are being pushed is supplied as lines to |
| | | # the standard input in the form: |
| | | # |
| | | # <local ref> <local oid> <remote ref> <remote oid> |
| | | # |
| | | # This sample shows how to prevent push of commits where the log message starts |
| | | # with "WIP" (work in progress). |
| | | |
| | | remote="$1" |
| | | url="$2" |
| | | |
| | | zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0') |
| | | |
| | | while read local_ref local_oid remote_ref remote_oid |
| | | do |
| | | if test "$local_oid" = "$zero" |
| | | then |
| | | # Handle delete |
| | | : |
| | | else |
| | | if test "$remote_oid" = "$zero" |
| | | then |
| | | # New branch, examine all commits |
| | | range="$local_oid" |
| | | else |
| | | # Update to existing branch, examine new commits |
| | | range="$remote_oid..$local_oid" |
| | | fi |
| | | |
| | | # Check for WIP commit |
| | | commit=$(git rev-list -n 1 --grep '^WIP' "$range") |
| | | if test -n "$commit" |
| | | then |
| | | echo >&2 "Found WIP commit in $local_ref, not pushing" |
| | | exit 1 |
| | | fi |
| | | fi |
| | | done |
| | | |
| | | exit 0 |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # Copyright (c) 2006, 2008 Junio C Hamano |
| | | # |
| | | # The "pre-rebase" hook is run just before "git rebase" starts doing |
| | | # its job, and can prevent the command from running by exiting with |
| | | # non-zero status. |
| | | # |
| | | # The hook is called with the following parameters: |
| | | # |
| | | # $1 -- the upstream the series was forked from. |
| | | # $2 -- the branch being rebased (or empty when rebasing the current branch). |
| | | # |
| | | # This sample shows how to prevent topic branches that are already |
| | | # merged to 'next' branch from getting rebased, because allowing it |
| | | # would result in rebasing already published history. |
| | | |
| | | publish=next |
| | | basebranch="$1" |
| | | if test "$#" = 2 |
| | | then |
| | | topic="refs/heads/$2" |
| | | else |
| | | topic=`git symbolic-ref HEAD` || |
| | | exit 0 ;# we do not interrupt rebasing detached HEAD |
| | | fi |
| | | |
| | | case "$topic" in |
| | | refs/heads/??/*) |
| | | ;; |
| | | *) |
| | | exit 0 ;# we do not interrupt others. |
| | | ;; |
| | | esac |
| | | |
| | | # Now we are dealing with a topic branch being rebased |
| | | # on top of master. Is it OK to rebase it? |
| | | |
| | | # Does the topic really exist? |
| | | git show-ref -q "$topic" || { |
| | | echo >&2 "No such branch $topic" |
| | | exit 1 |
| | | } |
| | | |
| | | # Is topic fully merged to master? |
| | | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` |
| | | if test -z "$not_in_master" |
| | | then |
| | | echo >&2 "$topic is fully merged to master; better remove it." |
| | | exit 1 ;# we could allow it, but there is no point. |
| | | fi |
| | | |
| | | # Is topic ever merged to next? If so you should not be rebasing it. |
| | | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` |
| | | only_next_2=`git rev-list ^master ${publish} | sort` |
| | | if test "$only_next_1" = "$only_next_2" |
| | | then |
| | | not_in_topic=`git rev-list "^$topic" master` |
| | | if test -z "$not_in_topic" |
| | | then |
| | | echo >&2 "$topic is already up to date with master" |
| | | exit 1 ;# we could allow it, but there is no point. |
| | | else |
| | | exit 0 |
| | | fi |
| | | else |
| | | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` |
| | | /usr/bin/perl -e ' |
| | | my $topic = $ARGV[0]; |
| | | my $msg = "* $topic has commits already merged to public branch:\n"; |
| | | my (%not_in_next) = map { |
| | | /^([0-9a-f]+) /; |
| | | ($1 => 1); |
| | | } split(/\n/, $ARGV[1]); |
| | | for my $elem (map { |
| | | /^([0-9a-f]+) (.*)$/; |
| | | [$1 => $2]; |
| | | } split(/\n/, $ARGV[2])) { |
| | | if (!exists $not_in_next{$elem->[0]}) { |
| | | if ($msg) { |
| | | print STDERR $msg; |
| | | undef $msg; |
| | | } |
| | | print STDERR " $elem->[1]\n"; |
| | | } |
| | | } |
| | | ' "$topic" "$not_in_next" "$not_in_master" |
| | | exit 1 |
| | | fi |
| | | |
| | | <<\DOC_END |
| | | |
| | | This sample hook safeguards topic branches that have been |
| | | published from being rewound. |
| | | |
| | | The workflow assumed here is: |
| | | |
| | | * Once a topic branch forks from "master", "master" is never |
| | | merged into it again (either directly or indirectly). |
| | | |
| | | * Once a topic branch is fully cooked and merged into "master", |
| | | it is deleted. If you need to build on top of it to correct |
| | | earlier mistakes, a new topic branch is created by forking at |
| | | the tip of the "master". This is not strictly necessary, but |
| | | it makes it easier to keep your history simple. |
| | | |
| | | * Whenever you need to test or publish your changes to topic |
| | | branches, merge them into "next" branch. |
| | | |
| | | The script, being an example, hardcodes the publish branch name |
| | | to be "next", but it is trivial to make it configurable via |
| | | $GIT_DIR/config mechanism. |
| | | |
| | | With this workflow, you would want to know: |
| | | |
| | | (1) ... if a topic branch has ever been merged to "next". Young |
| | | topic branches can have stupid mistakes you would rather |
| | | clean up before publishing, and things that have not been |
| | | merged into other branches can be easily rebased without |
| | | affecting other people. But once it is published, you would |
| | | not want to rewind it. |
| | | |
| | | (2) ... if a topic branch has been fully merged to "master". |
| | | Then you can delete it. More importantly, you should not |
| | | build on top of it -- other people may already want to |
| | | change things related to the topic as patches against your |
| | | "master", so if you need further changes, it is better to |
| | | fork the topic (perhaps with the same name) afresh from the |
| | | tip of "master". |
| | | |
| | | Let's look at this example: |
| | | |
| | | o---o---o---o---o---o---o---o---o---o "next" |
| | | / / / / |
| | | / a---a---b A / / |
| | | / / / / |
| | | / / c---c---c---c B / |
| | | / / / \ / |
| | | / / / b---b C \ / |
| | | / / / / \ / |
| | | ---o---o---o---o---o---o---o---o---o---o---o "master" |
| | | |
| | | |
| | | A, B and C are topic branches. |
| | | |
| | | * A has one fix since it was merged up to "next". |
| | | |
| | | * B has finished. It has been fully merged up to "master" and "next", |
| | | and is ready to be deleted. |
| | | |
| | | * C has not merged to "next" at all. |
| | | |
| | | We would want to allow C to be rebased, refuse A, and encourage |
| | | B to be deleted. |
| | | |
| | | To compute (1): |
| | | |
| | | git rev-list ^master ^topic next |
| | | git rev-list ^master next |
| | | |
| | | if these match, topic has not merged in next at all. |
| | | |
| | | To compute (2): |
| | | |
| | | git rev-list master..topic |
| | | |
| | | if this is empty, it is fully merged to "master". |
| | | |
| | | DOC_END |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to make use of push options. |
| | | # The example simply echoes all push options that start with 'echoback=' |
| | | # and rejects all pushes when the "reject" push option is used. |
| | | # |
| | | # To enable this hook, rename this file to "pre-receive". |
| | | |
| | | if test -n "$GIT_PUSH_OPTION_COUNT" |
| | | then |
| | | i=0 |
| | | while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" |
| | | do |
| | | eval "value=\$GIT_PUSH_OPTION_$i" |
| | | case "$value" in |
| | | echoback=*) |
| | | echo "echo from the pre-receive-hook: ${value#*=}" >&2 |
| | | ;; |
| | | reject) |
| | | exit 1 |
| | | esac |
| | | i=$((i + 1)) |
| | | done |
| | | fi |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to prepare the commit log message. |
| | | # Called by "git commit" with the name of the file that has the |
| | | # commit message, followed by the description of the commit |
| | | # message's source. The hook's purpose is to edit the commit |
| | | # message file. If the hook fails with a non-zero status, |
| | | # the commit is aborted. |
| | | # |
| | | # To enable this hook, rename this file to "prepare-commit-msg". |
| | | |
| | | # This hook includes three examples. The first one removes the |
| | | # "# Please enter the commit message..." help message. |
| | | # |
| | | # The second includes the output of "git diff --name-status -r" |
| | | # into the message, just before the "git status" output. It is |
| | | # commented because it doesn't cope with --amend or with squashed |
| | | # commits. |
| | | # |
| | | # The third example adds a Signed-off-by line to the message, that can |
| | | # still be edited. This is rarely a good idea. |
| | | |
| | | COMMIT_MSG_FILE=$1 |
| | | COMMIT_SOURCE=$2 |
| | | SHA1=$3 |
| | | |
| | | /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" |
| | | |
| | | # case "$COMMIT_SOURCE,$SHA1" in |
| | | # ,|template,) |
| | | # /usr/bin/perl -i.bak -pe ' |
| | | # print "\n" . `git diff --cached --name-status -r` |
| | | # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; |
| | | # *) ;; |
| | | # esac |
| | | |
| | | # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') |
| | | # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" |
| | | # if test -z "$COMMIT_SOURCE" |
| | | # then |
| | | # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" |
| | | # fi |
| New file |
| | |
| | | #!/bin/sh |
| | | |
| | | # An example hook script to update a checked-out tree on a git push. |
| | | # |
| | | # This hook is invoked by git-receive-pack(1) when it reacts to git |
| | | # push and updates reference(s) in its repository, and when the push |
| | | # tries to update the branch that is currently checked out and the |
| | | # receive.denyCurrentBranch configuration variable is set to |
| | | # updateInstead. |
| | | # |
| | | # By default, such a push is refused if the working tree and the index |
| | | # of the remote repository has any difference from the currently |
| | | # checked out commit; when both the working tree and the index match |
| | | # the current commit, they are updated to match the newly pushed tip |
| | | # of the branch. This hook is to be used to override the default |
| | | # behaviour; however the code below reimplements the default behaviour |
| | | # as a starting point for convenient modification. |
| | | # |
| | | # The hook receives the commit with which the tip of the current |
| | | # branch is going to be updated: |
| | | commit=$1 |
| | | |
| | | # It can exit with a non-zero status to refuse the push (when it does |
| | | # so, it must not modify the index or the working tree). |
| | | die () { |
| | | echo >&2 "$*" |
| | | exit 1 |
| | | } |
| | | |
| | | # Or it can make any necessary changes to the working tree and to the |
| | | # index to bring them to the desired state when the tip of the current |
| | | # branch is updated to the new commit, and exit with a zero status. |
| | | # |
| | | # For example, the hook can simply run git read-tree -u -m HEAD "$1" |
| | | # in order to emulate git fetch that is run in the reverse direction |
| | | # with git push, as the two-tree form of git read-tree -u -m is |
| | | # essentially the same as git switch or git checkout that switches |
| | | # branches while keeping the local changes in the working tree that do |
| | | # not interfere with the difference between the branches. |
| | | |
| | | # The below is a more-or-less exact translation to shell of the C code |
| | | # for the default behaviour for git's push-to-checkout hook defined in |
| | | # the push_to_deploy() function in builtin/receive-pack.c. |
| | | # |
| | | # Note that the hook will be executed from the repository directory, |
| | | # not from the working tree, so if you want to perform operations on |
| | | # the working tree, you will have to adapt your code accordingly, e.g. |
| | | # by adding "cd .." or using relative paths. |
| | | |
| | | if ! git update-index -q --ignore-submodules --refresh |
| | | then |
| | | die "Up-to-date check failed" |
| | | fi |
| | | |
| | | if ! git diff-files --quiet --ignore-submodules -- |
| | | then |
| | | die "Working directory has unstaged changes" |
| | | fi |
| | | |
| | | # This is a rough translation of: |
| | | # |
| | | # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX |
| | | if git cat-file -e HEAD 2>/dev/null |
| | | then |
| | | head=HEAD |
| | | else |
| | | head=$(git hash-object -t tree --stdin </dev/null) |
| | | fi |
| | | |
| | | if ! git diff-index --quiet --cached --ignore-submodules $head -- |
| | | then |
| | | die "Working directory has staged changes" |
| | | fi |
| | | |
| | | if ! git read-tree -u -m "$commit" |
| | | then |
| | | die "Could not update working tree to new HEAD" |
| | | fi |
| New file |
| | |
| | | #!/bin/sh |
| | | # |
| | | # An example hook script to block unannotated tags from entering. |
| | | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new |
| | | # |
| | | # To enable this hook, rename this file to "update". |
| | | # |
| | | # Config |
| | | # ------ |
| | | # hooks.allowunannotated |
| | | # This boolean sets whether unannotated tags will be allowed into the |
| | | # repository. By default they won't be. |
| | | # hooks.allowdeletetag |
| | | # This boolean sets whether deleting tags will be allowed in the |
| | | # repository. By default they won't be. |
| | | # hooks.allowmodifytag |
| | | # This boolean sets whether a tag may be modified after creation. By default |
| | | # it won't be. |
| | | # hooks.allowdeletebranch |
| | | # This boolean sets whether deleting branches will be allowed in the |
| | | # repository. By default they won't be. |
| | | # hooks.denycreatebranch |
| | | # This boolean sets whether remotely creating branches will be denied |
| | | # in the repository. By default this is allowed. |
| | | # |
| | | |
| | | # --- Command line |
| | | refname="$1" |
| | | oldrev="$2" |
| | | newrev="$3" |
| | | |
| | | # --- Safety check |
| | | if [ -z "$GIT_DIR" ]; then |
| | | echo "Don't run this script from the command line." >&2 |
| | | echo " (if you want, you could supply GIT_DIR then run" >&2 |
| | | echo " $0 <ref> <oldrev> <newrev>)" >&2 |
| | | exit 1 |
| | | fi |
| | | |
| | | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then |
| | | echo "usage: $0 <ref> <oldrev> <newrev>" >&2 |
| | | exit 1 |
| | | fi |
| | | |
| | | # --- Config |
| | | allowunannotated=$(git config --type=bool hooks.allowunannotated) |
| | | allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) |
| | | denycreatebranch=$(git config --type=bool hooks.denycreatebranch) |
| | | allowdeletetag=$(git config --type=bool hooks.allowdeletetag) |
| | | allowmodifytag=$(git config --type=bool hooks.allowmodifytag) |
| | | |
| | | # check for no description |
| | | projectdesc=$(sed -e '1q' "$GIT_DIR/description") |
| | | case "$projectdesc" in |
| | | "Unnamed repository"* | "") |
| | | echo "*** Project description file hasn't been set" >&2 |
| | | exit 1 |
| | | ;; |
| | | esac |
| | | |
| | | # --- Check types |
| | | # if $newrev is 0000...0000, it's a commit to delete a ref. |
| | | zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0') |
| | | if [ "$newrev" = "$zero" ]; then |
| | | newrev_type=delete |
| | | else |
| | | newrev_type=$(git cat-file -t $newrev) |
| | | fi |
| | | |
| | | case "$refname","$newrev_type" in |
| | | refs/tags/*,commit) |
| | | # un-annotated tag |
| | | short_refname=${refname##refs/tags/} |
| | | if [ "$allowunannotated" != "true" ]; then |
| | | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 |
| | | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 |
| | | exit 1 |
| | | fi |
| | | ;; |
| | | refs/tags/*,delete) |
| | | # delete tag |
| | | if [ "$allowdeletetag" != "true" ]; then |
| | | echo "*** Deleting a tag is not allowed in this repository" >&2 |
| | | exit 1 |
| | | fi |
| | | ;; |
| | | refs/tags/*,tag) |
| | | # annotated tag |
| | | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 |
| | | then |
| | | echo "*** Tag '$refname' already exists." >&2 |
| | | echo "*** Modifying a tag is not allowed in this repository." >&2 |
| | | exit 1 |
| | | fi |
| | | ;; |
| | | refs/heads/*,commit) |
| | | # branch |
| | | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then |
| | | echo "*** Creating a branch is not allowed in this repository" >&2 |
| | | exit 1 |
| | | fi |
| | | ;; |
| | | refs/heads/*,delete) |
| | | # delete branch |
| | | if [ "$allowdeletebranch" != "true" ]; then |
| | | echo "*** Deleting a branch is not allowed in this repository" >&2 |
| | | exit 1 |
| | | fi |
| | | ;; |
| | | refs/remotes/*,commit) |
| | | # tracking branch |
| | | ;; |
| | | refs/remotes/*,delete) |
| | | # delete tracking branch |
| | | if [ "$allowdeletebranch" != "true" ]; then |
| | | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 |
| | | exit 1 |
| | | fi |
| | | ;; |
| | | *) |
| | | # Anything else (is there anything else?) |
| | | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 |
| | | exit 1 |
| | | ;; |
| | | esac |
| | | |
| | | # --- Finished |
| | | exit 0 |
| New file |
| | |
| | | import { |
| | | clientId, |
| | | clientSecret |
| | | } from '@/common/setting' |
| | | import { |
| | | options |
| | | } from '@/http/config.js'; |
| | | import { |
| | | Base64 |
| | | } from '@/utils/base64.js'; |
| | | import Request from '@/utils/luch-request/index.js'; |
| | | const http = new Request(options); |
| | | http.interceptors.request.use((config) => { // 可使用async await 做异步操作 |
| | | // 假设有token值需要在头部需要携带 |
| | | let accessToken = uni.getStorageSync('accessToken'); |
| | | if (accessToken) { |
| | | config.header['Blade-Auth'] = 'bearer ' + accessToken; |
| | | } |
| | | // 客户端认证参数 |
| | | config.header['Authorization'] = 'Basic ' + Base64.encode(clientId + ':' + clientSecret); |
| | | |
| | | // 额外参数 |
| | | // config.data = config.data || {}; |
| | | // config.data.pf = uni.getSystemInfoSync().platform; |
| | | // config.data.sys = uni.getSystemInfoSync().system; |
| | | |
| | | // 演示custom 用处 |
| | | // if (config.custom.auth) { |
| | | // config.header.token = 'token' |
| | | // } |
| | | // if (config.custom.loading) { |
| | | // uni.showLoading() |
| | | // } |
| | | /** |
| | | /* 演示 |
| | | if (!token) { // 如果token不存在,return Promise.reject(config) 会取消本次请求 |
| | | return Promise.reject(config) |
| | | } |
| | | **/ |
| | | return config |
| | | }, config => { // 可使用async await 做异步操作 |
| | | return Promise.reject(config) |
| | | }) |
| | | http.interceptors.response.use((response) => { |
| | | // 若有数据返回则通过 |
| | | if (response.data.access_token || response.data.key) { |
| | | return response.data |
| | | } |
| | | // 服务端返回的状态码不等于200,则reject() |
| | | if (response.data.code !== 200) { |
| | | return Promise.reject(response); |
| | | } |
| | | return response.data; |
| | | }, (response) => { |
| | | /* 对响应错误做点什么 (statusCode !== 200)*/ |
| | | uni.showToast({ |
| | | title: response.data.msg, |
| | | icon: 'none' |
| | | }); |
| | | return Promise.reject(response) |
| | | }) |
| | | export default http; |
| New file |
| | |
| | | import { |
| | | devUrl, |
| | | prodUrl, |
| | | contentType |
| | | } from '@/common/setting' |
| | | |
| | | var options = { |
| | | baseURL: process.env.NODE_ENV === 'development' ? devUrl : prodUrl, |
| | | header: { |
| | | 'Content-Type': contentType |
| | | }, |
| | | method: 'POST', |
| | | dataType: 'json', |
| | | // #ifndef MP-ALIPAY || APP-PLUS |
| | | responseType: 'text', |
| | | // #endif |
| | | // 注:如果局部custom与全局custom有同名属性,则后面的属性会覆盖前面的属性,相当于Object.assign(全局,局部) |
| | | custom: {}, // 全局自定义参数默认值 |
| | | // #ifdef MP-ALIPAY || MP-WEIXIN |
| | | timeout: 30000, |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | sslVerify: true, |
| | | // #endif |
| | | // #ifdef H5 |
| | | // 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) |
| | | //withCredentials: false, |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | firstIpv4: false, // DNS解析时优先使用ipv4 仅 App-Android 支持 (HBuilderX 2.8.0+) |
| | | // #endif |
| | | // 局部优先级高于全局,返回当前请求的task,options。请勿在此处修改options。非必填 |
| | | // getTask: (task, options) => { |
| | | // 相当于设置了请求超时时间500ms |
| | | // setTimeout(() => { |
| | | // task.abort() |
| | | // }, 500) |
| | | // }, |
| | | // 全局自定义验证器。参数为statusCode 且必存在,不用判断空情况。 |
| | | // validateStatus: (statusCode) => { // statusCode 必存在。此处示例为全局默认配置 |
| | | // return statusCode >= 200 && statusCode < 300 |
| | | // } |
| | | }; |
| | | export { options }; |
| New file |
| | |
| | | // 获取api目录所有js文件 |
| | | const files = require.context('@/api', false, /\.js$/) |
| | | // 此处第二个参数vm,就是我们在页面使用的this,可以通过vm获取vuex等操作 |
| | | const install = (Vue, vm) => { |
| | | // 将各个定义的接口名称,统一放进对象挂载到vm.$u.api下(因为vm就是this,也即this.$u.api) |
| | | // 自动将所有api挂载到vm.$u.api中 |
| | | vm.$u.api = {} |
| | | files.keys().forEach(key => { |
| | | const api = files(key).default |
| | | for (let item in api) { |
| | | vm.$u.api[item] = api[item] |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export default { |
| | | install |
| | | } |
| New file |
| | |
| | | # git ls-files --others --exclude-from=.git/info/exclude |
| | | # Lines that start with '#' are comments. |
| | | # For a project mostly in C, the following would be a good set of |
| | | # exclude patterns (uncomment them if you want to use them): |
| | | # *.[oa] |
| | | # *~ |
| New file |
| | |
| | | import Vue from 'vue' |
| | | import App from './App' |
| | | import store from '@/store'; |
| | | |
| | | Vue.config.productionTip = false; |
| | | |
| | | App.mpType = 'app'; |
| | | |
| | | // 引入全局uView |
| | | import uView from 'uview-ui' |
| | | Vue.use(uView); |
| | | |
| | | // 引入vuex |
| | | const vuexStore = require("@/store/$u.mixin.js"); |
| | | Vue.mixin(vuexStore); |
| | | |
| | | // 创建对象 |
| | | const app = new Vue({ |
| | | store, |
| | | ...App |
| | | }); |
| | | |
| | | // 接口集中管理 |
| | | import httpInstall from '@/http/install.js' |
| | | Vue.use(httpInstall, app) |
| | | |
| | | // 公共函数 |
| | | import globalFunc from '@/utils/func.js' |
| | | Vue.use(globalFunc, app); |
| | | |
| | | app.$mount() |
| New file |
| | |
| | | { |
| | | "name" : "Rider", |
| | | "appid" : "__UNI__C29A206", |
| | | "description" : "", |
| | | "versionName" : "1.0.0", |
| | | "versionCode" : "100", |
| | | "transformPx" : false, |
| | | /* 5+App特有相关 */ |
| | | "app-plus" : { |
| | | "safearea" : { |
| | | "bottom" : { |
| | | "offset" : "none" |
| | | } |
| | | }, |
| | | "usingComponents" : true, |
| | | "nvueCompiler" : "uni-app", |
| | | "compilerVersion" : 3, |
| | | "splashscreen" : { |
| | | "alwaysShowBeforeRender" : true, |
| | | "waiting" : true, |
| | | "autoclose" : true, |
| | | "delay" : 0 |
| | | }, |
| | | /* 模块配置 */ |
| | | "modules" : {}, |
| | | /* 应用发布信息 */ |
| | | "distribute" : { |
| | | /* android打包配置 */ |
| | | "android" : { |
| | | "permissions" : [ |
| | | "<uses-feature android:name=\"android.hardware.camera\"/>", |
| | | "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CAMERA\"/>", |
| | | "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
| | | "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
| | | "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>", |
| | | "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", |
| | | "<uses-permission android:name=\"android.permission.VIBRATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", |
| | | "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>", |
| | | "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" |
| | | ] |
| | | }, |
| | | /* ios打包配置 */ |
| | | "ios" : {}, |
| | | /* SDK配置 */ |
| | | "sdkConfigs" : { |
| | | "ad" : {} |
| | | } |
| | | } |
| | | }, |
| | | /* 快应用特有相关 */ |
| | | "quickapp" : {}, |
| | | /* 小程序特有相关 */ |
| | | "mp-weixin" : { |
| | | "appid" : "wxc256e348c4032ebd", |
| | | "setting" : { |
| | | "urlCheck" : false |
| | | }, |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-alipay" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-baidu" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-toutiao" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "h5" : { |
| | | "template" : "template.h5.html", |
| | | "router" : { |
| | | "mode" : "hash", |
| | | "base" : "/rider/" |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | xÍ=Â0@aæÂ;óÓ$båv¥¦*s¤r¦7}zmC
ü/vôp&ßpñ
hîkà,>2ÆX¢°ÔÊ>öÞ eè·3¯jwð9§8X\±"ºvn¬ÿ
ÜsSSZá'¿Ø>3. |
| New file |
| | |
| | | { |
| | | "easycom": { |
| | | "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" |
| | | }, |
| | | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages |
| | | { |
| | | "path": "pages/login/login-account", |
| | | "style": { |
| | | "navigationBarTitleText": "登录", |
| | | "enablePullDownRefresh": false, |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/service/service", |
| | | "style": { |
| | | "navigationBarTitleText": "首页", |
| | | "enablePullDownRefresh": false, |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/taskinfo/list", |
| | | "style": { |
| | | "navigationBarTitleText": "任务列表", |
| | | "enablePullDownRefresh": false |
| | | } |
| | | |
| | | } |
| | | ], |
| | | "globalStyle": { |
| | | "navigationBarTextStyle": "black", |
| | | "navigationBarTitleText": "", |
| | | "navigationBarBackgroundColor": "#fff", |
| | | "backgroundColor": "#F7F7F7" |
| | | }, |
| | | "tabBar": { |
| | | "color": "#A6ABB5", |
| | | "selectedColor": "#0BB9C8", |
| | | "borderStyle": "white", |
| | | "backgroundColor": "#ffffff", |
| | | "list": [{ |
| | | "pagePath": "pages/service/service", |
| | | "iconPath": "static/images/tabbar/home.png", |
| | | "selectedIconPath": "static/images/tabbar/home_selected.png", |
| | | "text": "首页" |
| | | }, { |
| | | "pagePath": "pages/demo/demo", |
| | | "iconPath": "static/images/tabbar/demo.png", |
| | | "selectedIconPath": "static/images/tabbar/demo_selected.png", |
| | | "text": "示例" |
| | | }, { |
| | | "pagePath": "pages/user/center", |
| | | "iconPath": "static/images/tabbar/user.png", |
| | | "selectedIconPath": "static/images/tabbar/user_selected.png", |
| | | "text": "我的" |
| | | }] |
| | | }, |
| | | "condition": { //模式配置,仅开发期间生效 |
| | | "current": 0, //当前激活的模式(list 的索引项) |
| | | "list": [{ |
| | | "name": "", //模式名称 |
| | | "path": "", //启动页面,必选 |
| | | "query": "" //启动参数,在页面的onLoad函数里面得到 |
| | | }] |
| | | } |
| | | } |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <!-- 头部 start --> |
| | | <view class="head"> |
| | | <u-navbar :is-fixed="false" :border-bottom="false" :is-back="false" title="" |
| | | :background="{ background: '#0BB9C8' }"> |
| | | <view class="nav-wrap"> |
| | | <picker mode="selector" :range="positionArr" range-key="name" @change="changePicker" |
| | | class="picker-box"> |
| | | {{ position }} |
| | | <u-icon name="arrow-down" class="arrow" size="18" color="#C9C9C9"></u-icon> |
| | | </picker> |
| | | <image src="/static/images/rider.png" class="rider" mode="widthFix" v-if="!focus"></image> |
| | | |
| | | <view class="search-input" v-else> |
| | | <u-search height="50" placeholder="关键字" @blur="handleSearchBlur" :show-action="false" |
| | | v-model="keyword"></u-search> |
| | | </view> |
| | | |
| | | <view class="tool"> |
| | | <image src="/static/images/home/search.png" class="icon search-icon" mode="widthFix" |
| | | @click="handleFocus" v-if="!focus"></image> |
| | | <image src="/static/images/home/message.png" class="icon message-icon" mode="widthFix"></image> |
| | | <image src="/static/images/home/qr.png" class="icon qr-icon" mode="widthFix"></image> |
| | | </view> |
| | | </view> |
| | | </u-navbar> |
| | | <view class="head-bg"></view> |
| | | <swiper class="swiper-box" :indicator-dots="false" :autoplay="true" :interval="3000" :duration="1000"> |
| | | <swiper-item v-for="(item, index) in bannerList" :key="index"> |
| | | <navigator :url="item.url" hover-class="none" class="swiper-item"> |
| | | <image :src="item.img" class="banner"></image> |
| | | </navigator> |
| | | </swiper-item> |
| | | </swiper> |
| | | </view> |
| | | <!-- 头部 end --> |
| | | |
| | | <!-- 头部按钮 start --> |
| | | <view class="nav"> |
| | | <u-grid :col="5" :border="false"> |
| | | <u-grid-item bg-color="transparent" v-for="(item, index) in navButton" :key="index"> |
| | | <navigator :url="item.url" hover-class="none" class="nav-item"> |
| | | <image :src="item.img" mode="widthFix" class="nav-item-img"></image> |
| | | <view class="nav-item-name">{{ item.name }}</view> |
| | | </navigator> |
| | | </u-grid-item> |
| | | </u-grid> |
| | | </view> |
| | | <!-- 头部按钮 end --> |
| | | |
| | | <!-- 公告 start --> |
| | | <view class="notice-box"> |
| | | <image src="/static/images/home/notice.png" class="img" mode="widthFix"></image> |
| | | <view class="notice-info"> |
| | | <navigator hover-class="none" class="notice-cell" v-for="(item, index) in noticeList" :key="index"> |
| | | <image :src="item.img" class="icon" mode="widthFix"></image> |
| | | <view class="text">{{item.title}}</view> |
| | | <u-icon name="arrow-right" size="12" color="#C9C9C9"></u-icon> |
| | | </navigator> |
| | | </view> |
| | | </view> |
| | | <!-- 公告 end --> |
| | | |
| | | <!-- 服务按钮 start --> |
| | | <view class="service-box"> |
| | | <u-grid :col="4" :border="false"> |
| | | <u-grid-item bg-color="transparent" v-for="(item, index) in serviceButton" :key="index"> |
| | | <navigator url="" hover-class="none" class="service-item"> |
| | | <image :src="item.img" mode="widthFix" class="img"></image> |
| | | <view class="name">{{ item.name }}</view> |
| | | </navigator> |
| | | </u-grid-item> |
| | | <u-grid-item bg-color="transparent" key="8"> |
| | | <navigator url="/pages/service/service" hover-class="none" class="service-item"> |
| | | <image src="/static/images/home/s8.png" mode="widthFix" class="img"></image> |
| | | <view class="name">更多</view> |
| | | </navigator> |
| | | </u-grid-item> |
| | | </u-grid> |
| | | </view> |
| | | <!-- 服务按钮 end --> |
| | | |
| | | <!-- 新闻模块 start --> |
| | | <view class="news"> |
| | | <navigator hover-class="none" url="/pages/news/list" class="cell"> |
| | | <view class="ctitle">新闻</view> |
| | | <view class="more"> |
| | | 更多 |
| | | <u-icon name="arrow-right" color="#A6ABB5" size="16"></u-icon> |
| | | </view> |
| | | </navigator> |
| | | |
| | | <view class="news-list"> |
| | | <navigator url="/pages/news/detail" hover-class="none" class="news-item" |
| | | v-for="(item, index) in newsList" :key="index"> |
| | | <view class="left"> |
| | | <view class="info"> |
| | | [中国空间站] 神舟十二号载人发射任务取得圆满成功 |
| | | </view> |
| | | <view class="date"> |
| | | <image src="/static/images/home/date.png" class="icon" mode=""></image> |
| | | 2021.06.17 |
| | | </view> |
| | | </view> |
| | | <image src="" mode="" class="img"></image> |
| | | </navigator> |
| | | <u-loadmore :status="status" /> |
| | | </view> |
| | | <!-- 新闻模块 end --> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | fakePosition, |
| | | fakeBannerList, |
| | | fakeNoticeList, |
| | | fakeNavButton, |
| | | fakeServiceButton |
| | | } from "@/api/mock/home.js"; |
| | | export default { |
| | | data() { |
| | | return { |
| | | position: '', |
| | | positionArr: [], |
| | | status: 'loadmore', |
| | | list: 15, |
| | | page: 0, |
| | | keyword: '', |
| | | focus: false, |
| | | bannerList: [], |
| | | noticeList: [], |
| | | newsList: [{}, {}], |
| | | navButton: [], |
| | | serviceButton: [] |
| | | }; |
| | | }, |
| | | onLoad() { |
| | | // 后续将改为与后端联动 |
| | | // 加载banner数据 |
| | | fakePosition().then(data => { |
| | | this.position = data.position; |
| | | this.positionArr = data.positionArr; |
| | | }); |
| | | // 加载banner数据 |
| | | fakeBannerList().then(data => { |
| | | this.bannerList = data; |
| | | }); |
| | | // 加载通知公告数据 |
| | | fakeNoticeList().then(data => { |
| | | this.noticeList = data; |
| | | }); |
| | | // 加载顶部按钮数据 |
| | | fakeNavButton().then(data => { |
| | | this.navButton = data; |
| | | }); |
| | | // 加载服务按钮数据 |
| | | fakeServiceButton().then(data => { |
| | | this.serviceButton = data; |
| | | }); |
| | | }, |
| | | onReachBottom() { |
| | | // 后续将改为与后端联动 |
| | | if (this.page >= 3) return; |
| | | this.status = 'loading'; |
| | | this.page = ++this.page; |
| | | setTimeout(() => { |
| | | this.list += 10; |
| | | if (this.page >= 3) this.status = 'nomore'; |
| | | else this.status = 'loading'; |
| | | this.newsList.push(...[{}, {}]); |
| | | }, 2000); |
| | | }, |
| | | methods: { |
| | | changePicker(e) { |
| | | console.log(this.pickerArr[e.detail.value].name); |
| | | this.position = this.pickerArr[e.detail.value].name; |
| | | }, |
| | | handleFocus() { |
| | | this.focus = !this.focus; |
| | | }, |
| | | handleSearchBlur() { |
| | | this.focus = false; |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .container { |
| | | background-color: #f7f7f7; |
| | | min-height: 100vh; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .head { |
| | | position: relative; |
| | | top: 0; |
| | | left: 0; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .head-bg { |
| | | position: absolute; |
| | | left: 0px; |
| | | top: 0px; |
| | | z-index: -1; |
| | | width: 750rpx; |
| | | height: 270rpx; |
| | | background: #0bb9c8; |
| | | } |
| | | |
| | | .nav-wrap { |
| | | width: 100%; |
| | | padding: 0 22rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .picker-box { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-start; |
| | | font-size: 32rpx; |
| | | font-family: Microsoft YaHei; |
| | | font-weight: bold; |
| | | color: #ffffff; |
| | | |
| | | .arrow { |
| | | margin-left: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .rider { |
| | | flex-shrink: 0; |
| | | width: 131rpx; |
| | | height: auto; |
| | | } |
| | | |
| | | .search-input { |
| | | width: 300rpx; |
| | | height: 50rpx; |
| | | } |
| | | |
| | | .tool { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | |
| | | .icon { |
| | | height: auto; |
| | | } |
| | | |
| | | .search-icon { |
| | | width: 40rpx; |
| | | margin-right: 34rpx; |
| | | } |
| | | |
| | | .message-icon { |
| | | width: 32rpx; |
| | | margin-right: 27rpx; |
| | | } |
| | | |
| | | .qr-icon { |
| | | width: 37rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .swiper-box { |
| | | margin: 50rpx auto 0; |
| | | width: 710rpx; |
| | | height: 253rpx; |
| | | |
| | | .swiper-item { |
| | | width: 100%; |
| | | height: 100%; |
| | | |
| | | .banner { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .nav { |
| | | margin: 0rpx 0; |
| | | box-sizing: border-box; |
| | | padding: 0 10rpx; |
| | | |
| | | &-item { |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | height: 130rpx; |
| | | |
| | | &-img { |
| | | width: 80rpx; |
| | | height: 80rpx; |
| | | } |
| | | |
| | | &-name { |
| | | font-size: 26rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: 400; |
| | | color: #585b61; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .notice-box { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #ffffff; |
| | | border-radius: 20px 20px 20px 20px; |
| | | margin: 0 20rpx; |
| | | padding: 30rpx 20rpx; |
| | | |
| | | .img { |
| | | width: 75rpx; |
| | | height: auto; |
| | | margin-right: 36rpx; |
| | | margin-left: 10rpx; |
| | | } |
| | | |
| | | .notice-info { |
| | | flex: 1; |
| | | |
| | | .notice-cell:first-of-type { |
| | | margin-bottom: 15rpx; |
| | | } |
| | | } |
| | | |
| | | .notice-cell { |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 24rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: 500; |
| | | color: #585b61; |
| | | |
| | | .icon { |
| | | width: 63rpx; |
| | | margin-right: 18rpx; |
| | | } |
| | | |
| | | .text { |
| | | flex: 1; |
| | | min-width: 0; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | display: -webkit-box; |
| | | -webkit-line-clamp: 1; |
| | | -webkit-box-orient: vertical; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .service-box { |
| | | background: #ffffff; |
| | | border-radius: 20px 20px 20px 20px; |
| | | margin: 30rpx 20rpx 0; |
| | | padding: 0rpx 20rpx; |
| | | |
| | | .service-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | width: 100%; |
| | | height: 120rpx; |
| | | font-size: 26rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: 500; |
| | | color: #585b61; |
| | | |
| | | .img { |
| | | width: 70rpx; |
| | | height: auto; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .news { |
| | | background: #ffffff; |
| | | border-radius: 20px 20px 20px 20px; |
| | | margin: 30rpx 20rpx 60rpx; |
| | | padding: 30rpx 20rpx; |
| | | |
| | | .cell { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .ctitle { |
| | | font-size: 32rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: bold; |
| | | color: #585b61; |
| | | } |
| | | |
| | | .more { |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 26rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: 500; |
| | | color: #a6abb5; |
| | | } |
| | | } |
| | | |
| | | .news-list { |
| | | margin-top: 30rpx; |
| | | |
| | | .news-item { |
| | | &:not(:last-of-type) { |
| | | padding: 0 0 30rpx; |
| | | margin-bottom: 30rpx; |
| | | border-bottom: 1px solid #eeeeee; |
| | | } |
| | | |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | |
| | | .left { |
| | | flex: 1; |
| | | height: 160rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-around; |
| | | |
| | | .info { |
| | | min-width: 0; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | display: -webkit-box; |
| | | -webkit-line-clamp: 2; |
| | | -webkit-box-orient: vertical; |
| | | |
| | | font-size: 28rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: 500; |
| | | color: #585b61; |
| | | line-height: 36rpx; |
| | | } |
| | | |
| | | .date { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .icon { |
| | | width: 21rpx; |
| | | height: 21rpx; |
| | | margin-right: 9rpx; |
| | | } |
| | | |
| | | font-size: 26rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: 500; |
| | | color: #a6abb5; |
| | | } |
| | | } |
| | | |
| | | .img { |
| | | flex-shrink: 0; |
| | | width: 252rpx; |
| | | height: 160rpx; |
| | | border-radius: 20rpx; |
| | | background-color: #82848a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <u-navbar :is-fixed="false" :border-bottom="false" :is-back="false" back-icon-name="arrow-leftward" title="登录" |
| | | :background="{ background: '#fff' }" title-color="#000000"> |
| | | <view class="" slot="right"> |
| | | <image src="/static/images/user/c8.png" class="set-icon" mode="widthFix"></image> |
| | | </view> |
| | | </u-navbar> |
| | | |
| | | <view class="content"> |
| | | <view class="top"> |
| | | <image src="/static/images/logo.png" class="logo" mode="widthFix"></image> |
| | | <view class="cell"> |
| | | <view class="name">账号</view> |
| | | <view class="input-box"> |
| | | <input type="text" v-model="username" placeholder="请输入账号" class="ipt" placeholder-class="hold" |
| | | @blur="handleInputCheck" /> |
| | | </view> |
| | | </view> |
| | | <view class="cell"> |
| | | <view class="name">密码</view> |
| | | <view class="input-box"> |
| | | <input type="password" v-model="password" placeholder="请输入密码" class="ipt" |
| | | placeholder-class="hold" @blur="handleInputCheck" /> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="agree"> |
| | | 登录即代表同意 |
| | | <text class="a">《用户协议》</text> |
| | | 和 |
| | | <text class="a">《隐私政策》</text> |
| | | </view> --> |
| | | <button class="submit" @click="submit" :disabled="disabled">登录</button> |
| | | <view class="tip">未注册用户验证后将自动注册并登录</view> |
| | | <navigator url="/pages/login/login-phone" hover-class="none" class="change">手机登录 ></navigator> |
| | | </view> |
| | | |
| | | <!-- 社交账号登录 --> |
| | | <view class="bottom"> |
| | | <view class="tag">社交账号登录</view> |
| | | <view class="chat-arr"> |
| | | <image src="/static/images/wx.png" class="icon" mode=""></image> |
| | | <image src="/static/images/qq.png" class="icon" mode=""></image> |
| | | <image src="/static/images/wb.png" class="icon" mode=""></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- --> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import md5 from '@/utils/md5.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | tenantId: '000000', |
| | | username: '', |
| | | password: '', |
| | | disabled: true |
| | | }; |
| | | }, |
| | | methods: { |
| | | submit() { |
| | | this.$u.api.token(this.tenantId, this.username, md5(this.password)).then(data => { |
| | | this.$u.func.login(data) |
| | | }).catch(err => { |
| | | console.log(err) |
| | | this.$u.func.showToast({ |
| | | title: '用户名或密码错误', |
| | | }) |
| | | }) |
| | | }, |
| | | handleInputCheck() { |
| | | this.disabled = false |
| | | }, |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .container { |
| | | min-height: 100vh; |
| | | overflow: hidden; |
| | | |
| | | .set-icon { |
| | | vertical-align: middle; |
| | | width: 41rpx; |
| | | height: auto; |
| | | margin-right: 35rpx; |
| | | } |
| | | } |
| | | |
| | | .content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | |
| | | height: 90vh; |
| | | width: 100%; |
| | | |
| | | .top { |
| | | width: 100%; |
| | | } |
| | | |
| | | .logo { |
| | | display: block; |
| | | width: 281rpx; |
| | | height: auto; |
| | | margin: 0 auto 120rpx; |
| | | } |
| | | |
| | | .cell { |
| | | width: 100%; |
| | | padding: 0 85rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 36rpx; |
| | | |
| | | .name { |
| | | font-size: 22rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 400; |
| | | color: #3e4a59; |
| | | line-height: 30rpx; |
| | | opacity: 0.72; |
| | | } |
| | | |
| | | .input-box { |
| | | padding: 30rpx 0; |
| | | border-bottom: 2rpx solid #f6f6f6; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .code { |
| | | font-size: 22rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 400; |
| | | color: #0d0d0d; |
| | | line-height: 30rpx; |
| | | |
| | | text { |
| | | color: #14b9c8; |
| | | } |
| | | } |
| | | |
| | | .ipt { |
| | | flex: 1; |
| | | // height: 24rpx; |
| | | font-size: 24rpx; |
| | | } |
| | | |
| | | .hold { |
| | | font-size: 26rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 400; |
| | | color: #3e4a59; |
| | | line-height: 30px; |
| | | opacity: 0.45; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .agree { |
| | | margin: 27rpx 95rpx 0; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #cacaca; |
| | | line-height: 34rpx; |
| | | |
| | | .a { |
| | | color: #000000; |
| | | } |
| | | } |
| | | |
| | | .submit { |
| | | margin: 60rpx 90rpx 0; |
| | | border: none; |
| | | width: 572rpx; |
| | | height: 86rpx; |
| | | line-height: 86rpx; |
| | | box-sizing: border-box; |
| | | border-radius: 15rpx; |
| | | background-color: #14b9c8; |
| | | color: #ffffff; |
| | | |
| | | &::after { |
| | | content: none; |
| | | } |
| | | |
| | | &::before { |
| | | content: none; |
| | | } |
| | | |
| | | &[disabled='true'] { |
| | | background: #e4e4e4; |
| | | font-size: 36rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 500; |
| | | color: #ffffff; |
| | | } |
| | | } |
| | | |
| | | .tip { |
| | | margin-top: 30rpx; |
| | | text-align: center; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #cacaca; |
| | | line-height: 34rpx; |
| | | } |
| | | |
| | | .change { |
| | | margin-top: 20rpx; |
| | | text-align: center; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #14b9c8; |
| | | line-height: 34rpx; |
| | | } |
| | | |
| | | .tag { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #9f9f9f; |
| | | line-height: 34rpx; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | display: block; |
| | | width: 160rpx; |
| | | height: 1px; |
| | | background: #d8d8d8; |
| | | opacity: 0.86; |
| | | } |
| | | |
| | | &::after { |
| | | content: ''; |
| | | display: block; |
| | | width: 160rpx; |
| | | height: 1px; |
| | | background: #d8d8d8; |
| | | opacity: 0.86; |
| | | } |
| | | } |
| | | |
| | | .chat-arr { |
| | | margin-top: 50rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | |
| | | .icon { |
| | | width: 73rpx; |
| | | height: 73rpx; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <u-navbar :is-fixed="false" :border-bottom="false" :is-back="false" back-icon-name="arrow-leftward" title="登录" |
| | | :background="{ background: '#fff' }" title-color="#000000"> |
| | | <view class="" slot="right"> |
| | | <image src="/static/images/user/c8.png" class="set-icon" mode="widthFix"></image> |
| | | </view> |
| | | </u-navbar> |
| | | |
| | | <view class="content"> |
| | | <view class="top"> |
| | | <image src="/static/images/logo.png" class="logo" mode="widthFix"></image> |
| | | <view class="cell"> |
| | | <view class="name">手机号</view> |
| | | <view class="input-box"> |
| | | <input type="number" v-model="phone" placeholder="请输入手机号码" class="ipt" placeholder-class="hold" |
| | | @blur="handleInputCheck" /> |
| | | </view> |
| | | </view> |
| | | <view class="cell"> |
| | | <view class="name">短信验证码</view> |
| | | <view class="input-box"> |
| | | <input type="number" v-model="code" placeholder="请输入验证码" class="ipt" placeholder-class="hold" |
| | | @blur="handleInputCheck" /> |
| | | <view class="code" @click="sendCode" v-if="count === 60">获取验证码</view> |
| | | <view class="code" v-else><text>{{ count }}</text>秒重新获取</view> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="agree"> |
| | | 登录即代表同意 |
| | | <text class="a">《用户协议》</text> |
| | | 和 |
| | | <text class="a">《隐私政策》</text> |
| | | </view> --> |
| | | <button class="submit" @click="submit" :disabled="disabled">登录</button> |
| | | <view class="tip">未注册用户验证后将自动注册并登录</view> |
| | | <navigator url="/pages/login/login-account" hover-class="none" class="change">密码登录 ></navigator> |
| | | </view> |
| | | |
| | | <!-- 社交账号登录 --> |
| | | <view class="bottom"> |
| | | <view class="tag">社交账号登录</view> |
| | | <view class="chat-arr"> |
| | | <image src="/static/images/wx.png" class="icon" mode=""></image> |
| | | <image src="/static/images/qq.png" class="icon" mode=""></image> |
| | | <image src="/static/images/wb.png" class="icon" mode=""></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- --> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | data() { |
| | | return { |
| | | phone: '', |
| | | code: '', |
| | | disabled: true, |
| | | count: 60, |
| | | timer: '' |
| | | }; |
| | | }, |
| | | methods: { |
| | | submit() { |
| | | this.$u.func.showToast({ |
| | | title: '新版本即将到来', |
| | | }) |
| | | }, |
| | | handleInputCheck() { |
| | | if (!this.phone && !/^1\d{10}$/.test(this.phone)) { |
| | | this.disabled = true |
| | | return uni.showToast({ |
| | | title: '请输入正确的手机号', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | if (!this.code) { |
| | | this.disabled = true |
| | | return uni.showToast({ |
| | | title: '请输入验证码', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | this.disabled = false |
| | | }, |
| | | async sendCode() { |
| | | this.count = this.count - 1; |
| | | this.timer = setInterval(() => { |
| | | if (this.count == 0) { |
| | | clearInterval(this.timer); |
| | | this.count = 60; |
| | | return; |
| | | } |
| | | this.count = this.count - 1; |
| | | }, 1000); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .container { |
| | | min-height: 100vh; |
| | | overflow: hidden; |
| | | |
| | | .set-icon { |
| | | vertical-align: middle; |
| | | width: 41rpx; |
| | | height: auto; |
| | | margin-right: 35rpx; |
| | | } |
| | | } |
| | | |
| | | .content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | |
| | | height: 90vh; |
| | | width: 100%; |
| | | |
| | | .top { |
| | | width: 100%; |
| | | } |
| | | |
| | | .logo { |
| | | display: block; |
| | | width: 281rpx; |
| | | height: auto; |
| | | margin: 0 auto 120rpx; |
| | | } |
| | | |
| | | .cell { |
| | | width: 100%; |
| | | padding: 0 85rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 36rpx; |
| | | |
| | | .name { |
| | | font-size: 22rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 400; |
| | | color: #3e4a59; |
| | | line-height: 30rpx; |
| | | opacity: 0.72; |
| | | } |
| | | |
| | | .input-box { |
| | | padding: 30rpx 0; |
| | | border-bottom: 2rpx solid #f6f6f6; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .code { |
| | | font-size: 22rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 400; |
| | | color: #0d0d0d; |
| | | line-height: 30rpx; |
| | | |
| | | text { |
| | | color: #14b9c8; |
| | | } |
| | | } |
| | | |
| | | .ipt { |
| | | flex: 1; |
| | | // height: 24rpx; |
| | | font-size: 24rpx; |
| | | } |
| | | |
| | | .hold { |
| | | font-size: 26rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 400; |
| | | color: #3e4a59; |
| | | line-height: 30px; |
| | | opacity: 0.45; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .agree { |
| | | margin: 27rpx 95rpx 0; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #cacaca; |
| | | line-height: 34rpx; |
| | | |
| | | .a { |
| | | color: #000000; |
| | | } |
| | | } |
| | | |
| | | .submit { |
| | | margin: 60rpx 90rpx 0; |
| | | border: none; |
| | | width: 572rpx; |
| | | height: 86rpx; |
| | | line-height: 86rpx; |
| | | box-sizing: border-box; |
| | | border-radius: 15rpx; |
| | | background-color: #14b9c8; |
| | | color: #ffffff; |
| | | |
| | | &::after { |
| | | content: none; |
| | | } |
| | | |
| | | &::before { |
| | | content: none; |
| | | } |
| | | |
| | | &[disabled='true'] { |
| | | background: #e4e4e4; |
| | | font-size: 36rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 500; |
| | | color: #ffffff; |
| | | } |
| | | } |
| | | |
| | | .tip { |
| | | margin-top: 30rpx; |
| | | text-align: center; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #cacaca; |
| | | line-height: 34rpx; |
| | | } |
| | | |
| | | .change { |
| | | margin-top: 20rpx; |
| | | text-align: center; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #14b9c8; |
| | | line-height: 34rpx; |
| | | } |
| | | |
| | | .tag { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | font-size: 22rpx; |
| | | font-family: Adobe Heiti Std; |
| | | font-weight: normal; |
| | | color: #9f9f9f; |
| | | line-height: 34rpx; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | display: block; |
| | | width: 160rpx; |
| | | height: 1px; |
| | | background: #d8d8d8; |
| | | opacity: 0.86; |
| | | } |
| | | |
| | | &::after { |
| | | content: ''; |
| | | display: block; |
| | | width: 160rpx; |
| | | height: 1px; |
| | | background: #d8d8d8; |
| | | opacity: 0.86; |
| | | } |
| | | } |
| | | |
| | | .chat-arr { |
| | | margin-top: 50rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | |
| | | .icon { |
| | | width: 73rpx; |
| | | height: 73rpx; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <view class="head"> |
| | | <u-navbar :is-fixed="false" :border-bottom="false" :is-back="false" back-icon-name="arrow-leftward" |
| | | back-icon-color="#fff" back-icon-size="35" :background="{ background: '#0BB9C8' }" title="工作台" |
| | | title-color="#fff"></u-navbar> |
| | | <view class="head-bg"></view> |
| | | <!-- 服务 start --> |
| | | <view class="card sub"> |
| | | <!-- <view class="title">服务</view> --> |
| | | <view class="list"> |
| | | <u-grid :col="4" :border="false"> |
| | | <u-grid-item bg-color="transparent" v-for="(item, index) in subscribeList" :key="index"> |
| | | <navigator :url="item.url" hover-class="none" class="nav-item"> |
| | | <image :src="item.img" mode="widthFix" class="nav-item-img"></image> |
| | | <view class="nav-item-name">{{ item.name }}</view> |
| | | </navigator> |
| | | </u-grid-item> |
| | | </u-grid> |
| | | </view> |
| | | </view> |
| | | <!-- 服务 end --> |
| | | |
| | | |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | data() { |
| | | return { |
| | | // 我的订阅 |
| | | subscribeList: [{ |
| | | img: '/static/images/service/b4.png', |
| | | name: '巡检任务', |
| | | url: '../taskinfo/list' |
| | | }, |
| | | { |
| | | img: '/static/images/service/b5.png', |
| | | name: '维修任务', |
| | | url: '' |
| | | } |
| | | ], |
| | | // 服务列表 |
| | | serviceList: [], |
| | | }; |
| | | }, |
| | | onLoad() { |
| | | |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .container { |
| | | background-color: #f7f7f7; |
| | | min-height: 100vh; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .head { |
| | | position: relative; |
| | | top: 0; |
| | | left: 0; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .head-bg { |
| | | position: absolute; |
| | | left: 0px; |
| | | top: 0px; |
| | | z-index: -1; |
| | | width: 750rpx; |
| | | height: 270rpx; |
| | | background: #0bb9c8; |
| | | } |
| | | |
| | | .card { |
| | | margin: 30rpx; |
| | | background: #ffffff; |
| | | border-radius: 20rpx; |
| | | margin-top: 36rpx; |
| | | } |
| | | |
| | | .sub { |
| | | .title { |
| | | padding-top: 36rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | |
| | | font-size: 30rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 500; |
| | | color: #000000; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | display: block; |
| | | width: 135rpx; |
| | | height: 1rpx; |
| | | background: #e4e7ed; |
| | | margin: 0 17rpx; |
| | | } |
| | | |
| | | &::after { |
| | | content: ''; |
| | | display: block; |
| | | width: 135rpx; |
| | | height: 1rpx; |
| | | background: #e4e7ed; |
| | | margin: 0 17rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | padding: 0 0 30rpx; |
| | | |
| | | .nav-item { |
| | | width: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-size: 30rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 400; |
| | | color: #131313; |
| | | line-height: 48rpx; |
| | | opacity: 0.77; |
| | | |
| | | &-img { |
| | | width: 54rpx; |
| | | height: 54rpx; |
| | | margin-bottom: 30rpx; |
| | | } |
| | | |
| | | &-name { |
| | | font-size: 26rpx; |
| | | font-family: PingFang SC; |
| | | font-weight: 400; |
| | | color: #585b61; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .wrap { |
| | | .title { |
| | | padding: 36rpx 16rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 32rpx; |
| | | font-family: Source Han Sans CN; |
| | | font-weight: 500; |
| | | color: #000000; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | display: block; |
| | | width: 5rpx; |
| | | height: 36rpx; |
| | | margin-right: 10rpx; |
| | | background: #3ac4d1; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <u-top-tips ref="uTips"></u-top-tips> |
| | | <view class="tabs"> |
| | | <u-tabs-swiper ref="uTabs" :list="tabsList" :current="current" :is-scroll="false" swiperWidth="750" |
| | | @change="tabsChange"> |
| | | </u-tabs-swiper> |
| | | </view> |
| | | <view class="swiper-item"> |
| | | <scroll-view scroll-y :style="{ height: useHeight, width: '100%' }" @scrolltolower="scrolltolower"> |
| | | <view class="onveMain"> |
| | | <view class="once" v-for="(item, index) in taskinfoList" :key="index"> |
| | | <view class="onve-left"> |
| | | <!-- <view class="o-l-img"> |
| | | <image src="" mode=""></image> |
| | | </view> --> |
| | | <view class="o-l-main"> |
| | | <view class="o-l-m-up"> |
| | | {{item.title}} |
| | | </view> |
| | | <view class="o-l-m-center"> |
| | | <u-icon name="order" color="#d0d0d0" size="28"></u-icon> |
| | | 状态:{{item.state == 0?'待开始':item.state == 1?'正在进行':'已完成'}} |
| | | </view> |
| | | <view class="o-l-m-center"> |
| | | <u-icon name="volume" color="#d0d0d0" size="28"></u-icon> |
| | | 时间:{{item.startTime}} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="onve-right"> |
| | | <u-button class='greens' size="mini" v-if="current == 0">开始任务</u-button> |
| | | <u-button class='greens' size="mini" v-if="current == 1">完成任务</u-button> |
| | | <u-button class='o-r-down' plain type="error" size="mini" v-if="current == 1">事件上报 |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | <u-divider>没有更多了</u-divider> |
| | | </scroll-view> |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | getList |
| | | } from "@/api/taskinfo/list.js"; |
| | | export default { |
| | | data() { |
| | | return { |
| | | useHeight: "87vh", |
| | | current: 0, // tabs组件的current值,表示当前活动的tab选项 |
| | | tabsList: [{ |
| | | id: 0, |
| | | name: "待开始", |
| | | }, { |
| | | id: 1, |
| | | name: "正在进行", |
| | | }, { |
| | | id: 2, |
| | | name: "已完成", |
| | | }], |
| | | page: { |
| | | pageSize: 6, |
| | | currentPage: 1, |
| | | total: 0 |
| | | }, |
| | | query: { |
| | | state: 0, |
| | | toUserId: this.$store.state.userInfo.user_id //获取当前登录用户id |
| | | }, |
| | | taskinfoList: null |
| | | } |
| | | }, |
| | | created() { |
| | | this.getList() |
| | | }, |
| | | methods: { |
| | | // tabs通知swiper切换 |
| | | tabsChange(index) { |
| | | this.current = index; |
| | | this.query.state = this.current; |
| | | this.page.currentPage = 1 |
| | | this.getList(); |
| | | }, |
| | | getList() { |
| | | // 获取任务列表API |
| | | getList(this.page.currentPage, this.page.pageSize, this.query).then(res => { |
| | | const data = res.data; |
| | | this.page.total = data.total; |
| | | this.taskinfoList = data.records; |
| | | }) |
| | | }, |
| | | // 下一页任务 |
| | | scrolltolower() { |
| | | this.page.currentPage += 1 |
| | | // 获取任务列表API |
| | | getList(this.page.currentPage, this.page.pageSize, this.query).then(res => { |
| | | const data = res.data; |
| | | this.page.total = data.total; |
| | | this.taskinfoList = this.taskinfoList.concat(data.records) |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | page { |
| | | height: 100% !important; |
| | | width: 100% !important; |
| | | overflow: hidden !important; |
| | | } |
| | | |
| | | .swiper-item { |
| | | flex: 1; |
| | | } |
| | | |
| | | .onveMain { |
| | | width: 100%; |
| | | height: auto; |
| | | } |
| | | |
| | | .once { |
| | | width: calc(100% - 10px); |
| | | height: 100px; |
| | | border: 1px solid #d0d0d0; |
| | | padding: 8px; |
| | | box-sizing: border-box; |
| | | margin: 5px; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .onve-left { |
| | | width: 75%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .o-l-img { |
| | | width: 80px; |
| | | height: 80px; |
| | | background-color: #d0d0d0; |
| | | margin-right: 5px; |
| | | border-radius: 7px; |
| | | |
| | | image { |
| | | width: 100%; |
| | | height: 100%; |
| | | border-radius: 7px; |
| | | } |
| | | } |
| | | |
| | | .o-l-main { |
| | | width: 100%; |
| | | height: 80px; |
| | | display: flex; |
| | | // align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | |
| | | .o-l-m-up { |
| | | height: 30px; |
| | | font-size: 16px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .onve-right { |
| | | width: 25%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | |
| | | .o-r-down { |
| | | margin-top: 4px; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | .orange { |
| | | color: #FF7B15; |
| | | } |
| | | |
| | | .green { |
| | | color: #00e713; |
| | | } |
| | | |
| | | .greens { |
| | | color: #18a655 !important; |
| | | } |
| | | |
| | | .blues { |
| | | color: #2680F0 !important; |
| | | } |
| | | |
| | | .Cation { |
| | | display: inline-block; |
| | | position: absolute; |
| | | left: -35px; |
| | | |
| | | &::after { |
| | | content: ""; |
| | | } |
| | | } |
| | | |
| | | .notCation { |
| | | color: #18a655; |
| | | } |
| | | </style> |
| New file |
| | |
| | | 8fe8a906a5a5cc31c8aee6953db98ab275def06d |
| New file |
| | |
| | | @import '~@/uni.scss'; |
| | | |
| | | /* start--演示页面使用的统一样式--start */ |
| | | .u-demo { |
| | | padding: 25px 20px; |
| | | } |
| | | |
| | | .u-demo-wrap { |
| | | border-width: 1px; |
| | | border-color: #ddd; |
| | | border-style: dashed; |
| | | background-color: rgb(250, 250, 250); |
| | | padding: 20px 10px; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .u-demo-area { |
| | | text-align: center; |
| | | } |
| | | |
| | | .u-no-demo-here { |
| | | color: $u-tips-color; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .u-demo-result-line { |
| | | border-width: 1px; |
| | | border-color: #ddd; |
| | | border-style: dashed; |
| | | padding: 5px 20px; |
| | | margin-top: 30px; |
| | | border-radius: 5px; |
| | | background-color: rgb(240, 240, 240); |
| | | color: $u-content-color; |
| | | font-size: 16px; |
| | | /* #ifndef APP-NVUE */ |
| | | word-break: break-word; |
| | | display: inline-block; |
| | | /* #endif */ |
| | | text-align: left; |
| | | |
| | | } |
| | | |
| | | .u-demo-title, |
| | | .u-config-title { |
| | | text-align: center; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .u-config-item { |
| | | margin-top: 25px; |
| | | } |
| | | |
| | | .u-config-title { |
| | | margin-top: 20px; |
| | | padding-bottom: 5px; |
| | | } |
| | | |
| | | .u-item-title { |
| | | position: relative; |
| | | font-size: 15px; |
| | | padding-left: 8px; |
| | | line-height: 1; |
| | | margin-bottom: 11px; |
| | | } |
| | | |
| | | .u-item-title:after { |
| | | position: absolute; |
| | | width: 4px; |
| | | top: -1px; |
| | | height: 16px; |
| | | /* #ifndef APP-NVUE */ |
| | | content: ''; |
| | | /* #endif */ |
| | | left: 0; |
| | | border-radius: 10px; |
| | | background-color: $u-content-color; |
| | | } |
| | | /* end--演示页面使用的统一样式--end */ |
| New file |
| | |
| | | // $u.mixin.js |
| | | |
| | | import { |
| | | mapState |
| | | } from 'vuex' |
| | | import store from "@/store" |
| | | |
| | | // 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中 |
| | | let $uStoreKey = []; |
| | | try { |
| | | $uStoreKey = store.state ? Object.keys(store.state) : []; |
| | | } catch (e) { |
| | | |
| | | } |
| | | |
| | | module.exports = { |
| | | mounted() { |
| | | // 将vuex方法挂在到$u中 |
| | | // 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗') |
| | | // 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1') |
| | | this.$u.vuex = (name, value) => { |
| | | this.$store.commit('$uStore', { |
| | | name, |
| | | value |
| | | }) |
| | | } |
| | | }, |
| | | computed: { |
| | | // 将vuex的state中的所有变量,解构到全局混入的mixin中 |
| | | ...mapState($uStoreKey) |
| | | } |
| | | } |
| New file |
| | |
| | | import Vue from 'vue' |
| | | import Vuex from 'vuex' |
| | | Vue.use(Vuex) |
| | | |
| | | let lifeData = {}; |
| | | |
| | | try { |
| | | // 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的 |
| | | lifeData = uni.getStorageSync('lifeData'); |
| | | } catch (e) { |
| | | |
| | | } |
| | | |
| | | // 需要永久存储,且下次APP启动需要取出的,在state中的变量名 |
| | | let saveStateKeys = ['userInfo', 'accessToken', 'isLogin']; |
| | | |
| | | // 保存变量到本地存储中 |
| | | const saveLifeData = function(key, value) { |
| | | // 判断变量名是否在需要存储的数组中 |
| | | if (saveStateKeys.indexOf(key) != -1) { |
| | | // 获取本地存储的lifeData对象,将变量添加到对象中 |
| | | let tmp = uni.getStorageSync('lifeData'); |
| | | // 第一次打开APP,不存在lifeData变量,故放一个{}空对象 |
| | | tmp = tmp ? tmp : {}; |
| | | tmp[key] = value; |
| | | // 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中 |
| | | uni.setStorageSync(key, value); |
| | | uni.setStorageSync('lifeData', tmp); |
| | | } |
| | | } |
| | | const store = new Vuex.Store({ |
| | | // 下面这些值仅为示例,使用过程中请删除 |
| | | state: { |
| | | // 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量 |
| | | userInfo: lifeData.userInfo ? lifeData.userInfo : { |
| | | avatar: '', |
| | | nick_name: '游客', |
| | | tenant_id: '暂无' |
| | | }, |
| | | accessToken: lifeData.accessToken ? lifeData.accessToken : '', |
| | | isLogin: lifeData.isLogin ? lifeData.isLogin : false, |
| | | // 如果version无需保存到本地永久存储,无需lifeData.version方式 |
| | | version: '1.0.0', |
| | | }, |
| | | mutations: { |
| | | $uStore(state, payload) { |
| | | // 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1 |
| | | let nameArr = payload.name.split('.'); |
| | | let saveKey = ''; |
| | | let len = nameArr.length; |
| | | if (nameArr.length >= 2) { |
| | | let obj = state[nameArr[0]]; |
| | | for (let i = 1; i < len - 1; i++) { |
| | | obj = obj[nameArr[i]]; |
| | | } |
| | | obj[nameArr[len - 1]] = payload.value; |
| | | saveKey = nameArr[0]; |
| | | } else { |
| | | // 单层级变量,在state就是一个普通变量的情况 |
| | | state[payload.name] = payload.value; |
| | | saveKey = payload.name; |
| | | } |
| | | // 保存变量到本地,见顶部函数定义 |
| | | saveLifeData(saveKey, state[saveKey]) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | export default store |
| New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| | | <link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico"> |
| | | <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> |
| | | <title> |
| | | <%= htmlWebpackPlugin.options.title %> |
| | | </title> |
| | | <script> |
| | | document.addEventListener('DOMContentLoaded', function() { |
| | | document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px' |
| | | }) |
| | | </script> |
| | | <link rel="stylesheet" href="<%= BASE_URL %>static/index.css" /> |
| | | </head> |
| | | <body> |
| | | <noscript> |
| | | <strong>本站点必须要开启JavaScript才能运行</strong> |
| | | </noscript> |
| | | <div id="app"></div> |
| | | </body> |
| | | </html> |
| New file |
| | |
| | | /** |
| | | * 下方引入的为uView UI的集成样式文件,为scss预处理器,其中包含了一些"u-"开头的自定义变量 |
| | | * uView自定义的css类名和scss变量,均以"u-"开头,不会造成冲突,请放心使用 |
| | | */ |
| | | @import 'uview-ui/theme.scss'; |
| New file |
| | |
| | | /* |
| | | * base64.js |
| | | * |
| | | * Licensed under the BSD 3-Clause License. |
| | | * http://opensource.org/licenses/BSD-3-Clause |
| | | * |
| | | * References: |
| | | * http://en.wikipedia.org/wiki/Base64 |
| | | */ |
| | | ;(function (global, factory) { |
| | | typeof exports === 'object' && typeof module !== 'undefined' |
| | | ? module.exports = factory(global) |
| | | : typeof define === 'function' && define.amd |
| | | ? define(factory) : factory(global) |
| | | }(( |
| | | typeof self !== 'undefined' ? self |
| | | : typeof window !== 'undefined' ? window |
| | | : typeof global !== 'undefined' ? global |
| | | : this |
| | | ), function(global) { |
| | | 'use strict'; |
| | | // existing version for noConflict() |
| | | global = global || {}; |
| | | var _Base64 = global.Base64; |
| | | var version = "2.5.1"; |
| | | // if node.js and NOT React Native, we use Buffer |
| | | var buffer; |
| | | if (typeof module !== 'undefined' && module.exports) { |
| | | try { |
| | | buffer = eval("require('buffer').Buffer"); |
| | | } catch (err) { |
| | | buffer = undefined; |
| | | } |
| | | } |
| | | // constants |
| | | var b64chars |
| | | = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; |
| | | var b64tab = function(bin) { |
| | | var t = {}; |
| | | for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i; |
| | | return t; |
| | | }(b64chars); |
| | | var fromCharCode = String.fromCharCode; |
| | | // encoder stuff |
| | | var cb_utob = function(c) { |
| | | if (c.length < 2) { |
| | | var cc = c.charCodeAt(0); |
| | | return cc < 0x80 ? c |
| | | : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6)) |
| | | + fromCharCode(0x80 | (cc & 0x3f))) |
| | | : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) |
| | | + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) |
| | | + fromCharCode(0x80 | ( cc & 0x3f))); |
| | | } else { |
| | | var cc = 0x10000 |
| | | + (c.charCodeAt(0) - 0xD800) * 0x400 |
| | | + (c.charCodeAt(1) - 0xDC00); |
| | | return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) |
| | | + fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) |
| | | + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) |
| | | + fromCharCode(0x80 | ( cc & 0x3f))); |
| | | } |
| | | }; |
| | | var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; |
| | | var utob = function(u) { |
| | | return u.replace(re_utob, cb_utob); |
| | | }; |
| | | var cb_encode = function(ccc) { |
| | | var padlen = [0, 2, 1][ccc.length % 3], |
| | | ord = ccc.charCodeAt(0) << 16 |
| | | | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) |
| | | | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)), |
| | | chars = [ |
| | | b64chars.charAt( ord >>> 18), |
| | | b64chars.charAt((ord >>> 12) & 63), |
| | | padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), |
| | | padlen >= 1 ? '=' : b64chars.charAt(ord & 63) |
| | | ]; |
| | | return chars.join(''); |
| | | }; |
| | | var btoa = global.btoa ? function(b) { |
| | | return global.btoa(b); |
| | | } : function(b) { |
| | | return b.replace(/[\s\S]{1,3}/g, cb_encode); |
| | | }; |
| | | var _encode = buffer ? |
| | | buffer.from && Uint8Array && buffer.from !== Uint8Array.from |
| | | ? function (u) { |
| | | return (u.constructor === buffer.constructor ? u : buffer.from(u)) |
| | | .toString('base64') |
| | | } |
| | | : function (u) { |
| | | return (u.constructor === buffer.constructor ? u : new buffer(u)) |
| | | .toString('base64') |
| | | } |
| | | : function (u) { return btoa(utob(u)) } |
| | | ; |
| | | var encode = function(u, urisafe) { |
| | | return !urisafe |
| | | ? _encode(String(u)) |
| | | : _encode(String(u)).replace(/[+\/]/g, function(m0) { |
| | | return m0 == '+' ? '-' : '_'; |
| | | }).replace(/=/g, ''); |
| | | }; |
| | | var encodeURI = function(u) { return encode(u, true) }; |
| | | // decoder stuff |
| | | var re_btou = new RegExp([ |
| | | '[\xC0-\xDF][\x80-\xBF]', |
| | | '[\xE0-\xEF][\x80-\xBF]{2}', |
| | | '[\xF0-\xF7][\x80-\xBF]{3}' |
| | | ].join('|'), 'g'); |
| | | var cb_btou = function(cccc) { |
| | | switch(cccc.length) { |
| | | case 4: |
| | | var cp = ((0x07 & cccc.charCodeAt(0)) << 18) |
| | | | ((0x3f & cccc.charCodeAt(1)) << 12) |
| | | | ((0x3f & cccc.charCodeAt(2)) << 6) |
| | | | (0x3f & cccc.charCodeAt(3)), |
| | | offset = cp - 0x10000; |
| | | return (fromCharCode((offset >>> 10) + 0xD800) |
| | | + fromCharCode((offset & 0x3FF) + 0xDC00)); |
| | | case 3: |
| | | return fromCharCode( |
| | | ((0x0f & cccc.charCodeAt(0)) << 12) |
| | | | ((0x3f & cccc.charCodeAt(1)) << 6) |
| | | | (0x3f & cccc.charCodeAt(2)) |
| | | ); |
| | | default: |
| | | return fromCharCode( |
| | | ((0x1f & cccc.charCodeAt(0)) << 6) |
| | | | (0x3f & cccc.charCodeAt(1)) |
| | | ); |
| | | } |
| | | }; |
| | | var btou = function(b) { |
| | | return b.replace(re_btou, cb_btou); |
| | | }; |
| | | var cb_decode = function(cccc) { |
| | | var len = cccc.length, |
| | | padlen = len % 4, |
| | | n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) |
| | | | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) |
| | | | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) |
| | | | (len > 3 ? b64tab[cccc.charAt(3)] : 0), |
| | | chars = [ |
| | | fromCharCode( n >>> 16), |
| | | fromCharCode((n >>> 8) & 0xff), |
| | | fromCharCode( n & 0xff) |
| | | ]; |
| | | chars.length -= [0, 0, 2, 1][padlen]; |
| | | return chars.join(''); |
| | | }; |
| | | var _atob = global.atob ? function(a) { |
| | | return global.atob(a); |
| | | } : function(a){ |
| | | return a.replace(/\S{1,4}/g, cb_decode); |
| | | }; |
| | | var atob = function(a) { |
| | | return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, '')); |
| | | }; |
| | | var _decode = buffer ? |
| | | buffer.from && Uint8Array && buffer.from !== Uint8Array.from |
| | | ? function(a) { |
| | | return (a.constructor === buffer.constructor |
| | | ? a : buffer.from(a, 'base64')).toString(); |
| | | } |
| | | : function(a) { |
| | | return (a.constructor === buffer.constructor |
| | | ? a : new buffer(a, 'base64')).toString(); |
| | | } |
| | | : function(a) { return btou(_atob(a)) }; |
| | | var decode = function(a){ |
| | | return _decode( |
| | | String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' }) |
| | | .replace(/[^A-Za-z0-9\+\/]/g, '') |
| | | ); |
| | | }; |
| | | var noConflict = function() { |
| | | var Base64 = global.Base64; |
| | | global.Base64 = _Base64; |
| | | return Base64; |
| | | }; |
| | | // export Base64 |
| | | global.Base64 = { |
| | | VERSION: version, |
| | | atob: atob, |
| | | btoa: btoa, |
| | | fromBase64: decode, |
| | | toBase64: encode, |
| | | utob: utob, |
| | | encode: encode, |
| | | encodeURI: encodeURI, |
| | | btou: btou, |
| | | decode: decode, |
| | | noConflict: noConflict, |
| | | __buffer__: buffer |
| | | }; |
| | | // if ES5 is available, make Base64.extendString() available |
| | | if (typeof Object.defineProperty === 'function') { |
| | | var noEnum = function(v){ |
| | | return {value:v,enumerable:false,writable:true,configurable:true}; |
| | | }; |
| | | global.Base64.extendString = function () { |
| | | Object.defineProperty( |
| | | String.prototype, 'fromBase64', noEnum(function () { |
| | | return decode(this) |
| | | })); |
| | | Object.defineProperty( |
| | | String.prototype, 'toBase64', noEnum(function (urisafe) { |
| | | return encode(this, urisafe) |
| | | })); |
| | | Object.defineProperty( |
| | | String.prototype, 'toBase64URI', noEnum(function () { |
| | | return encode(this, true) |
| | | })); |
| | | }; |
| | | } |
| | | // |
| | | // export Base64 to the namespace |
| | | // |
| | | if (global['Meteor']) { // Meteor.js |
| | | Base64 = global.Base64; |
| | | } |
| | | // module.exports and AMD are mutually exclusive. |
| | | // module.exports has precedence. |
| | | if (typeof module !== 'undefined' && module.exports) { |
| | | module.exports.Base64 = global.Base64; |
| | | } |
| | | else if (typeof define === 'function' && define.amd) { |
| | | // AMD. Register as an anonymous module. |
| | | define([], function(){ return global.Base64 }); |
| | | } |
| | | // that's it! |
| | | return {Base64: global.Base64} |
| | | })); |
| New file |
| | |
| | | // 全局公共方法 |
| | | const install = (Vue, vm) => { |
| | | |
| | | // 登录操作 |
| | | const login = (userInfo) => { |
| | | vm.$u.vuex('userInfo', userInfo) |
| | | vm.$u.vuex('accessToken', userInfo.access_token) |
| | | vm.$u.vuex('isLogin', true) |
| | | uni.switchTab({ |
| | | url: '/pages/home/home' |
| | | }) |
| | | } |
| | | |
| | | // 退出登录 |
| | | const logout = () => { |
| | | vm.$u.vuex('userInfo', { |
| | | avatar: '', |
| | | nick_name: '游客', |
| | | tenant_id: '暂无' |
| | | }) |
| | | vm.$u.vuex('accessToken', '') |
| | | vm.$u.vuex('isLogin', false) |
| | | uni.redirectTo({ |
| | | url: '/pages/login/login-account' |
| | | }) |
| | | } |
| | | |
| | | // 检查登录状态 |
| | | const checkLogin = (e = {}) => { |
| | | if (!vm.isLogin) { |
| | | uni.navigateTo({ |
| | | url: '/pages/login/login-account' |
| | | }) |
| | | return false |
| | | } |
| | | return true |
| | | } |
| | | |
| | | // 跳转路由前检查登录状态 |
| | | const route = (url) => { |
| | | if (!vm.isLogin) { |
| | | uni.showToast({ |
| | | title: '请先登录', |
| | | icon: 'none' |
| | | }) |
| | | setTimeout(() => { |
| | | uni.navigateTo({ |
| | | url: '/pages/login/login-account' |
| | | }) |
| | | }, 500) |
| | | return false |
| | | } |
| | | uni.navigateTo({ |
| | | url: url |
| | | }) |
| | | } |
| | | |
| | | // URL参数转对象 |
| | | const paramsToObj = (url) => { |
| | | if (url.indexOf('?') != -1) { |
| | | let arr = url.split('?')[1] |
| | | } |
| | | let arr = url.split('&') |
| | | let obj = {} |
| | | for (let i of arr) { |
| | | obj[i.split('=')[0]] = i.split('=')[1] |
| | | } |
| | | return obj |
| | | } |
| | | |
| | | // 刷新当前页面 |
| | | const refreshPage = () => { |
| | | const pages = getCurrentPages() |
| | | const currentPage = pages[pages.length - 1] |
| | | const path = '/' + currentPage.route + vm.$u.queryParams(currentPage.options) |
| | | if (vm.$u.test.contains(currentPage.route, 'tabbar')) { |
| | | uni.reLaunch({ |
| | | url: path, |
| | | fail: (err) => { |
| | | console.log(err) |
| | | } |
| | | }) |
| | | } else { |
| | | uni.redirectTo({ |
| | | url: path, |
| | | fail: (err) => { |
| | | console.log(err) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // 提示 |
| | | const showToast = (data = {}) => { |
| | | if (typeof data == 'string') { |
| | | uni.showToast({ |
| | | title: data, |
| | | icon: 'none' |
| | | }) |
| | | } else { |
| | | uni.showToast({ |
| | | title: data.title, |
| | | icon: data.icon || 'none', |
| | | image: data.image || '', |
| | | mask: data.mask || false, |
| | | position: data.position || 'center', |
| | | duration: data.duration || 1500, |
| | | success: () => { |
| | | setTimeout(() => { |
| | | if (data.back) return uni.navigateBack() |
| | | data.success && data.success() |
| | | }, data.duration || 1500) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // 将定义的方法挂载,使用this.$u.func.xx调用 |
| | | Vue.prototype.$u.func = { |
| | | login, |
| | | logout, |
| | | route, |
| | | checkLogin, |
| | | paramsToObj, |
| | | refreshPage, |
| | | showToast |
| | | } |
| | | } |
| | | export default { |
| | | install |
| | | } |
| New file |
| | |
| | | import buildURL from '../helpers/buildURL' |
| | | import buildFullPath from '../core/buildFullPath' |
| | | import settle from '../core/settle' |
| | | import { isUndefined } from "../utils" |
| | | |
| | | /** |
| | | * 返回可选值存在的配置 |
| | | * @param {Array} keys - 可选值数组 |
| | | * @param {Object} config2 - 配置 |
| | | * @return {{}} - 存在的配置项 |
| | | */ |
| | | const mergeKeys = (keys, config2) => { |
| | | let config = {} |
| | | keys.forEach(prop => { |
| | | if (!isUndefined(config2[prop])) { |
| | | config[prop] = config2[prop] |
| | | } |
| | | }) |
| | | return config |
| | | } |
| | | export default (config) => { |
| | | return new Promise((resolve, reject) => { |
| | | let fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params) |
| | | const _config = { |
| | | url: fullPath, |
| | | header: config.header, |
| | | complete: (response) => { |
| | | config.fullPath = fullPath |
| | | response.config = config |
| | | try { |
| | | // 对可能字符串不是json 的情况容错 |
| | | if (typeof response.data === 'string') { |
| | | response.data = JSON.parse(response.data) |
| | | } |
| | | // eslint-disable-next-line no-empty |
| | | } catch (e) { |
| | | } |
| | | settle(resolve, reject, response) |
| | | } |
| | | } |
| | | let requestTask |
| | | if (config.method === 'UPLOAD') { |
| | | delete _config.header['content-type'] |
| | | delete _config.header['Content-Type'] |
| | | let otherConfig = { |
| | | // #ifdef MP-ALIPAY |
| | | fileType: config.fileType, |
| | | // #endif |
| | | filePath: config.filePath, |
| | | name: config.name |
| | | } |
| | | const optionalKeys = [ |
| | | // #ifdef APP-PLUS || H5 |
| | | 'files', |
| | | // #endif |
| | | // #ifdef H5 |
| | | 'file', |
| | | // #endif |
| | | // #ifdef H5 || APP-PLUS |
| | | 'timeout', |
| | | // #endif |
| | | 'formData' |
| | | ] |
| | | requestTask = uni.uploadFile({..._config, ...otherConfig, ...mergeKeys(optionalKeys, config)}) |
| | | } else if (config.method === 'DOWNLOAD') { |
| | | // #ifdef H5 || APP-PLUS |
| | | if (!isUndefined(config['timeout'])) { |
| | | _config['timeout'] = config['timeout'] |
| | | } |
| | | // #endif |
| | | requestTask = uni.downloadFile(_config) |
| | | } else { |
| | | const optionalKeys = [ |
| | | 'data', |
| | | 'method', |
| | | // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN |
| | | 'timeout', |
| | | // #endif |
| | | 'dataType', |
| | | // #ifndef MP-ALIPAY |
| | | 'responseType', |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | 'sslVerify', |
| | | // #endif |
| | | // #ifdef H5 |
| | | 'withCredentials', |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | 'firstIpv4', |
| | | // #endif |
| | | ] |
| | | requestTask = uni.request({..._config,...mergeKeys(optionalKeys, config)}) |
| | | } |
| | | if (config.getTask) { |
| | | config.getTask(requestTask, config) |
| | | } |
| | | }) |
| | | } |
| New file |
| | |
| | | 'use strict' |
| | | |
| | | |
| | | function InterceptorManager() { |
| | | this.handlers = [] |
| | | } |
| | | |
| | | /** |
| | | * Add a new interceptor to the stack |
| | | * |
| | | * @param {Function} fulfilled The function to handle `then` for a `Promise` |
| | | * @param {Function} rejected The function to handle `reject` for a `Promise` |
| | | * |
| | | * @return {Number} An ID used to remove interceptor later |
| | | */ |
| | | InterceptorManager.prototype.use = function use(fulfilled, rejected) { |
| | | this.handlers.push({ |
| | | fulfilled: fulfilled, |
| | | rejected: rejected |
| | | }) |
| | | return this.handlers.length - 1 |
| | | } |
| | | |
| | | /** |
| | | * Remove an interceptor from the stack |
| | | * |
| | | * @param {Number} id The ID that was returned by `use` |
| | | */ |
| | | InterceptorManager.prototype.eject = function eject(id) { |
| | | if (this.handlers[id]) { |
| | | this.handlers[id] = null |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Iterate over all the registered interceptors |
| | | * |
| | | * This method is particularly useful for skipping over any |
| | | * interceptors that may have become `null` calling `eject`. |
| | | * |
| | | * @param {Function} fn The function to call for each interceptor |
| | | */ |
| | | InterceptorManager.prototype.forEach = function forEach(fn) { |
| | | this.handlers.forEach(h => { |
| | | if (h !== null) { |
| | | fn(h) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export default InterceptorManager |
| New file |
| | |
| | | /** |
| | | * @Class Request |
| | | * @description luch-request http请求插件 |
| | | * @version 3.0.6 |
| | | * @Author lu-ch |
| | | * @Date 2021-05-10 |
| | | * @Email webwork.s@qq.com |
| | | * 文档: https://www.quanzhan.co/luch-request/ |
| | | * github: https://github.com/lei-mu/luch-request |
| | | * DCloud: http://ext.dcloud.net.cn/plugin?id=392 |
| | | * HBuilderX: beat-3.0.4 alpha-3.0.4 |
| | | */ |
| | | |
| | | |
| | | import dispatchRequest from './dispatchRequest' |
| | | import InterceptorManager from './InterceptorManager' |
| | | import mergeConfig from './mergeConfig' |
| | | import defaults from './defaults' |
| | | import { isPlainObject } from '../utils' |
| | | |
| | | export default class Request { |
| | | /** |
| | | * @param {Object} arg - 全局配置 |
| | | * @param {String} arg.baseURL - 全局根路径 |
| | | * @param {Object} arg.header - 全局header |
| | | * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式 |
| | | * @param {String} arg.dataType = [json] - 全局默认的dataType |
| | | * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持 |
| | | * @param {Object} arg.custom - 全局默认的自定义参数 |
| | | * @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序 |
| | | * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+) |
| | | * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+) |
| | | * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+) |
| | | * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300 |
| | | */ |
| | | constructor(arg = {}) { |
| | | if (!isPlainObject(arg)) { |
| | | arg = {} |
| | | console.warn('设置全局参数必须接收一个Object') |
| | | } |
| | | this.config = {...defaults, ...arg} |
| | | this.interceptors = { |
| | | request: new InterceptorManager(), |
| | | response: new InterceptorManager() |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @Function |
| | | * @param {Request~setConfigCallback} f - 设置全局默认配置 |
| | | */ |
| | | setConfig(f) { |
| | | this.config = f(this.config) |
| | | } |
| | | |
| | | middleware(config) { |
| | | config = mergeConfig(this.config, config) |
| | | let chain = [dispatchRequest, undefined] |
| | | let promise = Promise.resolve(config) |
| | | |
| | | this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { |
| | | chain.unshift(interceptor.fulfilled, interceptor.rejected) |
| | | }) |
| | | |
| | | this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { |
| | | chain.push(interceptor.fulfilled, interceptor.rejected) |
| | | }) |
| | | |
| | | while (chain.length) { |
| | | promise = promise.then(chain.shift(), chain.shift()) |
| | | } |
| | | |
| | | return promise |
| | | } |
| | | |
| | | /** |
| | | * @Function |
| | | * @param {Object} config - 请求配置项 |
| | | * @prop {String} options.url - 请求路径 |
| | | * @prop {Object} options.data - 请求参数 |
| | | * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型 |
| | | * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse |
| | | * @prop {Object} [options.header = config.header] - 请求header |
| | | * @prop {Object} [options.method = config.method] - 请求方法 |
| | | * @returns {Promise<unknown>} |
| | | */ |
| | | request(config = {}) { |
| | | return this.middleware(config) |
| | | } |
| | | |
| | | get(url, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | method: 'GET', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | post(url, data, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | data, |
| | | method: 'POST', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | // #ifndef MP-ALIPAY |
| | | put(url, data, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | data, |
| | | method: 'PUT', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | // #endif |
| | | |
| | | // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU |
| | | delete(url, data, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | data, |
| | | method: 'DELETE', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | // #endif |
| | | |
| | | // #ifdef H5 || MP-WEIXIN |
| | | connect(url, data, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | data, |
| | | method: 'CONNECT', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | // #endif |
| | | |
| | | // #ifdef H5 || MP-WEIXIN || MP-BAIDU |
| | | head(url, data, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | data, |
| | | method: 'HEAD', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | // #endif |
| | | |
| | | // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU |
| | | options(url, data, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | data, |
| | | method: 'OPTIONS', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | // #endif |
| | | |
| | | // #ifdef H5 || MP-WEIXIN |
| | | trace(url, data, options = {}) { |
| | | return this.middleware({ |
| | | url, |
| | | data, |
| | | method: 'TRACE', |
| | | ...options |
| | | }) |
| | | } |
| | | |
| | | // #endif |
| | | |
| | | upload(url, config = {}) { |
| | | config.url = url |
| | | config.method = 'UPLOAD' |
| | | return this.middleware(config) |
| | | } |
| | | |
| | | download(url, config = {}) { |
| | | config.url = url |
| | | config.method = 'DOWNLOAD' |
| | | return this.middleware(config) |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * setConfig回调 |
| | | * @return {Object} - 返回操作后的config |
| | | * @callback Request~setConfigCallback |
| | | * @param {Object} config - 全局默认config |
| | | */ |
| New file |
| | |
| | | 'use strict' |
| | | |
| | | import isAbsoluteURL from '../helpers/isAbsoluteURL' |
| | | import combineURLs from '../helpers/combineURLs' |
| | | |
| | | /** |
| | | * Creates a new URL by combining the baseURL with the requestedURL, |
| | | * only when the requestedURL is not already an absolute URL. |
| | | * If the requestURL is absolute, this function returns the requestedURL untouched. |
| | | * |
| | | * @param {string} baseURL The base URL |
| | | * @param {string} requestedURL Absolute or relative URL to combine |
| | | * @returns {string} The combined full path |
| | | */ |
| | | export default function buildFullPath(baseURL, requestedURL) { |
| | | if (baseURL && !isAbsoluteURL(requestedURL)) { |
| | | return combineURLs(baseURL, requestedURL) |
| | | } |
| | | return requestedURL |
| | | } |
| New file |
| | |
| | | /** |
| | | * 默认的全局配置 |
| | | */ |
| | | |
| | | |
| | | export default { |
| | | baseURL: '', |
| | | header: {}, |
| | | method: 'GET', |
| | | dataType: 'json', |
| | | // #ifndef MP-ALIPAY |
| | | responseType: 'text', |
| | | // #endif |
| | | custom: {}, |
| | | // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN |
| | | timeout: 60000, |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | sslVerify: true, |
| | | // #endif |
| | | // #ifdef H5 |
| | | withCredentials: false, |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | firstIpv4: false, |
| | | // #endif |
| | | validateStatus: function validateStatus(status) { |
| | | return status >= 200 && status < 300 |
| | | } |
| | | } |
| New file |
| | |
| | | import adapter from '../adapters/index' |
| | | |
| | | |
| | | export default (config) => { |
| | | return adapter(config) |
| | | } |
| New file |
| | |
| | | import {deepMerge, isUndefined} from '../utils' |
| | | |
| | | /** |
| | | * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局 |
| | | * @param {Array} keys - 配置项 |
| | | * @param {Object} globalsConfig - 当前的全局配置 |
| | | * @param {Object} config2 - 局部配置 |
| | | * @return {{}} |
| | | */ |
| | | const mergeKeys = (keys, globalsConfig, config2) => { |
| | | let config = {} |
| | | keys.forEach(prop => { |
| | | if (!isUndefined(config2[prop])) { |
| | | config[prop] = config2[prop] |
| | | } else if (!isUndefined(globalsConfig[prop])) { |
| | | config[prop] = globalsConfig[prop] |
| | | } |
| | | }) |
| | | return config |
| | | } |
| | | /** |
| | | * |
| | | * @param globalsConfig - 当前实例的全局配置 |
| | | * @param config2 - 当前的局部配置 |
| | | * @return - 合并后的配置 |
| | | */ |
| | | export default (globalsConfig, config2 = {}) => { |
| | | const method = config2.method || globalsConfig.method || 'GET' |
| | | let config = { |
| | | baseURL: globalsConfig.baseURL || '', |
| | | method: method, |
| | | url: config2.url || '', |
| | | params: config2.params || {}, |
| | | custom: {...(globalsConfig.custom || {}), ...(config2.custom || {})}, |
| | | header: deepMerge(globalsConfig.header || {}, config2.header || {}) |
| | | } |
| | | const defaultToConfig2Keys = ['getTask', 'validateStatus'] |
| | | config = {...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2)} |
| | | |
| | | // eslint-disable-next-line no-empty |
| | | if (method === 'DOWNLOAD') { |
| | | // #ifdef H5 || APP-PLUS |
| | | if (!isUndefined(config2.timeout)) { |
| | | config['timeout'] = config2['timeout'] |
| | | } else if (!isUndefined(globalsConfig.timeout)) { |
| | | config['timeout'] = globalsConfig['timeout'] |
| | | } |
| | | // #endif |
| | | } else if (method === 'UPLOAD') { |
| | | delete config.header['content-type'] |
| | | delete config.header['Content-Type'] |
| | | const uploadKeys = [ |
| | | // #ifdef APP-PLUS || H5 |
| | | 'files', |
| | | // #endif |
| | | // #ifdef MP-ALIPAY |
| | | 'fileType', |
| | | // #endif |
| | | // #ifdef H5 |
| | | 'file', |
| | | // #endif |
| | | 'filePath', |
| | | 'name', |
| | | // #ifdef H5 || APP-PLUS |
| | | 'timeout', |
| | | // #endif |
| | | 'formData', |
| | | ] |
| | | uploadKeys.forEach(prop => { |
| | | if (!isUndefined(config2[prop])) { |
| | | config[prop] = config2[prop] |
| | | } |
| | | }) |
| | | // #ifdef H5 || APP-PLUS |
| | | if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) { |
| | | config['timeout'] = globalsConfig['timeout'] |
| | | } |
| | | // #endif |
| | | } else { |
| | | const defaultsKeys = [ |
| | | 'data', |
| | | // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN |
| | | 'timeout', |
| | | // #endif |
| | | 'dataType', |
| | | // #ifndef MP-ALIPAY |
| | | 'responseType', |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | 'sslVerify', |
| | | // #endif |
| | | // #ifdef H5 |
| | | 'withCredentials', |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | 'firstIpv4', |
| | | // #endif |
| | | ] |
| | | config = {...config, ...mergeKeys(defaultsKeys, globalsConfig, config2)} |
| | | } |
| | | |
| | | return config |
| | | } |
| New file |
| | |
| | | /** |
| | | * Resolve or reject a Promise based on response status. |
| | | * |
| | | * @param {Function} resolve A function that resolves the promise. |
| | | * @param {Function} reject A function that rejects the promise. |
| | | * @param {object} response The response. |
| | | */ |
| | | export default function settle(resolve, reject, response) { |
| | | const validateStatus = response.config.validateStatus |
| | | const status = response.statusCode |
| | | if (status && (!validateStatus || validateStatus(status))) { |
| | | resolve(response) |
| | | } else { |
| | | reject(response) |
| | | } |
| | | } |
| New file |
| | |
| | | 'use strict' |
| | | |
| | | import * as utils from './../utils' |
| | | |
| | | function encode(val) { |
| | | return encodeURIComponent(val). |
| | | replace(/%40/gi, '@'). |
| | | replace(/%3A/gi, ':'). |
| | | replace(/%24/g, '$'). |
| | | replace(/%2C/gi, ','). |
| | | replace(/%20/g, '+'). |
| | | replace(/%5B/gi, '['). |
| | | replace(/%5D/gi, ']') |
| | | } |
| | | |
| | | /** |
| | | * Build a URL by appending params to the end |
| | | * |
| | | * @param {string} url The base of the url (e.g., http://www.google.com) |
| | | * @param {object} [params] The params to be appended |
| | | * @returns {string} The formatted url |
| | | */ |
| | | export default function buildURL(url, params) { |
| | | /*eslint no-param-reassign:0*/ |
| | | if (!params) { |
| | | return url |
| | | } |
| | | |
| | | var serializedParams |
| | | if (utils.isURLSearchParams(params)) { |
| | | serializedParams = params.toString() |
| | | } else { |
| | | var parts = [] |
| | | |
| | | utils.forEach(params, function serialize(val, key) { |
| | | if (val === null || typeof val === 'undefined') { |
| | | return |
| | | } |
| | | |
| | | if (utils.isArray(val)) { |
| | | key = key + '[]' |
| | | } else { |
| | | val = [val] |
| | | } |
| | | |
| | | utils.forEach(val, function parseValue(v) { |
| | | if (utils.isDate(v)) { |
| | | v = v.toISOString() |
| | | } else if (utils.isObject(v)) { |
| | | v = JSON.stringify(v) |
| | | } |
| | | parts.push(encode(key) + '=' + encode(v)) |
| | | }) |
| | | }) |
| | | |
| | | serializedParams = parts.join('&') |
| | | } |
| | | |
| | | if (serializedParams) { |
| | | var hashmarkIndex = url.indexOf('#') |
| | | if (hashmarkIndex !== -1) { |
| | | url = url.slice(0, hashmarkIndex) |
| | | } |
| | | |
| | | url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams |
| | | } |
| | | |
| | | return url |
| | | } |
| New file |
| | |
| | | 'use strict' |
| | | |
| | | /** |
| | | * Creates a new URL by combining the specified URLs |
| | | * |
| | | * @param {string} baseURL The base URL |
| | | * @param {string} relativeURL The relative URL |
| | | * @returns {string} The combined URL |
| | | */ |
| | | export default function combineURLs(baseURL, relativeURL) { |
| | | return relativeURL |
| | | ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') |
| | | : baseURL |
| | | } |
| New file |
| | |
| | | 'use strict' |
| | | |
| | | /** |
| | | * Determines whether the specified URL is absolute |
| | | * |
| | | * @param {string} url The URL to test |
| | | * @returns {boolean} True if the specified URL is absolute, otherwise false |
| | | */ |
| | | export default function isAbsoluteURL(url) { |
| | | // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL). |
| | | // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed |
| | | // by any combination of letters, digits, plus, period, or hyphen. |
| | | return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url) |
| | | } |
| New file |
| | |
| | | type AnyObject = Record<string | number | symbol, any> |
| | | type HttpPromise<T> = Promise<HttpResponse<T>>; |
| | | type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask |
| | | export interface RequestTask { |
| | | abort: () => void; |
| | | offHeadersReceived: () => void; |
| | | onHeadersReceived: () => void; |
| | | } |
| | | export interface HttpRequestConfig<T = Tasks> { |
| | | /** 请求基地址 */ |
| | | baseURL?: string; |
| | | /** 请求服务器接口地址 */ |
| | | url?: string; |
| | | |
| | | /** 请求查询参数,自动拼接为查询字符串 */ |
| | | params?: AnyObject; |
| | | /** 请求体参数 */ |
| | | data?: AnyObject; |
| | | |
| | | /** 文件对应的 key */ |
| | | name?: string; |
| | | /** HTTP 请求中其他额外的 form data */ |
| | | formData?: AnyObject; |
| | | /** 要上传文件资源的路径。 */ |
| | | filePath?: string; |
| | | /** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5( 2.6.15+) */ |
| | | files?: Array<{ |
| | | name?: string; |
| | | file?: File; |
| | | uri: string; |
| | | }>; |
| | | /** 要上传的文件对象,仅H5(2.6.15+)支持 */ |
| | | file?: File; |
| | | |
| | | /** 请求头信息 */ |
| | | header?: AnyObject; |
| | | /** 请求方式 */ |
| | | method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD"; |
| | | /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */ |
| | | dataType?: string; |
| | | /** 设置响应的数据类型,支付宝小程序不支持 */ |
| | | responseType?: "text" | "arraybuffer"; |
| | | /** 自定义参数 */ |
| | | custom?: AnyObject; |
| | | /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */ |
| | | timeout?: number; |
| | | /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */ |
| | | firstIpv4?: boolean; |
| | | /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */ |
| | | sslVerify?: boolean; |
| | | /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */ |
| | | withCredentials?: boolean; |
| | | |
| | | /** 返回当前请求的task, options。请勿在此处修改options。 */ |
| | | getTask?: (task: T, options: HttpRequestConfig<T>) => void; |
| | | /** 全局自定义验证器 */ |
| | | validateStatus?: (statusCode: number) => boolean | void; |
| | | } |
| | | export interface HttpResponse<T = any> { |
| | | config: HttpRequestConfig; |
| | | statusCode: number; |
| | | cookies: Array<string>; |
| | | data: T; |
| | | errMsg: string; |
| | | header: AnyObject; |
| | | } |
| | | export interface HttpUploadResponse<T = any> { |
| | | config: HttpRequestConfig; |
| | | statusCode: number; |
| | | data: T; |
| | | errMsg: string; |
| | | } |
| | | export interface HttpDownloadResponse extends HttpResponse { |
| | | tempFilePath: string; |
| | | } |
| | | export interface HttpError { |
| | | config: HttpRequestConfig; |
| | | statusCode?: number; |
| | | cookies?: Array<string>; |
| | | data?: any; |
| | | errMsg: string; |
| | | header?: AnyObject; |
| | | } |
| | | export interface HttpInterceptorManager<V, E = V> { |
| | | use( |
| | | onFulfilled?: (config: V) => Promise<V> | V, |
| | | onRejected?: (config: E) => Promise<E> | E |
| | | ): void; |
| | | eject(id: number): void; |
| | | } |
| | | export abstract class HttpRequestAbstract { |
| | | constructor(config?: HttpRequestConfig); |
| | | config: HttpRequestConfig; |
| | | interceptors: { |
| | | request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>; |
| | | response: HttpInterceptorManager<HttpResponse, HttpError>; |
| | | } |
| | | middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>; |
| | | request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>; |
| | | delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; |
| | | |
| | | download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>; |
| | | |
| | | setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void; |
| | | } |
| | | |
| | | declare class HttpRequest extends HttpRequestAbstract { } |
| | | export default HttpRequest; |
| New file |
| | |
| | | import Request from './core/Request' |
| | | export default Request |
| New file |
| | |
| | | 'use strict' |
| | | |
| | | // utils is a library of generic helper functions non-specific to axios |
| | | |
| | | var toString = Object.prototype.toString |
| | | |
| | | /** |
| | | * Determine if a value is an Array |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is an Array, otherwise false |
| | | */ |
| | | export function isArray (val) { |
| | | return toString.call(val) === '[object Array]' |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Determine if a value is an Object |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is an Object, otherwise false |
| | | */ |
| | | export function isObject (val) { |
| | | return val !== null && typeof val === 'object' |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a Date |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a Date, otherwise false |
| | | */ |
| | | export function isDate (val) { |
| | | return toString.call(val) === '[object Date]' |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a URLSearchParams object |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a URLSearchParams object, otherwise false |
| | | */ |
| | | export function isURLSearchParams (val) { |
| | | return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Iterate over an Array or an Object invoking a function for each item. |
| | | * |
| | | * If `obj` is an Array callback will be called passing |
| | | * the value, index, and complete array for each item. |
| | | * |
| | | * If 'obj' is an Object callback will be called passing |
| | | * the value, key, and complete object for each property. |
| | | * |
| | | * @param {Object|Array} obj The object to iterate |
| | | * @param {Function} fn The callback to invoke for each item |
| | | */ |
| | | export function forEach (obj, fn) { |
| | | // Don't bother if no value provided |
| | | if (obj === null || typeof obj === 'undefined') { |
| | | return |
| | | } |
| | | |
| | | // Force an array if not already something iterable |
| | | if (typeof obj !== 'object') { |
| | | /*eslint no-param-reassign:0*/ |
| | | obj = [obj] |
| | | } |
| | | |
| | | if (isArray(obj)) { |
| | | // Iterate over array values |
| | | for (var i = 0, l = obj.length; i < l; i++) { |
| | | fn.call(null, obj[i], i, obj) |
| | | } |
| | | } else { |
| | | // Iterate over object keys |
| | | for (var key in obj) { |
| | | if (Object.prototype.hasOwnProperty.call(obj, key)) { |
| | | fn.call(null, obj[key], key, obj) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 是否为boolean 值 |
| | | * @param val |
| | | * @returns {boolean} |
| | | */ |
| | | export function isBoolean(val) { |
| | | return typeof val === 'boolean' |
| | | } |
| | | |
| | | /** |
| | | * 是否为真正的对象{} new Object |
| | | * @param {any} obj - 检测的对象 |
| | | * @returns {boolean} |
| | | */ |
| | | export function isPlainObject(obj) { |
| | | return Object.prototype.toString.call(obj) === '[object Object]' |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Function equal to merge with the difference being that no reference |
| | | * to original objects is kept. |
| | | * |
| | | * @see merge |
| | | * @param {Object} obj1 Object to merge |
| | | * @returns {Object} Result of all merge properties |
| | | */ |
| | | export function deepMerge(/* obj1, obj2, obj3, ... */) { |
| | | let result = {} |
| | | function assignValue(val, key) { |
| | | if (typeof result[key] === 'object' && typeof val === 'object') { |
| | | result[key] = deepMerge(result[key], val) |
| | | } else if (typeof val === 'object') { |
| | | result[key] = deepMerge({}, val) |
| | | } else { |
| | | result[key] = val |
| | | } |
| | | } |
| | | for (let i = 0, l = arguments.length; i < l; i++) { |
| | | forEach(arguments[i], assignValue) |
| | | } |
| | | return result |
| | | } |
| | | |
| | | export function isUndefined (val) { |
| | | return typeof val === 'undefined' |
| | | } |
| New file |
| | |
| | | /** |
| | | * [js-md5]{@link https://github.com/emn178/js-md5} |
| | | * |
| | | * @namespace md5 |
| | | * @version 0.7.3 |
| | | * @author Chen, Yi-Cyuan [emn178@gmail.com] |
| | | * @copyright Chen, Yi-Cyuan 2014-2017 |
| | | * @license MIT |
| | | */ |
| | | (function () { |
| | | 'use strict'; |
| | | |
| | | var ERROR = 'input is invalid type'; |
| | | var WINDOW = typeof window === 'object'; |
| | | var root = WINDOW ? window : {}; |
| | | if (root.JS_MD5_NO_WINDOW) { |
| | | WINDOW = false; |
| | | } |
| | | var WEB_WORKER = !WINDOW && typeof self === 'object'; |
| | | var NODE_JS = !root.JS_MD5_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; |
| | | if (NODE_JS) { |
| | | root = global; |
| | | } else if (WEB_WORKER) { |
| | | root = self; |
| | | } |
| | | var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && typeof module === 'object' && module.exports; |
| | | var AMD = typeof define === 'function' && define.amd; |
| | | var ARRAY_BUFFER = !root.JS_MD5_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; |
| | | var HEX_CHARS = '0123456789abcdef'.split(''); |
| | | var EXTRA = [128, 32768, 8388608, -2147483648]; |
| | | var SHIFT = [0, 8, 16, 24]; |
| | | var OUTPUT_TYPES = ['hex', 'array', 'digest', 'buffer', 'arrayBuffer', 'base64']; |
| | | var BASE64_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); |
| | | |
| | | var blocks = [], buffer8; |
| | | if (ARRAY_BUFFER) { |
| | | var buffer = new ArrayBuffer(68); |
| | | buffer8 = new Uint8Array(buffer); |
| | | blocks = new Uint32Array(buffer); |
| | | } |
| | | |
| | | if (root.JS_MD5_NO_NODE_JS || !Array.isArray) { |
| | | Array.isArray = function (obj) { |
| | | return Object.prototype.toString.call(obj) === '[object Array]'; |
| | | }; |
| | | } |
| | | |
| | | if (ARRAY_BUFFER && (root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { |
| | | ArrayBuffer.isView = function (obj) { |
| | | return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * @method hex |
| | | * @memberof md5 |
| | | * @description Output hash as hex string |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {String} Hex string |
| | | * @example |
| | | * md5.hex('The quick brown fox jumps over the lazy dog'); |
| | | * // equal to |
| | | * md5('The quick brown fox jumps over the lazy dog'); |
| | | */ |
| | | /** |
| | | * @method digest |
| | | * @memberof md5 |
| | | * @description Output hash as bytes array |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {Array} Bytes array |
| | | * @example |
| | | * md5.digest('The quick brown fox jumps over the lazy dog'); |
| | | */ |
| | | /** |
| | | * @method array |
| | | * @memberof md5 |
| | | * @description Output hash as bytes array |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {Array} Bytes array |
| | | * @example |
| | | * md5.array('The quick brown fox jumps over the lazy dog'); |
| | | */ |
| | | /** |
| | | * @method arrayBuffer |
| | | * @memberof md5 |
| | | * @description Output hash as ArrayBuffer |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {ArrayBuffer} ArrayBuffer |
| | | * @example |
| | | * md5.arrayBuffer('The quick brown fox jumps over the lazy dog'); |
| | | */ |
| | | /** |
| | | * @method buffer |
| | | * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead. |
| | | * @memberof md5 |
| | | * @description Output hash as ArrayBuffer |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {ArrayBuffer} ArrayBuffer |
| | | * @example |
| | | * md5.buffer('The quick brown fox jumps over the lazy dog'); |
| | | */ |
| | | /** |
| | | * @method base64 |
| | | * @memberof md5 |
| | | * @description Output hash as base64 string |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {String} base64 string |
| | | * @example |
| | | * md5.base64('The quick brown fox jumps over the lazy dog'); |
| | | */ |
| | | var createOutputMethod = function (outputType) { |
| | | return function (message) { |
| | | return new Md5(true).update(message)[outputType](); |
| | | }; |
| | | }; |
| | | |
| | | /** |
| | | * @method create |
| | | * @memberof md5 |
| | | * @description Create Md5 object |
| | | * @returns {Md5} Md5 object. |
| | | * @example |
| | | * var hash = md5.create(); |
| | | */ |
| | | /** |
| | | * @method update |
| | | * @memberof md5 |
| | | * @description Create and update Md5 object |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {Md5} Md5 object. |
| | | * @example |
| | | * var hash = md5.update('The quick brown fox jumps over the lazy dog'); |
| | | * // equal to |
| | | * var hash = md5.create(); |
| | | * hash.update('The quick brown fox jumps over the lazy dog'); |
| | | */ |
| | | var createMethod = function () { |
| | | var method = createOutputMethod('hex'); |
| | | if (NODE_JS) { |
| | | method = nodeWrap(method); |
| | | } |
| | | method.create = function () { |
| | | return new Md5(); |
| | | }; |
| | | method.update = function (message) { |
| | | return method.create().update(message); |
| | | }; |
| | | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { |
| | | var type = OUTPUT_TYPES[i]; |
| | | method[type] = createOutputMethod(type); |
| | | } |
| | | return method; |
| | | }; |
| | | |
| | | var nodeWrap = function (method) { |
| | | var crypto = eval("require('crypto')"); |
| | | var Buffer = eval("require('buffer').Buffer"); |
| | | var nodeMethod = function (message) { |
| | | if (typeof message === 'string') { |
| | | return crypto.createHash('md5').update(message, 'utf8').digest('hex'); |
| | | } else { |
| | | if (message === null || message === undefined) { |
| | | throw ERROR; |
| | | } else if (message.constructor === ArrayBuffer) { |
| | | message = new Uint8Array(message); |
| | | } |
| | | } |
| | | if (Array.isArray(message) || ArrayBuffer.isView(message) || |
| | | message.constructor === Buffer) { |
| | | return crypto.createHash('md5').update(new Buffer(message)).digest('hex'); |
| | | } else { |
| | | return method(message); |
| | | } |
| | | }; |
| | | return nodeMethod; |
| | | }; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Md5 class |
| | | * @class Md5 |
| | | * @description This is internal class. |
| | | * @see {@link md5.create} |
| | | */ |
| | | function Md5(sharedMemory) { |
| | | if (sharedMemory) { |
| | | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = |
| | | blocks[4] = blocks[5] = blocks[6] = blocks[7] = |
| | | blocks[8] = blocks[9] = blocks[10] = blocks[11] = |
| | | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; |
| | | this.blocks = blocks; |
| | | this.buffer8 = buffer8; |
| | | } else { |
| | | if (ARRAY_BUFFER) { |
| | | var buffer = new ArrayBuffer(68); |
| | | this.buffer8 = new Uint8Array(buffer); |
| | | this.blocks = new Uint32Array(buffer); |
| | | } else { |
| | | this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; |
| | | } |
| | | } |
| | | this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0; |
| | | this.finalized = this.hashed = false; |
| | | this.first = true; |
| | | } |
| | | |
| | | /** |
| | | * @method update |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Update hash |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {Md5} Md5 object. |
| | | * @see {@link md5.update} |
| | | */ |
| | | Md5.prototype.update = function (message) { |
| | | if (this.finalized) { |
| | | return; |
| | | } |
| | | |
| | | var notString, type = typeof message; |
| | | if (type !== 'string') { |
| | | if (type === 'object') { |
| | | if (message === null) { |
| | | throw ERROR; |
| | | } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { |
| | | message = new Uint8Array(message); |
| | | } else if (!Array.isArray(message)) { |
| | | if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { |
| | | throw ERROR; |
| | | } |
| | | } |
| | | } else { |
| | | throw ERROR; |
| | | } |
| | | notString = true; |
| | | } |
| | | var code, index = 0, i, length = message.length, blocks = this.blocks; |
| | | var buffer8 = this.buffer8; |
| | | |
| | | while (index < length) { |
| | | if (this.hashed) { |
| | | this.hashed = false; |
| | | blocks[0] = blocks[16]; |
| | | blocks[16] = blocks[1] = blocks[2] = blocks[3] = |
| | | blocks[4] = blocks[5] = blocks[6] = blocks[7] = |
| | | blocks[8] = blocks[9] = blocks[10] = blocks[11] = |
| | | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; |
| | | } |
| | | |
| | | if (notString) { |
| | | if (ARRAY_BUFFER) { |
| | | for (i = this.start; index < length && i < 64; ++index) { |
| | | buffer8[i++] = message[index]; |
| | | } |
| | | } else { |
| | | for (i = this.start; index < length && i < 64; ++index) { |
| | | blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; |
| | | } |
| | | } |
| | | } else { |
| | | if (ARRAY_BUFFER) { |
| | | for (i = this.start; index < length && i < 64; ++index) { |
| | | code = message.charCodeAt(index); |
| | | if (code < 0x80) { |
| | | buffer8[i++] = code; |
| | | } else if (code < 0x800) { |
| | | buffer8[i++] = 0xc0 | (code >> 6); |
| | | buffer8[i++] = 0x80 | (code & 0x3f); |
| | | } else if (code < 0xd800 || code >= 0xe000) { |
| | | buffer8[i++] = 0xe0 | (code >> 12); |
| | | buffer8[i++] = 0x80 | ((code >> 6) & 0x3f); |
| | | buffer8[i++] = 0x80 | (code & 0x3f); |
| | | } else { |
| | | code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); |
| | | buffer8[i++] = 0xf0 | (code >> 18); |
| | | buffer8[i++] = 0x80 | ((code >> 12) & 0x3f); |
| | | buffer8[i++] = 0x80 | ((code >> 6) & 0x3f); |
| | | buffer8[i++] = 0x80 | (code & 0x3f); |
| | | } |
| | | } |
| | | } else { |
| | | for (i = this.start; index < length && i < 64; ++index) { |
| | | code = message.charCodeAt(index); |
| | | if (code < 0x80) { |
| | | blocks[i >> 2] |= code << SHIFT[i++ & 3]; |
| | | } else if (code < 0x800) { |
| | | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; |
| | | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; |
| | | } else if (code < 0xd800 || code >= 0xe000) { |
| | | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; |
| | | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; |
| | | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; |
| | | } else { |
| | | code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); |
| | | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; |
| | | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; |
| | | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; |
| | | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | this.lastByteIndex = i; |
| | | this.bytes += i - this.start; |
| | | if (i >= 64) { |
| | | this.start = i - 64; |
| | | this.hash(); |
| | | this.hashed = true; |
| | | } else { |
| | | this.start = i; |
| | | } |
| | | } |
| | | if (this.bytes > 4294967295) { |
| | | this.hBytes += this.bytes / 4294967296 << 0; |
| | | this.bytes = this.bytes % 4294967296; |
| | | } |
| | | return this; |
| | | }; |
| | | |
| | | Md5.prototype.finalize = function () { |
| | | if (this.finalized) { |
| | | return; |
| | | } |
| | | this.finalized = true; |
| | | var blocks = this.blocks, i = this.lastByteIndex; |
| | | blocks[i >> 2] |= EXTRA[i & 3]; |
| | | if (i >= 56) { |
| | | if (!this.hashed) { |
| | | this.hash(); |
| | | } |
| | | blocks[0] = blocks[16]; |
| | | blocks[16] = blocks[1] = blocks[2] = blocks[3] = |
| | | blocks[4] = blocks[5] = blocks[6] = blocks[7] = |
| | | blocks[8] = blocks[9] = blocks[10] = blocks[11] = |
| | | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; |
| | | } |
| | | blocks[14] = this.bytes << 3; |
| | | blocks[15] = this.hBytes << 3 | this.bytes >>> 29; |
| | | this.hash(); |
| | | }; |
| | | |
| | | Md5.prototype.hash = function () { |
| | | var a, b, c, d, bc, da, blocks = this.blocks; |
| | | |
| | | if (this.first) { |
| | | a = blocks[0] - 680876937; |
| | | a = (a << 7 | a >>> 25) - 271733879 << 0; |
| | | d = (-1732584194 ^ a & 2004318071) + blocks[1] - 117830708; |
| | | d = (d << 12 | d >>> 20) + a << 0; |
| | | c = (-271733879 ^ (d & (a ^ -271733879))) + blocks[2] - 1126478375; |
| | | c = (c << 17 | c >>> 15) + d << 0; |
| | | b = (a ^ (c & (d ^ a))) + blocks[3] - 1316259209; |
| | | b = (b << 22 | b >>> 10) + c << 0; |
| | | } else { |
| | | a = this.h0; |
| | | b = this.h1; |
| | | c = this.h2; |
| | | d = this.h3; |
| | | a += (d ^ (b & (c ^ d))) + blocks[0] - 680876936; |
| | | a = (a << 7 | a >>> 25) + b << 0; |
| | | d += (c ^ (a & (b ^ c))) + blocks[1] - 389564586; |
| | | d = (d << 12 | d >>> 20) + a << 0; |
| | | c += (b ^ (d & (a ^ b))) + blocks[2] + 606105819; |
| | | c = (c << 17 | c >>> 15) + d << 0; |
| | | b += (a ^ (c & (d ^ a))) + blocks[3] - 1044525330; |
| | | b = (b << 22 | b >>> 10) + c << 0; |
| | | } |
| | | |
| | | a += (d ^ (b & (c ^ d))) + blocks[4] - 176418897; |
| | | a = (a << 7 | a >>> 25) + b << 0; |
| | | d += (c ^ (a & (b ^ c))) + blocks[5] + 1200080426; |
| | | d = (d << 12 | d >>> 20) + a << 0; |
| | | c += (b ^ (d & (a ^ b))) + blocks[6] - 1473231341; |
| | | c = (c << 17 | c >>> 15) + d << 0; |
| | | b += (a ^ (c & (d ^ a))) + blocks[7] - 45705983; |
| | | b = (b << 22 | b >>> 10) + c << 0; |
| | | a += (d ^ (b & (c ^ d))) + blocks[8] + 1770035416; |
| | | a = (a << 7 | a >>> 25) + b << 0; |
| | | d += (c ^ (a & (b ^ c))) + blocks[9] - 1958414417; |
| | | d = (d << 12 | d >>> 20) + a << 0; |
| | | c += (b ^ (d & (a ^ b))) + blocks[10] - 42063; |
| | | c = (c << 17 | c >>> 15) + d << 0; |
| | | b += (a ^ (c & (d ^ a))) + blocks[11] - 1990404162; |
| | | b = (b << 22 | b >>> 10) + c << 0; |
| | | a += (d ^ (b & (c ^ d))) + blocks[12] + 1804603682; |
| | | a = (a << 7 | a >>> 25) + b << 0; |
| | | d += (c ^ (a & (b ^ c))) + blocks[13] - 40341101; |
| | | d = (d << 12 | d >>> 20) + a << 0; |
| | | c += (b ^ (d & (a ^ b))) + blocks[14] - 1502002290; |
| | | c = (c << 17 | c >>> 15) + d << 0; |
| | | b += (a ^ (c & (d ^ a))) + blocks[15] + 1236535329; |
| | | b = (b << 22 | b >>> 10) + c << 0; |
| | | a += (c ^ (d & (b ^ c))) + blocks[1] - 165796510; |
| | | a = (a << 5 | a >>> 27) + b << 0; |
| | | d += (b ^ (c & (a ^ b))) + blocks[6] - 1069501632; |
| | | d = (d << 9 | d >>> 23) + a << 0; |
| | | c += (a ^ (b & (d ^ a))) + blocks[11] + 643717713; |
| | | c = (c << 14 | c >>> 18) + d << 0; |
| | | b += (d ^ (a & (c ^ d))) + blocks[0] - 373897302; |
| | | b = (b << 20 | b >>> 12) + c << 0; |
| | | a += (c ^ (d & (b ^ c))) + blocks[5] - 701558691; |
| | | a = (a << 5 | a >>> 27) + b << 0; |
| | | d += (b ^ (c & (a ^ b))) + blocks[10] + 38016083; |
| | | d = (d << 9 | d >>> 23) + a << 0; |
| | | c += (a ^ (b & (d ^ a))) + blocks[15] - 660478335; |
| | | c = (c << 14 | c >>> 18) + d << 0; |
| | | b += (d ^ (a & (c ^ d))) + blocks[4] - 405537848; |
| | | b = (b << 20 | b >>> 12) + c << 0; |
| | | a += (c ^ (d & (b ^ c))) + blocks[9] + 568446438; |
| | | a = (a << 5 | a >>> 27) + b << 0; |
| | | d += (b ^ (c & (a ^ b))) + blocks[14] - 1019803690; |
| | | d = (d << 9 | d >>> 23) + a << 0; |
| | | c += (a ^ (b & (d ^ a))) + blocks[3] - 187363961; |
| | | c = (c << 14 | c >>> 18) + d << 0; |
| | | b += (d ^ (a & (c ^ d))) + blocks[8] + 1163531501; |
| | | b = (b << 20 | b >>> 12) + c << 0; |
| | | a += (c ^ (d & (b ^ c))) + blocks[13] - 1444681467; |
| | | a = (a << 5 | a >>> 27) + b << 0; |
| | | d += (b ^ (c & (a ^ b))) + blocks[2] - 51403784; |
| | | d = (d << 9 | d >>> 23) + a << 0; |
| | | c += (a ^ (b & (d ^ a))) + blocks[7] + 1735328473; |
| | | c = (c << 14 | c >>> 18) + d << 0; |
| | | b += (d ^ (a & (c ^ d))) + blocks[12] - 1926607734; |
| | | b = (b << 20 | b >>> 12) + c << 0; |
| | | bc = b ^ c; |
| | | a += (bc ^ d) + blocks[5] - 378558; |
| | | a = (a << 4 | a >>> 28) + b << 0; |
| | | d += (bc ^ a) + blocks[8] - 2022574463; |
| | | d = (d << 11 | d >>> 21) + a << 0; |
| | | da = d ^ a; |
| | | c += (da ^ b) + blocks[11] + 1839030562; |
| | | c = (c << 16 | c >>> 16) + d << 0; |
| | | b += (da ^ c) + blocks[14] - 35309556; |
| | | b = (b << 23 | b >>> 9) + c << 0; |
| | | bc = b ^ c; |
| | | a += (bc ^ d) + blocks[1] - 1530992060; |
| | | a = (a << 4 | a >>> 28) + b << 0; |
| | | d += (bc ^ a) + blocks[4] + 1272893353; |
| | | d = (d << 11 | d >>> 21) + a << 0; |
| | | da = d ^ a; |
| | | c += (da ^ b) + blocks[7] - 155497632; |
| | | c = (c << 16 | c >>> 16) + d << 0; |
| | | b += (da ^ c) + blocks[10] - 1094730640; |
| | | b = (b << 23 | b >>> 9) + c << 0; |
| | | bc = b ^ c; |
| | | a += (bc ^ d) + blocks[13] + 681279174; |
| | | a = (a << 4 | a >>> 28) + b << 0; |
| | | d += (bc ^ a) + blocks[0] - 358537222; |
| | | d = (d << 11 | d >>> 21) + a << 0; |
| | | da = d ^ a; |
| | | c += (da ^ b) + blocks[3] - 722521979; |
| | | c = (c << 16 | c >>> 16) + d << 0; |
| | | b += (da ^ c) + blocks[6] + 76029189; |
| | | b = (b << 23 | b >>> 9) + c << 0; |
| | | bc = b ^ c; |
| | | a += (bc ^ d) + blocks[9] - 640364487; |
| | | a = (a << 4 | a >>> 28) + b << 0; |
| | | d += (bc ^ a) + blocks[12] - 421815835; |
| | | d = (d << 11 | d >>> 21) + a << 0; |
| | | da = d ^ a; |
| | | c += (da ^ b) + blocks[15] + 530742520; |
| | | c = (c << 16 | c >>> 16) + d << 0; |
| | | b += (da ^ c) + blocks[2] - 995338651; |
| | | b = (b << 23 | b >>> 9) + c << 0; |
| | | a += (c ^ (b | ~d)) + blocks[0] - 198630844; |
| | | a = (a << 6 | a >>> 26) + b << 0; |
| | | d += (b ^ (a | ~c)) + blocks[7] + 1126891415; |
| | | d = (d << 10 | d >>> 22) + a << 0; |
| | | c += (a ^ (d | ~b)) + blocks[14] - 1416354905; |
| | | c = (c << 15 | c >>> 17) + d << 0; |
| | | b += (d ^ (c | ~a)) + blocks[5] - 57434055; |
| | | b = (b << 21 | b >>> 11) + c << 0; |
| | | a += (c ^ (b | ~d)) + blocks[12] + 1700485571; |
| | | a = (a << 6 | a >>> 26) + b << 0; |
| | | d += (b ^ (a | ~c)) + blocks[3] - 1894986606; |
| | | d = (d << 10 | d >>> 22) + a << 0; |
| | | c += (a ^ (d | ~b)) + blocks[10] - 1051523; |
| | | c = (c << 15 | c >>> 17) + d << 0; |
| | | b += (d ^ (c | ~a)) + blocks[1] - 2054922799; |
| | | b = (b << 21 | b >>> 11) + c << 0; |
| | | a += (c ^ (b | ~d)) + blocks[8] + 1873313359; |
| | | a = (a << 6 | a >>> 26) + b << 0; |
| | | d += (b ^ (a | ~c)) + blocks[15] - 30611744; |
| | | d = (d << 10 | d >>> 22) + a << 0; |
| | | c += (a ^ (d | ~b)) + blocks[6] - 1560198380; |
| | | c = (c << 15 | c >>> 17) + d << 0; |
| | | b += (d ^ (c | ~a)) + blocks[13] + 1309151649; |
| | | b = (b << 21 | b >>> 11) + c << 0; |
| | | a += (c ^ (b | ~d)) + blocks[4] - 145523070; |
| | | a = (a << 6 | a >>> 26) + b << 0; |
| | | d += (b ^ (a | ~c)) + blocks[11] - 1120210379; |
| | | d = (d << 10 | d >>> 22) + a << 0; |
| | | c += (a ^ (d | ~b)) + blocks[2] + 718787259; |
| | | c = (c << 15 | c >>> 17) + d << 0; |
| | | b += (d ^ (c | ~a)) + blocks[9] - 343485551; |
| | | b = (b << 21 | b >>> 11) + c << 0; |
| | | |
| | | if (this.first) { |
| | | this.h0 = a + 1732584193 << 0; |
| | | this.h1 = b - 271733879 << 0; |
| | | this.h2 = c - 1732584194 << 0; |
| | | this.h3 = d + 271733878 << 0; |
| | | this.first = false; |
| | | } else { |
| | | this.h0 = this.h0 + a << 0; |
| | | this.h1 = this.h1 + b << 0; |
| | | this.h2 = this.h2 + c << 0; |
| | | this.h3 = this.h3 + d << 0; |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * @method hex |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Output hash as hex string |
| | | * @returns {String} Hex string |
| | | * @see {@link md5.hex} |
| | | * @example |
| | | * hash.hex(); |
| | | */ |
| | | Md5.prototype.hex = function () { |
| | | this.finalize(); |
| | | |
| | | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3; |
| | | |
| | | return HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + |
| | | HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + |
| | | HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + |
| | | HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + |
| | | HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + |
| | | HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + |
| | | HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + |
| | | HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + |
| | | HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + |
| | | HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + |
| | | HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + |
| | | HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + |
| | | HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + |
| | | HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + |
| | | HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + |
| | | HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F]; |
| | | }; |
| | | |
| | | /** |
| | | * @method toString |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Output hash as hex string |
| | | * @returns {String} Hex string |
| | | * @see {@link md5.hex} |
| | | * @example |
| | | * hash.toString(); |
| | | */ |
| | | Md5.prototype.toString = Md5.prototype.hex; |
| | | |
| | | /** |
| | | * @method digest |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Output hash as bytes array |
| | | * @returns {Array} Bytes array |
| | | * @see {@link md5.digest} |
| | | * @example |
| | | * hash.digest(); |
| | | */ |
| | | Md5.prototype.digest = function () { |
| | | this.finalize(); |
| | | |
| | | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3; |
| | | return [ |
| | | h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 24) & 0xFF, |
| | | h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 24) & 0xFF, |
| | | h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 24) & 0xFF, |
| | | h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 24) & 0xFF |
| | | ]; |
| | | }; |
| | | |
| | | /** |
| | | * @method array |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Output hash as bytes array |
| | | * @returns {Array} Bytes array |
| | | * @see {@link md5.array} |
| | | * @example |
| | | * hash.array(); |
| | | */ |
| | | Md5.prototype.array = Md5.prototype.digest; |
| | | |
| | | /** |
| | | * @method arrayBuffer |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Output hash as ArrayBuffer |
| | | * @returns {ArrayBuffer} ArrayBuffer |
| | | * @see {@link md5.arrayBuffer} |
| | | * @example |
| | | * hash.arrayBuffer(); |
| | | */ |
| | | Md5.prototype.arrayBuffer = function () { |
| | | this.finalize(); |
| | | |
| | | var buffer = new ArrayBuffer(16); |
| | | var blocks = new Uint32Array(buffer); |
| | | blocks[0] = this.h0; |
| | | blocks[1] = this.h1; |
| | | blocks[2] = this.h2; |
| | | blocks[3] = this.h3; |
| | | return buffer; |
| | | }; |
| | | |
| | | /** |
| | | * @method buffer |
| | | * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead. |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Output hash as ArrayBuffer |
| | | * @returns {ArrayBuffer} ArrayBuffer |
| | | * @see {@link md5.buffer} |
| | | * @example |
| | | * hash.buffer(); |
| | | */ |
| | | Md5.prototype.buffer = Md5.prototype.arrayBuffer; |
| | | |
| | | /** |
| | | * @method base64 |
| | | * @memberof Md5 |
| | | * @instance |
| | | * @description Output hash as base64 string |
| | | * @returns {String} base64 string |
| | | * @see {@link md5.base64} |
| | | * @example |
| | | * hash.base64(); |
| | | */ |
| | | Md5.prototype.base64 = function () { |
| | | var v1, v2, v3, base64Str = '', bytes = this.array(); |
| | | for (var i = 0; i < 15;) { |
| | | v1 = bytes[i++]; |
| | | v2 = bytes[i++]; |
| | | v3 = bytes[i++]; |
| | | base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + |
| | | BASE64_ENCODE_CHAR[(v1 << 4 | v2 >>> 4) & 63] + |
| | | BASE64_ENCODE_CHAR[(v2 << 2 | v3 >>> 6) & 63] + |
| | | BASE64_ENCODE_CHAR[v3 & 63]; |
| | | } |
| | | v1 = bytes[i]; |
| | | base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + |
| | | BASE64_ENCODE_CHAR[(v1 << 4) & 63] + |
| | | '=='; |
| | | return base64Str; |
| | | }; |
| | | |
| | | var exports = createMethod(); |
| | | |
| | | if (COMMON_JS) { |
| | | module.exports = exports; |
| | | } else { |
| | | /** |
| | | * @method md5 |
| | | * @description Md5 hash function, export to global in browsers. |
| | | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash |
| | | * @returns {String} md5 hashes |
| | | * @example |
| | | * md5(''); // d41d8cd98f00b204e9800998ecf8427e |
| | | * md5('The quick brown fox jumps over the lazy dog'); // 9e107d9d372bb6826bd81d3542a419d6 |
| | | * md5('The quick brown fox jumps over the lazy dog.'); // e4d909c290d0fb1ca068ffaddf22cbd0 |
| | | * |
| | | * // It also supports UTF-8 encoding |
| | | * md5('中文'); // a7bac2239fcdcb3a067903d8077c4a07 |
| | | * |
| | | * // It also supports byte `Array`, `Uint8Array`, `ArrayBuffer` |
| | | * md5([]); // d41d8cd98f00b204e9800998ecf8427e |
| | | * md5(new Uint8Array([])); // d41d8cd98f00b204e9800998ecf8427e |
| | | */ |
| | | root.md5 = exports; |
| | | if (AMD) { |
| | | define(function () { |
| | | return exports; |
| | | }); |
| | | } |
| | | } |
| | | })(); |
| New file |
| | |
| | | MIT License |
| | | |
| | | Copyright (c) 2020 www.uviewui.com |
| | | |
| | | Permission is hereby granted, free of charge, to any person obtaining a copy |
| | | of this software and associated documentation files (the "Software"), to deal |
| | | in the Software without restriction, including without limitation the rights |
| | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| | | copies of the Software, and to permit persons to whom the Software is |
| | | furnished to do so, subject to the following conditions: |
| | | |
| | | The above copyright notice and this permission notice shall be included in all |
| | | copies or substantial portions of the Software. |
| | | |
| | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| | | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| | | SOFTWARE. |
| New file |
| | |
| | | <p align="center"> |
| | | <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;"> |
| | | </p> |
| | | <h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3> |
| | | <h3 align="center">多平台快速开发的UI框架</h3> |
| | | |
| | | |
| | | ## 说明 |
| | | |
| | | uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水 |
| | | |
| | | ## 特性 |
| | | |
| | | - 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序 |
| | | - 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用 |
| | | - 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨 |
| | | - 众多的常用页面和布局,让您专注逻辑,事半功倍 |
| | | - 详尽的文档支持,现代化的演示效果 |
| | | - 按需引入,精简打包体积 |
| | | |
| | | |
| | | ## 安装 |
| | | |
| | | ```bash |
| | | # npm方式安装 |
| | | npm i uview-ui |
| | | ``` |
| | | |
| | | ## 快速上手 |
| | | |
| | | 1. `main.js`引入uView库 |
| | | ```js |
| | | // main.js |
| | | import uView from 'uview-ui'; |
| | | Vue.use(uView); |
| | | ``` |
| | | |
| | | 2. `App.vue`引入基础样式(注意style标签需声明scss属性支持) |
| | | ```css |
| | | /* App.vue */ |
| | | <style lang="scss"> |
| | | @import "uview-ui/index.scss"; |
| | | </style> |
| | | ``` |
| | | |
| | | 3. `uni.scss`引入全局scss变量文件 |
| | | ```css |
| | | /* uni.scss */ |
| | | @import "uview-ui/theme.scss"; |
| | | ``` |
| | | |
| | | 4. `pages.json`配置easycom规则(按需引入) |
| | | |
| | | ```js |
| | | // pages.json |
| | | { |
| | | "easycom": { |
| | | // npm安装的方式不需要前面的"@/",下载安装的方式需要"@/" |
| | | // npm安装方式 |
| | | "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" |
| | | // 下载安装方式 |
| | | // "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" |
| | | }, |
| | | // 此为本身已有的内容 |
| | | "pages": [ |
| | | // ...... |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | 请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 |
| | | |
| | | ## 使用方法 |
| | | 配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 |
| | | |
| | | ```html |
| | | <template> |
| | | <u-button>按钮</u-button> |
| | | </template> |
| | | ``` |
| | | |
| | | 请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 |
| | | |
| | | ## 链接 |
| | | |
| | | - [官方文档](https://uviewui.com/) |
| | | - [更新日志](https://uviewui.com/components/changelog.html) |
| | | - [升级指南](https://uviewui.com/components/changelog.html) |
| | | - [关于我们](https://uviewui.com/cooperation/about.html) |
| | | |
| | | ## 预览 |
| | | |
| | | 您可以通过**微信**扫码,查看最佳的演示效果。 |
| | | <br> |
| | | <br> |
| | | <img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" > |
| | | |
| | | <!-- ## 捐赠uView的研发 |
| | | |
| | | uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。 |
| | | |
| | | <img src="https://uviewui.com/common/wechat.png" width="220" > |
| | | <img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" > |
| | | --> |
| | | ## 版权信息 |
| | | uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。 |
| New file |
| | |
| | | <template> |
| | | <u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble" |
| | | length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex"> |
| | | <view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]"> |
| | | {{tips.text}} |
| | | </view> |
| | | <block v-for="(item, index) in list" :key="index"> |
| | | <view |
| | | @touchmove.stop.prevent |
| | | @tap="itemClick(index)" |
| | | :style="[itemStyle(index)]" |
| | | class="u-action-sheet-item u-line-1" |
| | | :class="[index < list.length - 1 ? 'u-border-bottom' : '']" |
| | | :hover-stay-time="150" |
| | | > |
| | | <text>{{item.text}}</text> |
| | | <text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text> |
| | | </view> |
| | | </block> |
| | | <view class="u-gab" v-if="cancelBtn"> |
| | | </view> |
| | | <view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class" |
| | | :hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view> |
| | | </u-popup> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * actionSheet 操作菜单 |
| | | * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。 |
| | | * @tutorial https://www.uviewui.com/components/actionSheet.html |
| | | * @property {Array<Object>} list 按钮的文字数组,见官方文档示例 |
| | | * @property {Object} tips 顶部的提示文字,见官方文档示例 |
| | | * @property {String} cancel-text 取消按钮的提示文字 |
| | | * @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true) |
| | | * @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0) |
| | | * @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true) |
| | | * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false) |
| | | * @property {Number String} z-index z-index值(默认1075) |
| | | * @property {String} cancel-text 取消按钮的提示文字 |
| | | * @event {Function} click 点击ActionSheet列表项时触发 |
| | | * @event {Function} close 点击取消按钮时触发 |
| | | * @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet> |
| | | */ |
| | | export default { |
| | | name: "u-action-sheet", |
| | | props: { |
| | | // 点击遮罩是否可以关闭actionsheet |
| | | maskCloseAble: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx |
| | | list: { |
| | | type: Array, |
| | | default () { |
| | | // 如下 |
| | | // return [{ |
| | | // text: '确定', |
| | | // color: '', |
| | | // fontSize: '' |
| | | // }] |
| | | return []; |
| | | } |
| | | }, |
| | | // 顶部的提示文字 |
| | | tips: { |
| | | type: Object, |
| | | default () { |
| | | return { |
| | | text: '', |
| | | color: '', |
| | | fontSize: '26' |
| | | } |
| | | } |
| | | }, |
| | | // 底部的取消按钮 |
| | | cancelBtn: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 |
| | | safeAreaInsetBottom: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 通过双向绑定控制组件的弹出与收起 |
| | | value: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 弹出的顶部圆角值 |
| | | borderRadius: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 弹出的z-index值 |
| | | zIndex: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 取消按钮的文字提示 |
| | | cancelText: { |
| | | type: String, |
| | | default: '取消' |
| | | } |
| | | }, |
| | | computed: { |
| | | // 顶部提示的样式 |
| | | tipsStyle() { |
| | | let style = {}; |
| | | if (this.tips.color) style.color = this.tips.color; |
| | | if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx'; |
| | | return style; |
| | | }, |
| | | // 操作项目的样式 |
| | | itemStyle() { |
| | | return (index) => { |
| | | let style = {}; |
| | | if (this.list[index].color) style.color = this.list[index].color; |
| | | if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx'; |
| | | // 选项被禁用的样式 |
| | | if (this.list[index].disabled) style.color = '#c0c4cc'; |
| | | return style; |
| | | } |
| | | }, |
| | | uZIndex() { |
| | | // 如果用户有传递z-index值,优先使用 |
| | | return this.zIndex ? this.zIndex : this.$u.zIndex.popup; |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击取消按钮 |
| | | close() { |
| | | // 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数 |
| | | // 这是一个vue发送事件的特殊用法 |
| | | this.popupClose(); |
| | | this.$emit('close'); |
| | | }, |
| | | // 弹窗关闭 |
| | | popupClose() { |
| | | this.$emit('input', false); |
| | | }, |
| | | // 点击某一个item |
| | | itemClick(index) { |
| | | // disabled的项禁止点击 |
| | | if(this.list[index].disabled) return; |
| | | this.$emit('click', index); |
| | | this.$emit('input', false); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-tips { |
| | | font-size: 26rpx; |
| | | text-align: center; |
| | | padding: 34rpx 0; |
| | | line-height: 1; |
| | | color: $u-tips-color; |
| | | } |
| | | |
| | | .u-action-sheet-item { |
| | | @include vue-flex;; |
| | | line-height: 1; |
| | | justify-content: center; |
| | | align-items: center; |
| | | font-size: 32rpx; |
| | | padding: 34rpx 0; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .u-action-sheet-item__subtext { |
| | | font-size: 24rpx; |
| | | color: $u-tips-color; |
| | | margin-top: 20rpx; |
| | | } |
| | | |
| | | .u-gab { |
| | | height: 12rpx; |
| | | background-color: rgb(234, 234, 236); |
| | | } |
| | | |
| | | .u-actionsheet-cancel { |
| | | color: $u-main-color; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-alert-tips" v-if="show" :class="[ |
| | | !show ? 'u-close-alert-tips': '', |
| | | type ? 'u-alert-tips--bg--' + type + '-light' : '', |
| | | type ? 'u-alert-tips--border--' + type + '-disabled' : '', |
| | | ]" :style="{ |
| | | backgroundColor: bgColor, |
| | | borderColor: borderColor |
| | | }"> |
| | | <view class="u-icon-wrap"> |
| | | <u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon> |
| | | </view> |
| | | <view class="u-alert-content" @tap.stop="click"> |
| | | <view class="u-alert-title" :style="[uTitleStyle]"> |
| | | {{title}} |
| | | </view> |
| | | <view v-if="description" class="u-alert-desc" :style="[descStyle]"> |
| | | {{description}} |
| | | </view> |
| | | </view> |
| | | <view class="u-icon-wrap"> |
| | | <u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc" |
| | | :size="22" class="u-close-icon" :style="{ |
| | | top: description ? '18rpx' : '24rpx' |
| | | }"></u-icon> |
| | | </view> |
| | | <text v-if="closeAble && closeText" class="u-close-text" :style="{ |
| | | top: description ? '18rpx' : '24rpx' |
| | | }">{{closeText}}</text> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * alertTips 警告提示 |
| | | * @description 警告提示,展现需要关注的信息 |
| | | * @tutorial https://uviewui.com/components/alertTips.html |
| | | * @property {String} title 显示的标题文字 |
| | | * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选 |
| | | * @property {String} type 关闭按钮(默认为叉号icon图标) |
| | | * @property {String} icon 图标名称 |
| | | * @property {Object} icon-style 图标的样式,对象形式 |
| | | * @property {Object} title-style 标题的样式,对象形式 |
| | | * @property {Object} desc-style 描述的样式,对象形式 |
| | | * @property {String} close-able 用文字替代关闭图标,close-able为true时有效 |
| | | * @property {Boolean} show-icon 是否显示左边的辅助图标 |
| | | * @property {Boolean} show 显示或隐藏组件 |
| | | * @event {Function} click 点击组件时触发 |
| | | * @event {Function} close 点击关闭按钮时触发 |
| | | */ |
| | | export default { |
| | | name: 'u-alert-tips', |
| | | props: { |
| | | // 显示文字 |
| | | title: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 主题,success/warning/info/error |
| | | type: { |
| | | type: String, |
| | | default: 'warning' |
| | | }, |
| | | // 辅助性文字 |
| | | description: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否可关闭 |
| | | closeAble: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 关闭按钮自定义文本 |
| | | closeText: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否显示图标 |
| | | showIcon: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 文字颜色,如果定义了color值,icon会失效 |
| | | color: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 边框颜色 |
| | | borderColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否显示 |
| | | show: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 左边显示的icon |
| | | icon: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // icon的样式 |
| | | iconStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | // 标题的样式 |
| | | titleStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | // 描述文字的样式 |
| | | descStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | } |
| | | }, |
| | | computed: { |
| | | uTitleStyle() { |
| | | let style = {}; |
| | | // 如果有描述文字的话,标题进行加粗 |
| | | style.fontWeight = this.description ? 500 : 'normal'; |
| | | // 将用户传入样式对象和style合并,传入的优先级比style高,同属性会被覆盖 |
| | | return this.$u.deepMerge(style, this.titleStyle); |
| | | }, |
| | | uIcon() { |
| | | // 如果有设置icon名称就使用,否则根据type主题,推定一个默认的图标 |
| | | return this.icon ? this.icon : this.$u.type2icon(this.type); |
| | | }, |
| | | uIconType() { |
| | | // 如果有设置图标的样式,优先使用,没有的话,则用type的样式 |
| | | return Object.keys(this.iconStyle).length ? '' : this.type; |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击内容 |
| | | click() { |
| | | this.$emit('click'); |
| | | }, |
| | | // 点击关闭按钮 |
| | | close() { |
| | | this.$emit('close'); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-alert-tips { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | padding: 16rpx 30rpx; |
| | | border-radius: 8rpx; |
| | | position: relative; |
| | | transition: all 0.3s linear; |
| | | border: 1px solid #fff; |
| | | |
| | | &--bg--primary-light { |
| | | background-color: $u-type-primary-light; |
| | | } |
| | | |
| | | &--bg--info-light { |
| | | background-color: $u-type-info-light; |
| | | } |
| | | |
| | | &--bg--success-light { |
| | | background-color: $u-type-success-light; |
| | | } |
| | | |
| | | &--bg--warning-light { |
| | | background-color: $u-type-warning-light; |
| | | } |
| | | |
| | | &--bg--error-light { |
| | | background-color: $u-type-error-light; |
| | | } |
| | | |
| | | &--border--primary-disabled { |
| | | border-color: $u-type-primary-disabled; |
| | | } |
| | | |
| | | &--border--success-disabled { |
| | | border-color: $u-type-success-disabled; |
| | | } |
| | | |
| | | &--border--error-disabled { |
| | | border-color: $u-type-error-disabled; |
| | | } |
| | | |
| | | &--border--warning-disabled { |
| | | border-color: $u-type-warning-disabled; |
| | | } |
| | | |
| | | &--border--info-disabled { |
| | | border-color: $u-type-info-disabled; |
| | | } |
| | | } |
| | | |
| | | .u-close-alert-tips { |
| | | opacity: 0; |
| | | visibility: hidden; |
| | | } |
| | | |
| | | .u-icon { |
| | | margin-right: 16rpx; |
| | | } |
| | | |
| | | .u-alert-title { |
| | | font-size: 28rpx; |
| | | color: $u-main-color; |
| | | } |
| | | |
| | | .u-alert-desc { |
| | | font-size: 26rpx; |
| | | text-align: left; |
| | | color: $u-content-color; |
| | | } |
| | | |
| | | .u-close-icon { |
| | | position: absolute; |
| | | top: 20rpx; |
| | | right: 20rpx; |
| | | } |
| | | |
| | | .u-close-hover { |
| | | color: red; |
| | | } |
| | | |
| | | .u-close-text { |
| | | font-size: 24rpx; |
| | | color: $u-tips-color; |
| | | position: absolute; |
| | | top: 20rpx; |
| | | right: 20rpx; |
| | | line-height: 1; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="content"> |
| | | <view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }"> |
| | | <canvas |
| | | class="cropper" |
| | | :disable-scroll="true" |
| | | @touchstart="touchStart" |
| | | @touchmove="touchMove" |
| | | @touchend="touchEnd" |
| | | :style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }" |
| | | canvas-id="cropper" |
| | | id="cropper" |
| | | ></canvas> |
| | | <canvas |
| | | class="cropper" |
| | | :disable-scroll="true" |
| | | :style="{ |
| | | position: 'fixed', |
| | | top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`, |
| | | left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`, |
| | | width: `${cropperOpt.width * cropperOpt.pixelRatio}px`, |
| | | height: `${cropperOpt.height * cropperOpt.pixelRatio}` |
| | | }" |
| | | canvas-id="targetId" |
| | | id="targetId" |
| | | ></canvas> |
| | | </view> |
| | | <view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }"> |
| | | <!-- #ifdef H5 --> |
| | | <view class="upload" @tap="uploadTap">选择图片</view> |
| | | <!-- #endif --> |
| | | <!-- #ifndef H5 --> |
| | | <view class="upload" @tap="uploadTap">重新选择</view> |
| | | <!-- #endif --> |
| | | <view class="getCropperImage" @tap="getCropperImage(false)">确定</view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import WeCropper from './weCropper.js'; |
| | | export default { |
| | | props: { |
| | | // 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色, |
| | | // mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)" |
| | | boundStyle: { |
| | | type: Object, |
| | | default() { |
| | | return { |
| | | lineWidth: 4, |
| | | borderColor: 'rgb(245, 245, 245)', |
| | | mask: 'rgba(0, 0, 0, 0.35)' |
| | | }; |
| | | } |
| | | } |
| | | // // 裁剪框宽度,单位rpx |
| | | // rectWidth: { |
| | | // type: [String, Number], |
| | | // default: 400 |
| | | // }, |
| | | // // 裁剪框高度,单位rpx |
| | | // rectHeight: { |
| | | // type: [String, Number], |
| | | // default: 400 |
| | | // }, |
| | | // // 输出图片宽度,单位rpx |
| | | // destWidth: { |
| | | // type: [String, Number], |
| | | // default: 400 |
| | | // }, |
| | | // // 输出图片高度,单位rpx |
| | | // destHeight: { |
| | | // type: [String, Number], |
| | | // default: 400 |
| | | // }, |
| | | // // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可 |
| | | // fileType: { |
| | | // type: String, |
| | | // default: 'jpg', |
| | | // }, |
| | | // // 生成的图片质量 |
| | | // // H5上无效,目前不考虑使用此参数 |
| | | // quality: { |
| | | // type: [Number, String], |
| | | // default: 1 |
| | | // } |
| | | }, |
| | | data() { |
| | | return { |
| | | // 底部导航的高度 |
| | | bottomNavHeight: 50, |
| | | originWidth: 200, |
| | | width: 0, |
| | | height: 0, |
| | | cropperOpt: { |
| | | id: 'cropper', |
| | | targetId: 'targetCropper', |
| | | pixelRatio: 1, |
| | | width: 0, |
| | | height: 0, |
| | | scale: 2.5, |
| | | zoom: 8, |
| | | cut: { |
| | | x: (this.width - this.originWidth) / 2, |
| | | y: (this.height - this.originWidth) / 2, |
| | | width: this.originWidth, |
| | | height: this.originWidth |
| | | }, |
| | | boundStyle: { |
| | | lineWidth: uni.upx2px(this.boundStyle.lineWidth), |
| | | mask: this.boundStyle.mask, |
| | | color: this.boundStyle.borderColor |
| | | } |
| | | }, |
| | | // 裁剪框和输出图片的尺寸,高度默认等于宽度 |
| | | // 输出图片宽度,单位px |
| | | destWidth: 200, |
| | | // 裁剪框宽度,单位px |
| | | rectWidth: 200, |
| | | // 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可 |
| | | fileType: 'jpg', |
| | | src: '', // 选择的图片路径,用于在点击确定时,判断是否选择了图片 |
| | | }; |
| | | }, |
| | | onLoad(option) { |
| | | let rectInfo = uni.getSystemInfoSync(); |
| | | this.width = rectInfo.windowWidth; |
| | | this.height = rectInfo.windowHeight - this.bottomNavHeight; |
| | | this.cropperOpt.width = this.width; |
| | | this.cropperOpt.height = this.height; |
| | | this.cropperOpt.pixelRatio = rectInfo.pixelRatio; |
| | | |
| | | if (option.destWidth) this.destWidth = option.destWidth; |
| | | if (option.rectWidth) { |
| | | let rectWidth = Number(option.rectWidth); |
| | | this.cropperOpt.cut = { |
| | | x: (this.width - rectWidth) / 2, |
| | | y: (this.height - rectWidth) / 2, |
| | | width: rectWidth, |
| | | height: rectWidth |
| | | }; |
| | | } |
| | | this.rectWidth = option.rectWidth; |
| | | if (option.fileType) this.fileType = option.fileType; |
| | | // 初始化 |
| | | this.cropper = new WeCropper(this.cropperOpt) |
| | | .on('ready', ctx => { |
| | | // wecropper is ready for work! |
| | | }) |
| | | .on('beforeImageLoad', ctx => { |
| | | // before picture loaded, i can do something |
| | | }) |
| | | .on('imageLoad', ctx => { |
| | | // picture loaded |
| | | }) |
| | | .on('beforeDraw', (ctx, instance) => { |
| | | // before canvas draw,i can do something |
| | | }); |
| | | // 设置导航栏样式,以免用户在page.json中没有设置为黑色背景 |
| | | uni.setNavigationBarColor({ |
| | | frontColor: '#ffffff', |
| | | backgroundColor: '#000000' |
| | | }); |
| | | uni.chooseImage({ |
| | | count: 1, // 默认9 |
| | | sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有 |
| | | sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 |
| | | success: res => { |
| | | this.src = res.tempFilePaths[0]; |
| | | // 获取裁剪图片资源后,给data添加src属性及其值 |
| | | this.cropper.pushOrign(this.src); |
| | | } |
| | | }); |
| | | }, |
| | | methods: { |
| | | touchStart(e) { |
| | | this.cropper.touchStart(e); |
| | | }, |
| | | touchMove(e) { |
| | | this.cropper.touchMove(e); |
| | | }, |
| | | touchEnd(e) { |
| | | this.cropper.touchEnd(e); |
| | | }, |
| | | getCropperImage(isPre = false) { |
| | | if(!this.src) return this.$u.toast('请先选择图片再裁剪'); |
| | | |
| | | let cropper_opt = { |
| | | destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值 |
| | | destWidth: Number(this.destWidth), |
| | | fileType: this.fileType |
| | | }; |
| | | this.cropper.getCropperImage(cropper_opt, (path, err) => { |
| | | if (err) { |
| | | uni.showModal({ |
| | | title: '温馨提示', |
| | | content: err.message |
| | | }); |
| | | } else { |
| | | if (isPre) { |
| | | uni.previewImage({ |
| | | current: '', // 当前显示图片的 http 链接 |
| | | urls: [path] // 需要预览的图片 http 链接列表 |
| | | }); |
| | | } else { |
| | | uni.$emit('uAvatarCropper', path); |
| | | this.$u.route({ |
| | | type: 'back' |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | uploadTap() { |
| | | const self = this; |
| | | uni.chooseImage({ |
| | | count: 1, // 默认9 |
| | | sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有 |
| | | sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 |
| | | success: (res) => { |
| | | self.src = res.tempFilePaths[0]; |
| | | // 获取裁剪图片资源后,给data添加src属性及其值 |
| | | |
| | | self.cropper.pushOrign(this.src); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '../../libs/css/style.components.scss'; |
| | | |
| | | .content { |
| | | background: rgba(255, 255, 255, 1); |
| | | } |
| | | |
| | | .cropper { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | z-index: 11; |
| | | } |
| | | |
| | | .cropper-buttons { |
| | | background-color: #000000; |
| | | color: #eee; |
| | | } |
| | | |
| | | .cropper-wrapper { |
| | | position: relative; |
| | | @include vue-flex; |
| | | flex-direction: row; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | width: 100%; |
| | | background-color: #000; |
| | | } |
| | | |
| | | .cropper-buttons { |
| | | width: 100vw; |
| | | @include vue-flex; |
| | | flex-direction: row; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .cropper-buttons .upload, |
| | | .cropper-buttons .getCropperImage { |
| | | width: 50%; |
| | | text-align: center; |
| | | } |
| | | |
| | | .cropper-buttons .upload { |
| | | text-align: left; |
| | | padding-left: 50rpx; |
| | | } |
| | | |
| | | .cropper-buttons .getCropperImage { |
| | | text-align: right; |
| | | padding-right: 50rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | /** |
| | | * we-cropper v1.3.9 |
| | | * (c) 2020 dlhandsome |
| | | * @license MIT |
| | | */ |
| | | (function(global, factory) { |
| | | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : |
| | | typeof define === 'function' && define.amd ? define(factory) : |
| | | (global.WeCropper = factory()); |
| | | }(this, (function() { |
| | | 'use strict'; |
| | | |
| | | var device = void 0; |
| | | var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended']; |
| | | |
| | | function firstLetterUpper(str) { |
| | | return str.charAt(0).toUpperCase() + str.slice(1) |
| | | } |
| | | |
| | | function setTouchState(instance) { |
| | | var arg = [], |
| | | len = arguments.length - 1; |
| | | while (len-- > 0) arg[len] = arguments[len + 1]; |
| | | |
| | | TOUCH_STATE.forEach(function(key, i) { |
| | | if (arg[i] !== undefined) { |
| | | instance[key] = arg[i]; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function validator(instance, o) { |
| | | Object.defineProperties(instance, o); |
| | | } |
| | | |
| | | function getDevice() { |
| | | if (!device) { |
| | | device = uni.getSystemInfoSync(); |
| | | } |
| | | return device |
| | | } |
| | | |
| | | var tmp = {}; |
| | | |
| | | var ref = getDevice(); |
| | | var pixelRatio = ref.pixelRatio; |
| | | |
| | | var DEFAULT = { |
| | | id: { |
| | | default: 'cropper', |
| | | get: function get() { |
| | | return tmp.id |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'string') { |
| | | console.error(("id:" + value + " is invalid")); |
| | | } |
| | | tmp.id = value; |
| | | } |
| | | }, |
| | | width: { |
| | | default: 750, |
| | | get: function get() { |
| | | return tmp.width |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'number') { |
| | | console.error(("width:" + value + " is invalid")); |
| | | } |
| | | tmp.width = value; |
| | | } |
| | | }, |
| | | height: { |
| | | default: 750, |
| | | get: function get() { |
| | | return tmp.height |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'number') { |
| | | console.error(("height:" + value + " is invalid")); |
| | | } |
| | | tmp.height = value; |
| | | } |
| | | }, |
| | | pixelRatio: { |
| | | default: pixelRatio, |
| | | get: function get() { |
| | | return tmp.pixelRatio |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'number') { |
| | | console.error(("pixelRatio:" + value + " is invalid")); |
| | | } |
| | | tmp.pixelRatio = value; |
| | | } |
| | | }, |
| | | scale: { |
| | | default: 2.5, |
| | | get: function get() { |
| | | return tmp.scale |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'number') { |
| | | console.error(("scale:" + value + " is invalid")); |
| | | } |
| | | tmp.scale = value; |
| | | } |
| | | }, |
| | | zoom: { |
| | | default: 5, |
| | | get: function get() { |
| | | return tmp.zoom |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'number') { |
| | | console.error(("zoom:" + value + " is invalid")); |
| | | } else if (value < 0 || value > 10) { |
| | | console.error("zoom should be ranged in 0 ~ 10"); |
| | | } |
| | | tmp.zoom = value; |
| | | } |
| | | }, |
| | | src: { |
| | | default: '', |
| | | get: function get() { |
| | | return tmp.src |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'string') { |
| | | console.error(("src:" + value + " is invalid")); |
| | | } |
| | | tmp.src = value; |
| | | } |
| | | }, |
| | | cut: { |
| | | default: {}, |
| | | get: function get() { |
| | | return tmp.cut |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'object') { |
| | | console.error(("cut:" + value + " is invalid")); |
| | | } |
| | | tmp.cut = value; |
| | | } |
| | | }, |
| | | boundStyle: { |
| | | default: {}, |
| | | get: function get() { |
| | | return tmp.boundStyle |
| | | }, |
| | | set: function set(value) { |
| | | if (typeof(value) !== 'object') { |
| | | console.error(("boundStyle:" + value + " is invalid")); |
| | | } |
| | | tmp.boundStyle = value; |
| | | } |
| | | }, |
| | | onReady: { |
| | | default: null, |
| | | get: function get() { |
| | | return tmp.ready |
| | | }, |
| | | set: function set(value) { |
| | | tmp.ready = value; |
| | | } |
| | | }, |
| | | onBeforeImageLoad: { |
| | | default: null, |
| | | get: function get() { |
| | | return tmp.beforeImageLoad |
| | | }, |
| | | set: function set(value) { |
| | | tmp.beforeImageLoad = value; |
| | | } |
| | | }, |
| | | onImageLoad: { |
| | | default: null, |
| | | get: function get() { |
| | | return tmp.imageLoad |
| | | }, |
| | | set: function set(value) { |
| | | tmp.imageLoad = value; |
| | | } |
| | | }, |
| | | onBeforeDraw: { |
| | | default: null, |
| | | get: function get() { |
| | | return tmp.beforeDraw |
| | | }, |
| | | set: function set(value) { |
| | | tmp.beforeDraw = value; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | var ref$1 = getDevice(); |
| | | var windowWidth = ref$1.windowWidth; |
| | | |
| | | function prepare() { |
| | | var self = this; |
| | | |
| | | // v1.4.0 版本中将不再自动绑定we-cropper实例 |
| | | self.attachPage = function() { |
| | | var pages = getCurrentPages(); |
| | | // 获取到当前page上下文 |
| | | var pageContext = pages[pages.length - 1]; |
| | | // 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问 |
| | | Object.defineProperty(pageContext, 'wecropper', { |
| | | get: function get() { |
| | | console.warn( |
| | | 'Instance will not be automatically bound to the page after v1.4.0\n\n' + |
| | | 'Please use a custom instance name instead\n\n' + |
| | | 'Example: \n' + |
| | | 'this.mycropper = new WeCropper(options)\n\n' + |
| | | '// ...\n' + |
| | | 'this.mycropper.getCropperImage()' |
| | | ); |
| | | return self |
| | | }, |
| | | configurable: true |
| | | }); |
| | | }; |
| | | |
| | | self.createCtx = function() { |
| | | var id = self.id; |
| | | var targetId = self.targetId; |
| | | |
| | | if (id) { |
| | | self.ctx = self.ctx || uni.createCanvasContext(id); |
| | | self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId); |
| | | } else { |
| | | console.error("constructor: create canvas context failed, 'id' must be valuable"); |
| | | } |
| | | }; |
| | | |
| | | self.deviceRadio = windowWidth / 750; |
| | | } |
| | | |
| | | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== |
| | | 'undefined' ? self : {}; |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | function createCommonjsModule(fn, module) { |
| | | return module = { |
| | | exports: {} |
| | | }, fn(module, module.exports), module.exports; |
| | | } |
| | | |
| | | var tools = createCommonjsModule(function(module, exports) { |
| | | /** |
| | | * String type check |
| | | */ |
| | | exports.isStr = function(v) { |
| | | return typeof v === 'string'; |
| | | }; |
| | | /** |
| | | * Number type check |
| | | */ |
| | | exports.isNum = function(v) { |
| | | return typeof v === 'number'; |
| | | }; |
| | | /** |
| | | * Array type check |
| | | */ |
| | | exports.isArr = Array.isArray; |
| | | /** |
| | | * undefined type check |
| | | */ |
| | | exports.isUndef = function(v) { |
| | | return v === undefined; |
| | | }; |
| | | |
| | | exports.isTrue = function(v) { |
| | | return v === true; |
| | | }; |
| | | |
| | | exports.isFalse = function(v) { |
| | | return v === false; |
| | | }; |
| | | /** |
| | | * Function type check |
| | | */ |
| | | exports.isFunc = function(v) { |
| | | return typeof v === 'function'; |
| | | }; |
| | | /** |
| | | * Quick object check - this is primarily used to tell |
| | | * Objects from primitive values when we know the value |
| | | * is a JSON-compliant type. |
| | | */ |
| | | exports.isObj = exports.isObject = function(obj) { |
| | | return obj !== null && typeof obj === 'object' |
| | | }; |
| | | |
| | | /** |
| | | * Strict object type check. Only returns true |
| | | * for plain JavaScript objects. |
| | | */ |
| | | var _toString = Object.prototype.toString; |
| | | exports.isPlainObject = function(obj) { |
| | | return _toString.call(obj) === '[object Object]' |
| | | }; |
| | | |
| | | /** |
| | | * Check whether the object has the property. |
| | | */ |
| | | var hasOwnProperty = Object.prototype.hasOwnProperty; |
| | | exports.hasOwn = function(obj, key) { |
| | | return hasOwnProperty.call(obj, key) |
| | | }; |
| | | |
| | | /** |
| | | * Perform no operation. |
| | | * Stubbing args to make Flow happy without leaving useless transpiled code |
| | | * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) |
| | | */ |
| | | exports.noop = function(a, b, c) {}; |
| | | |
| | | /** |
| | | * Check if val is a valid array index. |
| | | */ |
| | | exports.isValidArrayIndex = function(val) { |
| | | var n = parseFloat(String(val)); |
| | | return n >= 0 && Math.floor(n) === n && isFinite(val) |
| | | }; |
| | | }); |
| | | |
| | | var tools_7 = tools.isFunc; |
| | | var tools_10 = tools.isPlainObject; |
| | | |
| | | var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad']; |
| | | |
| | | function observer() { |
| | | var self = this; |
| | | |
| | | self.on = function(event, fn) { |
| | | if (EVENT_TYPE.indexOf(event) > -1) { |
| | | if (tools_7(fn)) { |
| | | event === 'ready' ? |
| | | fn(self) : |
| | | self[("on" + (firstLetterUpper(event)))] = fn; |
| | | } |
| | | } else { |
| | | console.error(("event: " + event + " is invalid")); |
| | | } |
| | | return self |
| | | }; |
| | | } |
| | | |
| | | function wxPromise(fn) { |
| | | return function(obj) { |
| | | var args = [], |
| | | len = arguments.length - 1; |
| | | while (len-- > 0) args[len] = arguments[len + 1]; |
| | | |
| | | if (obj === void 0) obj = {}; |
| | | return new Promise(function(resolve, reject) { |
| | | obj.success = function(res) { |
| | | resolve(res); |
| | | }; |
| | | obj.fail = function(err) { |
| | | reject(err); |
| | | }; |
| | | fn.apply(void 0, [obj].concat(args)); |
| | | }) |
| | | } |
| | | } |
| | | |
| | | function draw(ctx, reserve) { |
| | | if (reserve === void 0) reserve = false; |
| | | |
| | | return new Promise(function(resolve) { |
| | | ctx.draw(reserve, resolve); |
| | | }) |
| | | } |
| | | |
| | | var getImageInfo = wxPromise(uni.getImageInfo); |
| | | |
| | | var canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath); |
| | | |
| | | var base64 = createCommonjsModule(function(module, exports) { |
| | | /*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */ |
| | | (function(root) { |
| | | |
| | | // Detect free variables `exports`. |
| | | var freeExports = 'object' == 'object' && exports; |
| | | |
| | | // Detect free variable `module`. |
| | | var freeModule = 'object' == 'object' && module && |
| | | module.exports == freeExports && module; |
| | | |
| | | // Detect free variable `global`, from Node.js or Browserified code, and use |
| | | // it as `root`. |
| | | var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal; |
| | | if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { |
| | | root = freeGlobal; |
| | | } |
| | | |
| | | /*--------------------------------------------------------------------------*/ |
| | | |
| | | var InvalidCharacterError = function(message) { |
| | | this.message = message; |
| | | }; |
| | | InvalidCharacterError.prototype = new Error; |
| | | InvalidCharacterError.prototype.name = 'InvalidCharacterError'; |
| | | |
| | | var error = function(message) { |
| | | // Note: the error messages used throughout this file match those used by |
| | | // the native `atob`/`btoa` implementation in Chromium. |
| | | throw new InvalidCharacterError(message); |
| | | }; |
| | | |
| | | var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; |
| | | // http://whatwg.org/html/common-microsyntaxes.html#space-character |
| | | var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g; |
| | | |
| | | // `decode` is designed to be fully compatible with `atob` as described in the |
| | | // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob |
| | | // The optimized base64-decoding algorithm used is based on @atk’s excellent |
| | | // implementation. https://gist.github.com/atk/1020396 |
| | | var decode = function(input) { |
| | | input = String(input) |
| | | .replace(REGEX_SPACE_CHARACTERS, ''); |
| | | var length = input.length; |
| | | if (length % 4 == 0) { |
| | | input = input.replace(/==?$/, ''); |
| | | length = input.length; |
| | | } |
| | | if ( |
| | | length % 4 == 1 || |
| | | // http://whatwg.org/C#alphanumeric-ascii-characters |
| | | /[^+a-zA-Z0-9/]/.test(input) |
| | | ) { |
| | | error( |
| | | 'Invalid character: the string to be decoded is not correctly encoded.' |
| | | ); |
| | | } |
| | | var bitCounter = 0; |
| | | var bitStorage; |
| | | var buffer; |
| | | var output = ''; |
| | | var position = -1; |
| | | while (++position < length) { |
| | | buffer = TABLE.indexOf(input.charAt(position)); |
| | | bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer; |
| | | // Unless this is the first of a group of 4 characters… |
| | | if (bitCounter++ % 4) { |
| | | // …convert the first 8 bits to a single ASCII character. |
| | | output += String.fromCharCode( |
| | | 0xFF & bitStorage >> (-2 * bitCounter & 6) |
| | | ); |
| | | } |
| | | } |
| | | return output; |
| | | }; |
| | | |
| | | // `encode` is designed to be fully compatible with `btoa` as described in the |
| | | // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa |
| | | var encode = function(input) { |
| | | input = String(input); |
| | | if (/[^\0-\xFF]/.test(input)) { |
| | | // Note: no need to special-case astral symbols here, as surrogates are |
| | | // matched, and the input is supposed to only contain ASCII anyway. |
| | | error( |
| | | 'The string to be encoded contains characters outside of the ' + |
| | | 'Latin1 range.' |
| | | ); |
| | | } |
| | | var padding = input.length % 3; |
| | | var output = ''; |
| | | var position = -1; |
| | | var a; |
| | | var b; |
| | | var c; |
| | | var buffer; |
| | | // Make sure any padding is handled outside of the loop. |
| | | var length = input.length - padding; |
| | | |
| | | while (++position < length) { |
| | | // Read three bytes, i.e. 24 bits. |
| | | a = input.charCodeAt(position) << 16; |
| | | b = input.charCodeAt(++position) << 8; |
| | | c = input.charCodeAt(++position); |
| | | buffer = a + b + c; |
| | | // Turn the 24 bits into four chunks of 6 bits each, and append the |
| | | // matching character for each of them to the output. |
| | | output += ( |
| | | TABLE.charAt(buffer >> 18 & 0x3F) + |
| | | TABLE.charAt(buffer >> 12 & 0x3F) + |
| | | TABLE.charAt(buffer >> 6 & 0x3F) + |
| | | TABLE.charAt(buffer & 0x3F) |
| | | ); |
| | | } |
| | | |
| | | if (padding == 2) { |
| | | a = input.charCodeAt(position) << 8; |
| | | b = input.charCodeAt(++position); |
| | | buffer = a + b; |
| | | output += ( |
| | | TABLE.charAt(buffer >> 10) + |
| | | TABLE.charAt((buffer >> 4) & 0x3F) + |
| | | TABLE.charAt((buffer << 2) & 0x3F) + |
| | | '=' |
| | | ); |
| | | } else if (padding == 1) { |
| | | buffer = input.charCodeAt(position); |
| | | output += ( |
| | | TABLE.charAt(buffer >> 2) + |
| | | TABLE.charAt((buffer << 4) & 0x3F) + |
| | | '==' |
| | | ); |
| | | } |
| | | |
| | | return output; |
| | | }; |
| | | |
| | | var base64 = { |
| | | 'encode': encode, |
| | | 'decode': decode, |
| | | 'version': '0.1.0' |
| | | }; |
| | | |
| | | // Some AMD build optimizers, like r.js, check for specific condition patterns |
| | | // like the following: |
| | | if ( |
| | | typeof undefined == 'function' && |
| | | typeof undefined.amd == 'object' && |
| | | undefined.amd |
| | | ) { |
| | | undefined(function() { |
| | | return base64; |
| | | }); |
| | | } else if (freeExports && !freeExports.nodeType) { |
| | | if (freeModule) { // in Node.js or RingoJS v0.8.0+ |
| | | freeModule.exports = base64; |
| | | } else { // in Narwhal or RingoJS v0.7.0- |
| | | for (var key in base64) { |
| | | base64.hasOwnProperty(key) && (freeExports[key] = base64[key]); |
| | | } |
| | | } |
| | | } else { // in Rhino or a web browser |
| | | root.base64 = base64; |
| | | } |
| | | |
| | | }(commonjsGlobal)); |
| | | }); |
| | | |
| | | function makeURI(strData, type) { |
| | | return 'data:' + type + ';base64,' + strData |
| | | } |
| | | |
| | | function fixType(type) { |
| | | type = type.toLowerCase().replace(/jpg/i, 'jpeg'); |
| | | var r = type.match(/png|jpeg|bmp|gif/)[0]; |
| | | return 'image/' + r |
| | | } |
| | | |
| | | function encodeData(data) { |
| | | var str = ''; |
| | | if (typeof data === 'string') { |
| | | str = data; |
| | | } else { |
| | | for (var i = 0; i < data.length; i++) { |
| | | str += String.fromCharCode(data[i]); |
| | | } |
| | | } |
| | | return base64.encode(str) |
| | | } |
| | | |
| | | /** |
| | | * 获取图像区域隐含的像素数据 |
| | | * @param canvasId canvas标识 |
| | | * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标 |
| | | * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标 |
| | | * @param width 将要被提取的图像数据矩形区域的宽度 |
| | | * @param height 将要被提取的图像数据矩形区域的高度 |
| | | * @param done 完成回调 |
| | | */ |
| | | function getImageData(canvasId, x, y, width, height, done) { |
| | | uni.canvasGetImageData({ |
| | | canvasId: canvasId, |
| | | x: x, |
| | | y: y, |
| | | width: width, |
| | | height: height, |
| | | success: function success(res) { |
| | | done(res, null); |
| | | }, |
| | | fail: function fail(res) { |
| | | done(null, res); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 生成bmp格式图片 |
| | | * 按照规则生成图片响应头和响应体 |
| | | * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData |
| | | * @returns {*} base64字符串 |
| | | */ |
| | | function genBitmapImage(oData) { |
| | | // |
| | | // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx |
| | | // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx |
| | | // |
| | | var biWidth = oData.width; |
| | | var biHeight = oData.height; |
| | | var biSizeImage = biWidth * biHeight * 3; |
| | | var bfSize = biSizeImage + 54; // total header size = 54 bytes |
| | | |
| | | // |
| | | // typedef struct tagBITMAPFILEHEADER { |
| | | // WORD bfType; |
| | | // DWORD bfSize; |
| | | // WORD bfReserved1; |
| | | // WORD bfReserved2; |
| | | // DWORD bfOffBits; |
| | | // } BITMAPFILEHEADER; |
| | | // |
| | | var BITMAPFILEHEADER = [ |
| | | // WORD bfType -- The file type signature; must be "BM" |
| | | 0x42, 0x4D, |
| | | // DWORD bfSize -- The size, in bytes, of the bitmap file |
| | | bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff, |
| | | // WORD bfReserved1 -- Reserved; must be zero |
| | | 0, 0, |
| | | // WORD bfReserved2 -- Reserved; must be zero |
| | | 0, 0, |
| | | // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits. |
| | | 54, 0, 0, 0 |
| | | ]; |
| | | |
| | | // |
| | | // typedef struct tagBITMAPINFOHEADER { |
| | | // DWORD biSize; |
| | | // LONG biWidth; |
| | | // LONG biHeight; |
| | | // WORD biPlanes; |
| | | // WORD biBitCount; |
| | | // DWORD biCompression; |
| | | // DWORD biSizeImage; |
| | | // LONG biXPelsPerMeter; |
| | | // LONG biYPelsPerMeter; |
| | | // DWORD biClrUsed; |
| | | // DWORD biClrImportant; |
| | | // } BITMAPINFOHEADER, *PBITMAPINFOHEADER; |
| | | // |
| | | var BITMAPINFOHEADER = [ |
| | | // DWORD biSize -- The number of bytes required by the structure |
| | | 40, 0, 0, 0, |
| | | // LONG biWidth -- The width of the bitmap, in pixels |
| | | biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff, |
| | | // LONG biHeight -- The height of the bitmap, in pixels |
| | | biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff, |
| | | // WORD biPlanes -- The number of planes for the target device. This value must be set to 1 |
| | | 1, 0, |
| | | // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap |
| | | // has a maximum of 2^24 colors (16777216, Truecolor) |
| | | 24, 0, |
| | | // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed |
| | | 0, 0, 0, 0, |
| | | // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps |
| | | biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff, |
| | | // LONG biXPelsPerMeter, unused |
| | | 0, 0, 0, 0, |
| | | // LONG biYPelsPerMeter, unused |
| | | 0, 0, 0, 0, |
| | | // DWORD biClrUsed, the number of color indexes of palette, unused |
| | | 0, 0, 0, 0, |
| | | // DWORD biClrImportant, unused |
| | | 0, 0, 0, 0 |
| | | ]; |
| | | |
| | | var iPadding = (4 - ((biWidth * 3) % 4)) % 4; |
| | | |
| | | var aImgData = oData.data; |
| | | |
| | | var strPixelData = ''; |
| | | var biWidth4 = biWidth << 2; |
| | | var y = biHeight; |
| | | var fromCharCode = String.fromCharCode; |
| | | |
| | | do { |
| | | var iOffsetY = biWidth4 * (y - 1); |
| | | var strPixelRow = ''; |
| | | for (var x = 0; x < biWidth; x++) { |
| | | var iOffsetX = x << 2; |
| | | strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) + |
| | | fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) + |
| | | fromCharCode(aImgData[iOffsetY + iOffsetX]); |
| | | } |
| | | |
| | | for (var c = 0; c < iPadding; c++) { |
| | | strPixelRow += String.fromCharCode(0); |
| | | } |
| | | |
| | | strPixelData += strPixelRow; |
| | | } while (--y) |
| | | |
| | | var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData); |
| | | |
| | | return strEncoded |
| | | } |
| | | |
| | | /** |
| | | * 转换为图片base64 |
| | | * @param canvasId canvas标识 |
| | | * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标 |
| | | * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标 |
| | | * @param width 将要被提取的图像数据矩形区域的宽度 |
| | | * @param height 将要被提取的图像数据矩形区域的高度 |
| | | * @param type 转换图片类型 |
| | | * @param done 完成回调 |
| | | */ |
| | | function convertToImage(canvasId, x, y, width, height, type, done) { |
| | | if (done === void 0) done = function() {}; |
| | | |
| | | if (type === undefined) { |
| | | type = 'png'; |
| | | } |
| | | type = fixType(type); |
| | | if (/bmp/.test(type)) { |
| | | getImageData(canvasId, x, y, width, height, function(data, err) { |
| | | var strData = genBitmapImage(data); |
| | | tools_7(done) && done(makeURI(strData, 'image/' + type), err); |
| | | }); |
| | | } else { |
| | | console.error('暂不支持生成\'' + type + '\'类型的base64图片'); |
| | | } |
| | | } |
| | | |
| | | var CanvasToBase64 = { |
| | | convertToImage: convertToImage, |
| | | // convertToPNG: function (width, height, done) { |
| | | // return convertToImage(width, height, 'png', done) |
| | | // }, |
| | | // convertToJPEG: function (width, height, done) { |
| | | // return convertToImage(width, height, 'jpeg', done) |
| | | // }, |
| | | // convertToGIF: function (width, height, done) { |
| | | // return convertToImage(width, height, 'gif', done) |
| | | // }, |
| | | convertToBMP: function(ref, done) { |
| | | if (ref === void 0) ref = {}; |
| | | var canvasId = ref.canvasId; |
| | | var x = ref.x; |
| | | var y = ref.y; |
| | | var width = ref.width; |
| | | var height = ref.height; |
| | | if (done === void 0) done = function() {}; |
| | | |
| | | return convertToImage(canvasId, x, y, width, height, 'bmp', done) |
| | | } |
| | | }; |
| | | |
| | | function methods() { |
| | | var self = this; |
| | | |
| | | var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度 |
| | | var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度 |
| | | |
| | | var id = self.id; |
| | | var targetId = self.targetId; |
| | | var pixelRatio = self.pixelRatio; |
| | | |
| | | var ref = self.cut; |
| | | var x = ref.x; |
| | | if (x === void 0) x = 0; |
| | | var y = ref.y; |
| | | if (y === void 0) y = 0; |
| | | var width = ref.width; |
| | | if (width === void 0) width = boundWidth; |
| | | var height = ref.height; |
| | | if (height === void 0) height = boundHeight; |
| | | |
| | | self.updateCanvas = function(done) { |
| | | if (self.croperTarget) { |
| | | // 画布绘制图片 |
| | | self.ctx.drawImage( |
| | | self.croperTarget, |
| | | self.imgLeft, |
| | | self.imgTop, |
| | | self.scaleWidth, |
| | | self.scaleHeight |
| | | ); |
| | | } |
| | | tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self); |
| | | |
| | | self.setBoundStyle(self.boundStyle); // 设置边界样式 |
| | | |
| | | self.ctx.draw(false, done); |
| | | return self |
| | | }; |
| | | |
| | | self.pushOrigin = self.pushOrign = function(src) { |
| | | self.src = src; |
| | | |
| | | tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self); |
| | | |
| | | return getImageInfo({ |
| | | src: src |
| | | }) |
| | | .then(function(res) { |
| | | var innerAspectRadio = res.width / res.height; |
| | | var customAspectRadio = width / height; |
| | | |
| | | self.croperTarget = res.path; |
| | | |
| | | if (innerAspectRadio < customAspectRadio) { |
| | | self.rectX = x; |
| | | self.baseWidth = width; |
| | | self.baseHeight = width / innerAspectRadio; |
| | | self.rectY = y - Math.abs((height - self.baseHeight) / 2); |
| | | } else { |
| | | self.rectY = y; |
| | | self.baseWidth = height * innerAspectRadio; |
| | | self.baseHeight = height; |
| | | self.rectX = x - Math.abs((width - self.baseWidth) / 2); |
| | | } |
| | | |
| | | self.imgLeft = self.rectX; |
| | | self.imgTop = self.rectY; |
| | | self.scaleWidth = self.baseWidth; |
| | | self.scaleHeight = self.baseHeight; |
| | | |
| | | self.update(); |
| | | |
| | | return new Promise(function(resolve) { |
| | | self.updateCanvas(resolve); |
| | | }) |
| | | }) |
| | | .then(function() { |
| | | tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self); |
| | | }) |
| | | }; |
| | | |
| | | self.removeImage = function() { |
| | | self.src = ''; |
| | | self.croperTarget = ''; |
| | | return draw(self.ctx) |
| | | }; |
| | | |
| | | self.getCropperBase64 = function(done) { |
| | | if (done === void 0) done = function() {}; |
| | | |
| | | CanvasToBase64.convertToBMP({ |
| | | canvasId: id, |
| | | x: x, |
| | | y: y, |
| | | width: width, |
| | | height: height |
| | | }, done); |
| | | }; |
| | | |
| | | self.getCropperImage = function(opt, fn) { |
| | | var customOptions = opt; |
| | | |
| | | var canvasOptions = { |
| | | canvasId: id, |
| | | x: x, |
| | | y: y, |
| | | width: width, |
| | | height: height |
| | | }; |
| | | |
| | | var task = function() { |
| | | return Promise.resolve(); |
| | | }; |
| | | |
| | | if ( |
| | | tools_10(customOptions) && |
| | | customOptions.original |
| | | ) { |
| | | // original mode |
| | | task = function() { |
| | | self.targetCtx.drawImage( |
| | | self.croperTarget, |
| | | self.imgLeft * pixelRatio, |
| | | self.imgTop * pixelRatio, |
| | | self.scaleWidth * pixelRatio, |
| | | self.scaleHeight * pixelRatio |
| | | ); |
| | | |
| | | canvasOptions = { |
| | | canvasId: targetId, |
| | | x: x * pixelRatio, |
| | | y: y * pixelRatio, |
| | | width: width * pixelRatio, |
| | | height: height * pixelRatio |
| | | }; |
| | | |
| | | return draw(self.targetCtx) |
| | | }; |
| | | } |
| | | |
| | | return task() |
| | | .then(function() { |
| | | if (tools_10(customOptions)) { |
| | | canvasOptions = Object.assign({}, canvasOptions, customOptions); |
| | | } |
| | | |
| | | if (tools_7(customOptions)) { |
| | | fn = customOptions; |
| | | } |
| | | |
| | | var arg = canvasOptions.componentContext ? |
| | | [canvasOptions, canvasOptions.componentContext] : |
| | | [canvasOptions]; |
| | | |
| | | return canvasToTempFilePath.apply(null, arg) |
| | | }) |
| | | .then(function(res) { |
| | | var tempFilePath = res.tempFilePath; |
| | | |
| | | return tools_7(fn) ? |
| | | fn.call(self, tempFilePath, null) : |
| | | tempFilePath |
| | | }) |
| | | .catch(function(err) { |
| | | if (tools_7(fn)) { |
| | | fn.call(self, null, err); |
| | | } else { |
| | | throw err |
| | | } |
| | | }) |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * 获取最新缩放值 |
| | | * @param oldScale 上一次触摸结束后的缩放值 |
| | | * @param oldDistance 上一次触摸结束后的双指距离 |
| | | * @param zoom 缩放系数 |
| | | * @param touch0 第一指touch对象 |
| | | * @param touch1 第二指touch对象 |
| | | * @returns {*} |
| | | */ |
| | | var getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1) { |
| | | var xMove, yMove, newDistance; |
| | | // 计算二指最新距离 |
| | | xMove = Math.round(touch1.x - touch0.x); |
| | | yMove = Math.round(touch1.y - touch0.y); |
| | | newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove)); |
| | | |
| | | return oldScale + 0.001 * zoom * (newDistance - oldDistance) |
| | | }; |
| | | |
| | | function update() { |
| | | var self = this; |
| | | |
| | | if (!self.src) { |
| | | return |
| | | } |
| | | |
| | | self.__oneTouchStart = function(touch) { |
| | | self.touchX0 = Math.round(touch.x); |
| | | self.touchY0 = Math.round(touch.y); |
| | | }; |
| | | |
| | | self.__oneTouchMove = function(touch) { |
| | | var xMove, yMove; |
| | | // 计算单指移动的距离 |
| | | if (self.touchended) { |
| | | return self.updateCanvas() |
| | | } |
| | | xMove = Math.round(touch.x - self.touchX0); |
| | | yMove = Math.round(touch.y - self.touchY0); |
| | | |
| | | var imgLeft = Math.round(self.rectX + xMove); |
| | | var imgTop = Math.round(self.rectY + yMove); |
| | | |
| | | self.outsideBound(imgLeft, imgTop); |
| | | |
| | | self.updateCanvas(); |
| | | }; |
| | | |
| | | self.__twoTouchStart = function(touch0, touch1) { |
| | | var xMove, yMove, oldDistance; |
| | | |
| | | self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2); |
| | | self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2); |
| | | |
| | | // 计算两指距离 |
| | | xMove = Math.round(touch1.x - touch0.x); |
| | | yMove = Math.round(touch1.y - touch0.y); |
| | | oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove)); |
| | | |
| | | self.oldDistance = oldDistance; |
| | | }; |
| | | |
| | | self.__twoTouchMove = function(touch0, touch1) { |
| | | var oldScale = self.oldScale; |
| | | var oldDistance = self.oldDistance; |
| | | var scale = self.scale; |
| | | var zoom = self.zoom; |
| | | |
| | | self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1); |
| | | |
| | | // 设定缩放范围 |
| | | self.newScale <= 1 && (self.newScale = 1); |
| | | self.newScale >= scale && (self.newScale = scale); |
| | | |
| | | self.scaleWidth = Math.round(self.newScale * self.baseWidth); |
| | | self.scaleHeight = Math.round(self.newScale * self.baseHeight); |
| | | var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2); |
| | | var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2); |
| | | |
| | | self.outsideBound(imgLeft, imgTop); |
| | | |
| | | self.updateCanvas(); |
| | | }; |
| | | |
| | | self.__xtouchEnd = function() { |
| | | self.oldScale = self.newScale; |
| | | self.rectX = self.imgLeft; |
| | | self.rectY = self.imgTop; |
| | | }; |
| | | } |
| | | |
| | | var handle = { |
| | | // 图片手势初始监测 |
| | | touchStart: function touchStart(e) { |
| | | var self = this; |
| | | var ref = e.touches; |
| | | var touch0 = ref[0]; |
| | | var touch1 = ref[1]; |
| | | |
| | | if (!self.src) { |
| | | return |
| | | } |
| | | |
| | | setTouchState(self, true, null, null); |
| | | |
| | | // 计算第一个触摸点的位置,并参照改点进行缩放 |
| | | self.__oneTouchStart(touch0); |
| | | |
| | | // 两指手势触发 |
| | | if (e.touches.length >= 2) { |
| | | self.__twoTouchStart(touch0, touch1); |
| | | } |
| | | }, |
| | | |
| | | // 图片手势动态缩放 |
| | | touchMove: function touchMove(e) { |
| | | var self = this; |
| | | var ref = e.touches; |
| | | var touch0 = ref[0]; |
| | | var touch1 = ref[1]; |
| | | |
| | | if (!self.src) { |
| | | return |
| | | } |
| | | |
| | | setTouchState(self, null, true); |
| | | |
| | | // 单指手势时触发 |
| | | if (e.touches.length === 1) { |
| | | self.__oneTouchMove(touch0); |
| | | } |
| | | // 两指手势触发 |
| | | if (e.touches.length >= 2) { |
| | | self.__twoTouchMove(touch0, touch1); |
| | | } |
| | | }, |
| | | |
| | | touchEnd: function touchEnd(e) { |
| | | var self = this; |
| | | |
| | | if (!self.src) { |
| | | return |
| | | } |
| | | |
| | | setTouchState(self, false, false, true); |
| | | self.__xtouchEnd(); |
| | | } |
| | | }; |
| | | |
| | | function cut() { |
| | | var self = this; |
| | | var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度 |
| | | var boundHeight = self.height; |
| | | // 裁剪框默认高度,即整个画布高度 |
| | | var ref = self.cut; |
| | | var x = ref.x; |
| | | if (x === void 0) x = 0; |
| | | var y = ref.y; |
| | | if (y === void 0) y = 0; |
| | | var width = ref.width; |
| | | if (width === void 0) width = boundWidth; |
| | | var height = ref.height; |
| | | if (height === void 0) height = boundHeight; |
| | | |
| | | /** |
| | | * 设置边界 |
| | | * @param imgLeft 图片左上角横坐标值 |
| | | * @param imgTop 图片左上角纵坐标值 |
| | | */ |
| | | self.outsideBound = function(imgLeft, imgTop) { |
| | | self.imgLeft = imgLeft >= x ? |
| | | x : |
| | | self.scaleWidth + imgLeft - x <= width ? |
| | | x + width - self.scaleWidth : |
| | | imgLeft; |
| | | |
| | | self.imgTop = imgTop >= y ? |
| | | y : |
| | | self.scaleHeight + imgTop - y <= height ? |
| | | y + height - self.scaleHeight : |
| | | imgTop; |
| | | }; |
| | | |
| | | /** |
| | | * 设置边界样式 |
| | | * @param color 边界颜色 |
| | | */ |
| | | self.setBoundStyle = function(ref) { |
| | | if (ref === void 0) ref = {}; |
| | | var color = ref.color; |
| | | if (color === void 0) color = '#04b00f'; |
| | | var mask = ref.mask; |
| | | if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)'; |
| | | var lineWidth = ref.lineWidth; |
| | | if (lineWidth === void 0) lineWidth = 1; |
| | | |
| | | var half = lineWidth / 2; |
| | | var boundOption = [{ |
| | | start: { |
| | | x: x - half, |
| | | y: y + 10 - half |
| | | }, |
| | | step1: { |
| | | x: x - half, |
| | | y: y - half |
| | | }, |
| | | step2: { |
| | | x: x + 10 - half, |
| | | y: y - half |
| | | } |
| | | }, |
| | | { |
| | | start: { |
| | | x: x - half, |
| | | y: y + height - 10 + half |
| | | }, |
| | | step1: { |
| | | x: x - half, |
| | | y: y + height + half |
| | | }, |
| | | step2: { |
| | | x: x + 10 - half, |
| | | y: y + height + half |
| | | } |
| | | }, |
| | | { |
| | | start: { |
| | | x: x + width - 10 + half, |
| | | y: y - half |
| | | }, |
| | | step1: { |
| | | x: x + width + half, |
| | | y: y - half |
| | | }, |
| | | step2: { |
| | | x: x + width + half, |
| | | y: y + 10 - half |
| | | } |
| | | }, |
| | | { |
| | | start: { |
| | | x: x + width + half, |
| | | y: y + height - 10 + half |
| | | }, |
| | | step1: { |
| | | x: x + width + half, |
| | | y: y + height + half |
| | | }, |
| | | step2: { |
| | | x: x + width - 10 + half, |
| | | y: y + height + half |
| | | } |
| | | } |
| | | ]; |
| | | |
| | | // 绘制半透明层 |
| | | self.ctx.beginPath(); |
| | | self.ctx.setFillStyle(mask); |
| | | self.ctx.fillRect(0, 0, x, boundHeight); |
| | | self.ctx.fillRect(x, 0, width, y); |
| | | self.ctx.fillRect(x, y + height, width, boundHeight - y - height); |
| | | self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight); |
| | | self.ctx.fill(); |
| | | |
| | | boundOption.forEach(function(op) { |
| | | self.ctx.beginPath(); |
| | | self.ctx.setStrokeStyle(color); |
| | | self.ctx.setLineWidth(lineWidth); |
| | | self.ctx.moveTo(op.start.x, op.start.y); |
| | | self.ctx.lineTo(op.step1.x, op.step1.y); |
| | | self.ctx.lineTo(op.step2.x, op.step2.y); |
| | | self.ctx.stroke(); |
| | | }); |
| | | }; |
| | | } |
| | | |
| | | var version = "1.3.9"; |
| | | |
| | | var WeCropper = function WeCropper(params) { |
| | | var self = this; |
| | | var _default = {}; |
| | | |
| | | validator(self, DEFAULT); |
| | | |
| | | Object.keys(DEFAULT).forEach(function(key) { |
| | | _default[key] = DEFAULT[key].default; |
| | | }); |
| | | Object.assign(self, _default, params); |
| | | |
| | | self.prepare(); |
| | | self.attachPage(); |
| | | self.createCtx(); |
| | | self.observer(); |
| | | self.cutt(); |
| | | self.methods(); |
| | | self.init(); |
| | | self.update(); |
| | | |
| | | return self |
| | | }; |
| | | |
| | | WeCropper.prototype.init = function init() { |
| | | var self = this; |
| | | var src = self.src; |
| | | |
| | | self.version = version; |
| | | |
| | | typeof self.onReady === 'function' && self.onReady(self.ctx, self); |
| | | |
| | | if (src) { |
| | | self.pushOrign(src); |
| | | } else { |
| | | self.updateCanvas(); |
| | | } |
| | | setTouchState(self, false, false, false); |
| | | |
| | | self.oldScale = 1; |
| | | self.newScale = 1; |
| | | |
| | | return self |
| | | }; |
| | | |
| | | Object.assign(WeCropper.prototype, handle); |
| | | |
| | | WeCropper.prototype.prepare = prepare; |
| | | WeCropper.prototype.observer = observer; |
| | | WeCropper.prototype.methods = methods; |
| | | WeCropper.prototype.cutt = cut; |
| | | WeCropper.prototype.update = update; |
| | | |
| | | return WeCropper; |
| | | |
| | | }))); |
| New file |
| | |
| | | <template> |
| | | <view class="u-avatar" :style="[wrapStyle]" @tap="click"> |
| | | <image |
| | | @error="loadError" |
| | | :style="[imgStyle]" |
| | | class="u-avatar__img" |
| | | v-if="!uText && avatar" |
| | | :src="avatar" |
| | | :mode="imgMode" |
| | | ></image> |
| | | <text class="u-line-1" v-else-if="uText" :style="{ |
| | | fontSize: '38rpx' |
| | | }">{{uText}}</text> |
| | | <slot v-else></slot> |
| | | <view class="u-avatar__sex" v-if="showSex" :class="['u-avatar__sex--' + sexIcon]" :style="[uSexStyle]"> |
| | | <u-icon :name="sexIcon" size="20"></u-icon> |
| | | </view> |
| | | <view class="u-avatar__level" v-if="showLevel" :style="[uLevelStyle]"> |
| | | <u-icon :name="levelIcon" size="20"></u-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | let base64Avatar = "data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z"; |
| | | /** |
| | | * avatar 头像 |
| | | * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 |
| | | * @tutorial https://www.uviewui.com/components/avatar.html |
| | | * @property {String} bg-color 背景颜色,一般显示文字时用(默认#ffffff) |
| | | * @property {String} src 头像路径,如加载失败,将会显示默认头像 |
| | | * @property {String Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值,单位rpx(默认default) |
| | | * @property {String} mode 显示类型,见上方说明(默认circle) |
| | | * @property {String} sex-icon 性别图标,man-男,woman-女(默认man) |
| | | * @property {String} level-icon 等级图标(默认level) |
| | | * @property {String} sex-bg-color 性别图标背景颜色 |
| | | * @property {String} level-bg-color 等级图标背景颜色 |
| | | * @property {String} show-sex 是否显示性别图标(默认false) |
| | | * @property {String} show-level 是否显示等级图标(默认false) |
| | | * @property {String} img-mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值(默认aspectFill) |
| | | * @property {String} index 用户传递的标识符值,如果是列表循环,可穿v-for的index值 |
| | | * @event {Function} click 头像被点击 |
| | | * @example <u-avatar :src="src"></u-avatar> |
| | | */ |
| | | export default { |
| | | name: 'u-avatar', |
| | | props: { |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: 'transparent' |
| | | }, |
| | | // 头像路径 |
| | | src: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 尺寸,large-大,default-中等,mini-小,如果为数值,则单位为rpx |
| | | // 宽度等于高度 |
| | | size: { |
| | | type: [String, Number], |
| | | default: 'default' |
| | | }, |
| | | // 头像模型,square-带圆角方形,circle-圆形 |
| | | mode: { |
| | | type: String, |
| | | default: 'circle' |
| | | }, |
| | | // 文字内容 |
| | | text: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 图片的裁剪模型 |
| | | imgMode: { |
| | | type: String, |
| | | default: 'aspectFill' |
| | | }, |
| | | // 标识符 |
| | | index: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 右上角性别角标,man-男,woman-女 |
| | | sexIcon: { |
| | | type: String, |
| | | default: 'man' |
| | | }, |
| | | // 右下角的等级图标 |
| | | levelIcon: { |
| | | type: String, |
| | | default: 'level' |
| | | }, |
| | | // 右下角等级图标背景颜色 |
| | | levelBgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 右上角性别图标的背景颜色 |
| | | sexBgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否显示性别图标 |
| | | showSex: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示等级图标 |
| | | showLevel: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | error: false, |
| | | // 头像的地址,因为如果加载错误,需要赋值为默认图片,props值无法修改,所以需要一个中间值 |
| | | avatar: this.src ? this.src : base64Avatar, |
| | | } |
| | | }, |
| | | watch: { |
| | | src(n) { |
| | | // 用户可能会在头像加载失败时,再次修改头像值,所以需要重新赋值 |
| | | if(!n) { |
| | | // 如果传入null或者'',或者undefined,显示默认头像 |
| | | this.avatar = base64Avatar; |
| | | this.error = true; |
| | | } else { |
| | | this.avatar = n; |
| | | this.error = false; |
| | | } |
| | | } |
| | | }, |
| | | computed: { |
| | | wrapStyle() { |
| | | let style = {}; |
| | | style.height = this.size == 'large' ? '120rpx' : this.size == 'default' ? |
| | | '90rpx' : this.size == 'mini' ? '70rpx' : this.size + 'rpx'; |
| | | style.width = style.height; |
| | | style.flex = `0 0 ${style.height}`; |
| | | style.backgroundColor = this.bgColor; |
| | | style.borderRadius = this.mode == 'circle' ? '500px' : '5px'; |
| | | if(this.text) style.padding = `0 6rpx`; |
| | | return style; |
| | | }, |
| | | imgStyle() { |
| | | let style = {}; |
| | | style.borderRadius = this.mode == 'circle' ? '500px' : '5px'; |
| | | return style; |
| | | }, |
| | | // 取字符串的第一个字符 |
| | | uText() { |
| | | return String(this.text)[0]; |
| | | }, |
| | | // 性别图标的自定义样式 |
| | | uSexStyle() { |
| | | let style = {}; |
| | | if(this.sexBgColor) style.backgroundColor = this.sexBgColor; |
| | | return style; |
| | | }, |
| | | // 等级图标的自定义样式 |
| | | uLevelStyle() { |
| | | let style = {}; |
| | | if(this.levelBgColor) style.backgroundColor = this.levelBgColor; |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | // 图片加载错误时,显示默认头像 |
| | | loadError() { |
| | | this.error = true; |
| | | this.avatar = base64Avatar; |
| | | }, |
| | | click() { |
| | | this.$emit('click', this.index); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-avatar { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 28rpx; |
| | | color: $u-content-color; |
| | | border-radius: 10px; |
| | | position: relative; |
| | | |
| | | &__img { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__sex { |
| | | position: absolute; |
| | | width: 32rpx; |
| | | color: #ffffff; |
| | | height: 32rpx; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | border-radius: 100rpx; |
| | | top: 5%; |
| | | z-index: 1; |
| | | right: -7%; |
| | | border: 1px #ffffff solid; |
| | | |
| | | &--man { |
| | | background-color: $u-type-primary; |
| | | } |
| | | |
| | | &--woman { |
| | | background-color: $u-type-error; |
| | | } |
| | | |
| | | &--none { |
| | | background-color: $u-type-warning; |
| | | } |
| | | } |
| | | |
| | | &__level { |
| | | position: absolute; |
| | | width: 32rpx; |
| | | color: #ffffff; |
| | | height: 32rpx; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | border-radius: 100rpx; |
| | | bottom: 5%; |
| | | z-index: 1; |
| | | right: -7%; |
| | | border: 1px #ffffff solid; |
| | | background-color: $u-type-warning; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{ |
| | | bottom: bottom + 'rpx', |
| | | right: right + 'rpx', |
| | | borderRadius: mode == 'circle' ? '10000rpx' : '8rpx', |
| | | zIndex: uZIndex, |
| | | opacity: opacity |
| | | }, customStyle]"> |
| | | <view class="u-back-top__content" v-if="!$slots.default && !$slots.$default"> |
| | | <u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon> |
| | | <view class="u-back-top__content__tips"> |
| | | {{tips}} |
| | | </view> |
| | | </view> |
| | | <slot v-else /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'u-back-top', |
| | | props: { |
| | | // 返回顶部的形状,circle-圆形,square-方形 |
| | | mode: { |
| | | type: String, |
| | | default: 'circle' |
| | | }, |
| | | // 自定义图标 |
| | | icon: { |
| | | type: String, |
| | | default: 'arrow-upward' |
| | | }, |
| | | // 提示文字 |
| | | tips: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 返回顶部滚动时间 |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 100 |
| | | }, |
| | | // 滚动距离 |
| | | scrollTop: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 距离顶部多少距离显示,单位rpx |
| | | top: { |
| | | type: [Number, String], |
| | | default: 400 |
| | | }, |
| | | // 返回顶部按钮到底部的距离,单位rpx |
| | | bottom: { |
| | | type: [Number, String], |
| | | default: 200 |
| | | }, |
| | | // 返回顶部按钮到右边的距离,单位rpx |
| | | right: { |
| | | type: [Number, String], |
| | | default: 40 |
| | | }, |
| | | // 层级 |
| | | zIndex: { |
| | | type: [Number, String], |
| | | default: '9' |
| | | }, |
| | | // 图标的样式,对象形式 |
| | | iconStyle: { |
| | | type: Object, |
| | | default() { |
| | | return { |
| | | color: '#909399', |
| | | fontSize: '38rpx' |
| | | } |
| | | } |
| | | }, |
| | | // 整个组件的样式 |
| | | customStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | } |
| | | }, |
| | | watch: { |
| | | showBackTop(nVal, oVal) { |
| | | // 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度 |
| | | // 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果 |
| | | if(nVal) { |
| | | this.uZIndex = this.zIndex; |
| | | this.opacity = 1; |
| | | } else { |
| | | this.uZIndex = -1; |
| | | this.opacity = 0; |
| | | } |
| | | } |
| | | }, |
| | | computed: { |
| | | showBackTop() { |
| | | // 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值 |
| | | // 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮 |
| | | return this.scrollTop > uni.upx2px(this.top); |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | // 不透明度,为了让组件有一个显示和隐藏的过渡动画 |
| | | opacity: 0, |
| | | // 组件的z-index值,隐藏时设置为-1,就会看不到 |
| | | uZIndex: -1 |
| | | } |
| | | }, |
| | | methods: { |
| | | backToTop() { |
| | | uni.pageScrollTo({ |
| | | scrollTop: 0, |
| | | duration: this.duration |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-back-top { |
| | | width: 80rpx; |
| | | height: 80rpx; |
| | | position: fixed; |
| | | z-index: 9; |
| | | @include vue-flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | background-color: #E1E1E1; |
| | | color: $u-content-color; |
| | | align-items: center; |
| | | transition: opacity 0.4s; |
| | | |
| | | &__content { |
| | | @include vue-flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | &__tips { |
| | | font-size: 24rpx; |
| | | transform: scale(0.8); |
| | | line-height: 1; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view v-if="show" class="u-badge" :class="[ |
| | | isDot ? 'u-badge-dot' : '', |
| | | size == 'mini' ? 'u-badge-mini' : '', |
| | | type ? 'u-badge--bg--' + type : '' |
| | | ]" :style="[{ |
| | | top: offset[0] + 'rpx', |
| | | right: offset[1] + 'rpx', |
| | | fontSize: fontSize + 'rpx', |
| | | position: absolute ? 'absolute' : 'static', |
| | | color: color, |
| | | backgroundColor: bgColor |
| | | }, boxStyle]" |
| | | > |
| | | {{showText}} |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * badge 角标 |
| | | * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 |
| | | * @tutorial https://www.uviewui.com/components/badge.html |
| | | * @property {String Number} count 展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为0且show-zero为false时隐藏 |
| | | * @property {Boolean} is-dot 不展示数字,只有一个小点(默认false) |
| | | * @property {Boolean} absolute 组件是否绝对定位,为true时,offset参数才有效(默认true) |
| | | * @property {String Number} overflow-count 展示封顶的数字值(默认99) |
| | | * @property {String} type 使用预设的背景颜色(默认error) |
| | | * @property {Boolean} show-zero 当数值为 0 时,是否展示 Badge(默认false) |
| | | * @property {String} size Badge的尺寸,设为mini会得到小一号的Badge(默认default) |
| | | * @property {Array} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,单位rpx。absolute为true时有效(默认[20, 20]) |
| | | * @property {String} color 字体颜色(默认#ffffff) |
| | | * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效 |
| | | * @property {Boolean} is-center 组件中心点是否和父组件右上角重合,优先级比offset高,如设置,offset参数会失效(默认false) |
| | | * @example <u-badge type="error" count="7"></u-badge> |
| | | */ |
| | | export default { |
| | | name: 'u-badge', |
| | | props: { |
| | | // primary,warning,success,error,info |
| | | type: { |
| | | type: String, |
| | | default: 'error' |
| | | }, |
| | | // default, mini |
| | | size: { |
| | | type: String, |
| | | default: 'default' |
| | | }, |
| | | //是否是圆点 |
| | | isDot: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 显示的数值内容 |
| | | count: { |
| | | type: [Number, String], |
| | | }, |
| | | // 展示封顶的数字值 |
| | | overflowCount: { |
| | | type: Number, |
| | | default: 99 |
| | | }, |
| | | // 当数值为 0 时,是否展示 Badge |
| | | showZero: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 位置偏移 |
| | | offset: { |
| | | type: Array, |
| | | default: () => { |
| | | return [20, 20] |
| | | } |
| | | }, |
| | | // 是否开启绝对定位,开启了offset才会起作用 |
| | | absolute: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 字体大小 |
| | | fontSize: { |
| | | type: [String, Number], |
| | | default: '24' |
| | | }, |
| | | // 字体演示 |
| | | color: { |
| | | type: String, |
| | | default: '#ffffff' |
| | | }, |
| | | // badge的背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否让badge组件的中心点和父组件右上角重合,配置的话,offset将会失效 |
| | | isCenter: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | computed: { |
| | | // 是否将badge中心与父组件右上角重合 |
| | | boxStyle() { |
| | | let style = {}; |
| | | if(this.isCenter) { |
| | | style.top = 0; |
| | | style.right = 0; |
| | | // Y轴-50%,意味着badge向上移动了badge自身高度一半,X轴50%,意味着向右移动了自身宽度一半 |
| | | style.transform = "translateY(-50%) translateX(50%)"; |
| | | } else { |
| | | style.top = this.offset[0] + 'rpx'; |
| | | style.right = this.offset[1] + 'rpx'; |
| | | style.transform = "translateY(0) translateX(0)"; |
| | | } |
| | | // 如果尺寸为mini,后接上scal() |
| | | if(this.size == 'mini') { |
| | | style.transform = style.transform + " scale(0.8)"; |
| | | } |
| | | return style; |
| | | }, |
| | | // isDot类型时,不显示文字 |
| | | showText() { |
| | | if(this.isDot) return ''; |
| | | else { |
| | | if(this.count > this.overflowCount) return `${this.overflowCount}+`; |
| | | else return this.count; |
| | | } |
| | | }, |
| | | // 是否显示组件 |
| | | show() { |
| | | // 如果count的值为0,并且showZero设置为false,不显示组件 |
| | | if(this.count == 0 && this.showZero == false) return false; |
| | | else return true; |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-badge { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | justify-content: center; |
| | | align-items: center; |
| | | line-height: 24rpx; |
| | | padding: 4rpx 8rpx; |
| | | border-radius: 100rpx; |
| | | z-index: 9; |
| | | |
| | | &--bg--primary { |
| | | background-color: $u-type-primary; |
| | | } |
| | | |
| | | &--bg--error { |
| | | background-color: $u-type-error; |
| | | } |
| | | |
| | | &--bg--success { |
| | | background-color: $u-type-success; |
| | | } |
| | | |
| | | &--bg--info { |
| | | background-color: $u-type-info; |
| | | } |
| | | |
| | | &--bg--warning { |
| | | background-color: $u-type-warning; |
| | | } |
| | | } |
| | | |
| | | .u-badge-dot { |
| | | height: 16rpx; |
| | | width: 16rpx; |
| | | border-radius: 100rpx; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .u-badge-mini { |
| | | transform: scale(0.8); |
| | | transform-origin: center center; |
| | | } |
| | | |
| | | // .u-primary { |
| | | // background: $u-type-primary; |
| | | // color: #fff; |
| | | // } |
| | | |
| | | // .u-error { |
| | | // background: $u-type-error; |
| | | // color: #fff; |
| | | // } |
| | | |
| | | // .u-warning { |
| | | // background: $u-type-warning; |
| | | // color: #fff; |
| | | // } |
| | | |
| | | // .u-success { |
| | | // background: $u-type-success; |
| | | // color: #fff; |
| | | // } |
| | | |
| | | // .u-black { |
| | | // background: #585858; |
| | | // color: #fff; |
| | | // } |
| | | |
| | | .u-info { |
| | | background-color: $u-type-info; |
| | | color: #fff; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <button |
| | | id="u-wave-btn" |
| | | class="u-btn u-line-1 u-fix-ios-appearance" |
| | | :class="[ |
| | | 'u-size-' + size, |
| | | plain ? 'u-btn--' + type + '--plain' : '', |
| | | loading ? 'u-loading' : '', |
| | | shape == 'circle' ? 'u-round-circle' : '', |
| | | hairLine ? showHairLineBorder : 'u-btn--bold-border', |
| | | 'u-btn--' + type, |
| | | disabled ? `u-btn--${type}--disabled` : '', |
| | | ]" |
| | | :hover-start-time="Number(hoverStartTime)" |
| | | :hover-stay-time="Number(hoverStayTime)" |
| | | :disabled="disabled" |
| | | :form-type="formType" |
| | | :open-type="openType" |
| | | :app-parameter="appParameter" |
| | | :hover-stop-propagation="hoverStopPropagation" |
| | | :send-message-title="sendMessageTitle" |
| | | send-message-path="sendMessagePath" |
| | | :lang="lang" |
| | | :data-name="dataName" |
| | | :session-from="sessionFrom" |
| | | :send-message-img="sendMessageImg" |
| | | :show-message-card="showMessageCard" |
| | | @getphonenumber="getphonenumber" |
| | | @getuserinfo="getuserinfo" |
| | | @error="error" |
| | | @opensetting="opensetting" |
| | | @launchapp="launchapp" |
| | | :style="[customStyle, { |
| | | overflow: ripple ? 'hidden' : 'visible' |
| | | }]" |
| | | @tap.stop="click($event)" |
| | | :hover-class="getHoverClass" |
| | | :loading="loading" |
| | | > |
| | | <slot></slot> |
| | | <view |
| | | v-if="ripple" |
| | | class="u-wave-ripple" |
| | | :class="[waveActive ? 'u-wave-active' : '']" |
| | | :style="{ |
| | | top: rippleTop + 'px', |
| | | left: rippleLeft + 'px', |
| | | width: fields.targetWidth + 'px', |
| | | height: fields.targetWidth + 'px', |
| | | 'background-color': rippleBgColor || 'rgba(0, 0, 0, 0.15)' |
| | | }" |
| | | ></view> |
| | | </button> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * button 按钮 |
| | | * @description Button 按钮 |
| | | * @tutorial https://www.uviewui.com/components/button.html |
| | | * @property {String} size 按钮的大小 |
| | | * @property {Boolean} ripple 是否开启点击水波纹效果 |
| | | * @property {String} ripple-bg-color 水波纹的背景色,ripple为true时有效 |
| | | * @property {String} type 按钮的样式类型 |
| | | * @property {Boolean} plain 按钮是否镂空,背景色透明 |
| | | * @property {Boolean} disabled 是否禁用 |
| | | * @property {Boolean} hair-line 是否显示按钮的细边框(默认true) |
| | | * @property {Boolean} shape 按钮外观形状,见文档说明 |
| | | * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) |
| | | * @property {String} form-type 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 |
| | | * @property {String} open-type 开放能力 |
| | | * @property {String} data-name 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 |
| | | * @property {String} hover-class 指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果(App-nvue 平台暂不支持) |
| | | * @property {Number} hover-start-time 按住后多久出现点击态,单位毫秒 |
| | | * @property {Number} hover-stay-time 手指松开后点击态保留时间,单位毫秒 |
| | | * @property {Object} custom-style 对按钮的自定义样式,对象形式,见文档说明 |
| | | * @event {Function} click 按钮点击 |
| | | * @event {Function} getphonenumber open-type="getPhoneNumber"时有效 |
| | | * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo |
| | | * @event {Function} error 当使用开放能力时,发生错误的回调 |
| | | * @event {Function} opensetting 在打开授权设置页并关闭后回调 |
| | | * @event {Function} launchapp 打开 APP 成功的回调 |
| | | * @example <u-button>月落</u-button> |
| | | */ |
| | | export default { |
| | | name: 'u-button', |
| | | props: { |
| | | // 是否细边框 |
| | | hairLine: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 按钮的预置样式,default,primary,error,warning,success |
| | | type: { |
| | | type: String, |
| | | default: 'default' |
| | | }, |
| | | // 按钮尺寸,default,medium,mini |
| | | size: { |
| | | type: String, |
| | | default: 'default' |
| | | }, |
| | | // 按钮形状,circle(两边为半圆),square(带圆角) |
| | | shape: { |
| | | type: String, |
| | | default: 'square' |
| | | }, |
| | | // 按钮是否镂空 |
| | | plain: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否禁止状态 |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否加载中 |
| | | loading: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 开放能力,具体请看uniapp稳定关于button组件部分说明 |
| | | // https://uniapp.dcloud.io/component/button |
| | | openType: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 |
| | | // 取值为submit(提交表单),reset(重置表单) |
| | | formType: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 |
| | | // 只微信小程序、QQ小程序有效 |
| | | appParameter: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效 |
| | | hoverStopPropagation: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效 |
| | | lang: { |
| | | type: String, |
| | | default: 'en' |
| | | }, |
| | | // 会话来源,open-type="contact"时有效。只微信小程序有效 |
| | | sessionFrom: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 会话内消息卡片标题,open-type="contact"时有效 |
| | | // 默认当前标题,只微信小程序有效 |
| | | sendMessageTitle: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效 |
| | | // 默认当前分享路径,只微信小程序有效 |
| | | sendMessagePath: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 会话内消息卡片图片,open-type="contact"时有效 |
| | | // 默认当前页面截图,只微信小程序有效 |
| | | sendMessageImg: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示, |
| | | // 用户点击后可以快速发送小程序消息,open-type="contact"时有效 |
| | | showMessageCard: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 手指按(触摸)按钮时按钮时的背景颜色 |
| | | hoverBgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 水波纹的背景颜色 |
| | | rippleBgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否开启水波纹效果 |
| | | ripple: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 按下的类名 |
| | | hoverClass: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 自定义样式,对象形式 |
| | | customStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 |
| | | dataName: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 节流,一定时间内只能触发一次 |
| | | throttleTime: { |
| | | type: [String, Number], |
| | | default: 1000 |
| | | }, |
| | | // 按住后多久出现点击态,单位毫秒 |
| | | hoverStartTime: { |
| | | type: [String, Number], |
| | | default: 20 |
| | | }, |
| | | // 手指松开后点击态保留时间,单位毫秒 |
| | | hoverStayTime: { |
| | | type: [String, Number], |
| | | default: 150 |
| | | }, |
| | | }, |
| | | computed: { |
| | | // 当没有传bgColor变量时,按钮按下去的颜色类名 |
| | | getHoverClass() { |
| | | // 如果开启水波纹效果,则不启用hover-class效果 |
| | | if (this.loading || this.disabled || this.ripple || this.hoverClass) return ''; |
| | | let hoverClass = ''; |
| | | hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover'; |
| | | return hoverClass; |
| | | }, |
| | | // 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象 |
| | | showHairLineBorder() { |
| | | if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) { |
| | | return ''; |
| | | } else { |
| | | return 'u-hairline-border'; |
| | | } |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离 |
| | | rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离 |
| | | fields: {}, // 波纹按钮节点信息 |
| | | waveActive: false // 激活水波纹 |
| | | }; |
| | | }, |
| | | methods: { |
| | | // 按钮点击 |
| | | click(e) { |
| | | // 进行节流控制,每this.throttle毫秒内,只在开始处执行 |
| | | this.$u.throttle(() => { |
| | | // 如果按钮时disabled和loading状态,不触发水波纹效果 |
| | | if (this.loading === true || this.disabled === true) return; |
| | | // 是否开启水波纹效果 |
| | | if (this.ripple) { |
| | | // 每次点击时,移除上一次的类,再次添加,才能触发动画效果 |
| | | this.waveActive = false; |
| | | this.$nextTick(function() { |
| | | this.getWaveQuery(e); |
| | | }); |
| | | } |
| | | this.$emit('click', e); |
| | | }, this.throttleTime); |
| | | }, |
| | | // 查询按钮的节点信息 |
| | | getWaveQuery(e) { |
| | | this.getElQuery().then(res => { |
| | | // 查询返回的是一个数组节点 |
| | | let data = res[0]; |
| | | // 查询不到节点信息,不操作 |
| | | if (!data.width || !data.width) return; |
| | | // 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边 |
| | | // 最终的方形(变换后的圆形)才能覆盖整个按钮 |
| | | data.targetWidth = data.height > data.width ? data.height : data.width; |
| | | if (!data.targetWidth) return; |
| | | this.fields = data; |
| | | let touchesX = '', |
| | | touchesY = ''; |
| | | // #ifdef MP-BAIDU |
| | | touchesX = e.changedTouches[0].clientX; |
| | | touchesY = e.changedTouches[0].clientY; |
| | | // #endif |
| | | // #ifdef MP-ALIPAY |
| | | touchesX = e.detail.clientX; |
| | | touchesY = e.detail.clientY; |
| | | // #endif |
| | | // #ifndef MP-BAIDU || MP-ALIPAY |
| | | touchesX = e.touches[0].clientX; |
| | | touchesY = e.touches[0].clientY; |
| | | // #endif |
| | | // 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top |
| | | // 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置 |
| | | // 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置 |
| | | this.rippleTop = touchesY - data.top - data.targetWidth / 2; |
| | | this.rippleLeft = touchesX - data.left - data.targetWidth / 2; |
| | | this.$nextTick(() => { |
| | | this.waveActive = true; |
| | | }); |
| | | }); |
| | | }, |
| | | // 获取节点信息 |
| | | getElQuery() { |
| | | return new Promise(resolve => { |
| | | let queryInfo = ''; |
| | | // 获取元素节点信息,请查看uniapp相关文档 |
| | | // https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect |
| | | queryInfo = uni.createSelectorQuery().in(this); |
| | | //#ifdef MP-ALIPAY |
| | | queryInfo = uni.createSelectorQuery(); |
| | | //#endif |
| | | queryInfo.select('.u-btn').boundingClientRect(); |
| | | queryInfo.exec(data => { |
| | | resolve(data); |
| | | }); |
| | | }); |
| | | }, |
| | | // 下面为对接uniapp官方按钮开放能力事件回调的对接 |
| | | getphonenumber(res) { |
| | | this.$emit('getphonenumber', res); |
| | | }, |
| | | getuserinfo(res) { |
| | | this.$emit('getuserinfo', res); |
| | | }, |
| | | error(res) { |
| | | this.$emit('error', res); |
| | | }, |
| | | opensetting(res) { |
| | | this.$emit('opensetting', res); |
| | | }, |
| | | launchapp(res) { |
| | | this.$emit('launchapp', res); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '../../libs/css/style.components.scss'; |
| | | .u-btn::after { |
| | | border: none; |
| | | } |
| | | |
| | | .u-btn { |
| | | position: relative; |
| | | border: 0; |
| | | //border-radius: 10rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | // 避免边框某些场景可能被“裁剪”,不能设置为hidden |
| | | overflow: visible; |
| | | line-height: 1; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | padding: 0 40rpx; |
| | | z-index: 1; |
| | | box-sizing: border-box; |
| | | transition: all 0.15s; |
| | | |
| | | &--bold-border { |
| | | border: 1px solid #ffffff; |
| | | } |
| | | |
| | | &--default { |
| | | color: $u-content-color; |
| | | border-color: #c0c4cc; |
| | | background-color: #ffffff; |
| | | } |
| | | |
| | | &--primary { |
| | | color: #ffffff; |
| | | border-color: $u-type-primary; |
| | | background-color: $u-type-primary; |
| | | } |
| | | |
| | | &--success { |
| | | color: #ffffff; |
| | | border-color: $u-type-success; |
| | | background-color: $u-type-success; |
| | | } |
| | | |
| | | &--error { |
| | | color: #ffffff; |
| | | border-color: $u-type-error; |
| | | background-color: $u-type-error; |
| | | } |
| | | |
| | | &--warning { |
| | | color: #ffffff; |
| | | border-color: $u-type-warning; |
| | | background-color: $u-type-warning; |
| | | } |
| | | |
| | | &--default--disabled { |
| | | color: #ffffff; |
| | | border-color: #e4e7ed; |
| | | background-color: #ffffff; |
| | | } |
| | | |
| | | &--primary--disabled { |
| | | color: #ffffff!important; |
| | | border-color: $u-type-primary-disabled!important; |
| | | background-color: $u-type-primary-disabled!important; |
| | | } |
| | | |
| | | &--success--disabled { |
| | | color: #ffffff!important; |
| | | border-color: $u-type-success-disabled!important; |
| | | background-color: $u-type-success-disabled!important; |
| | | } |
| | | |
| | | &--error--disabled { |
| | | color: #ffffff!important; |
| | | border-color: $u-type-error-disabled!important; |
| | | background-color: $u-type-error-disabled!important; |
| | | } |
| | | |
| | | &--warning--disabled { |
| | | color: #ffffff!important; |
| | | border-color: $u-type-warning-disabled!important; |
| | | background-color: $u-type-warning-disabled!important; |
| | | } |
| | | |
| | | &--primary--plain { |
| | | color: $u-type-primary!important; |
| | | border-color: $u-type-primary-disabled!important; |
| | | background-color: $u-type-primary-light!important; |
| | | } |
| | | |
| | | &--success--plain { |
| | | color: $u-type-success!important; |
| | | border-color: $u-type-success-disabled!important; |
| | | background-color: $u-type-success-light!important; |
| | | } |
| | | |
| | | &--error--plain { |
| | | color: $u-type-error!important; |
| | | border-color: $u-type-error-disabled!important; |
| | | background-color: $u-type-error-light!important; |
| | | } |
| | | |
| | | &--warning--plain { |
| | | color: $u-type-warning!important; |
| | | border-color: $u-type-warning-disabled!important; |
| | | background-color: $u-type-warning-light!important; |
| | | } |
| | | } |
| | | |
| | | .u-hairline-border:after { |
| | | content: ' '; |
| | | position: absolute; |
| | | pointer-events: none; |
| | | // 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border) |
| | | box-sizing: border-box; |
| | | // 中心点作为变形(scale())的原点 |
| | | -webkit-transform-origin: 0 0; |
| | | transform-origin: 0 0; |
| | | left: 0; |
| | | top: 0; |
| | | width: 199.8%; |
| | | height: 199.7%; |
| | | -webkit-transform: scale(0.5, 0.5); |
| | | transform: scale(0.5, 0.5); |
| | | border: 1px solid currentColor; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .u-wave-ripple { |
| | | z-index: 0; |
| | | position: absolute; |
| | | border-radius: 100%; |
| | | background-clip: padding-box; |
| | | pointer-events: none; |
| | | user-select: none; |
| | | transform: scale(0); |
| | | opacity: 1; |
| | | transform-origin: center; |
| | | } |
| | | |
| | | .u-wave-ripple.u-wave-active { |
| | | opacity: 0; |
| | | transform: scale(2); |
| | | transition: opacity 1s linear, transform 0.4s linear; |
| | | } |
| | | |
| | | .u-round-circle { |
| | | border-radius: 100rpx; |
| | | } |
| | | |
| | | .u-round-circle::after { |
| | | border-radius: 100rpx; |
| | | } |
| | | |
| | | .u-loading::after { |
| | | background-color: hsla(0, 0%, 100%, 0.35); |
| | | } |
| | | |
| | | .u-size-default { |
| | | font-size: 30rpx; |
| | | height: 80rpx; |
| | | line-height: 80rpx; |
| | | } |
| | | |
| | | .u-size-medium { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | width: auto; |
| | | font-size: 26rpx; |
| | | height: 70rpx; |
| | | line-height: 70rpx; |
| | | padding: 0 80rpx; |
| | | } |
| | | |
| | | .u-size-mini { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | width: auto; |
| | | font-size: 22rpx; |
| | | padding-top: 1px; |
| | | height: 50rpx; |
| | | line-height: 50rpx; |
| | | padding: 0 20rpx; |
| | | } |
| | | |
| | | .u-primary-plain-hover { |
| | | color: #ffffff !important; |
| | | background: $u-type-primary-dark !important; |
| | | } |
| | | |
| | | .u-default-plain-hover { |
| | | color: $u-type-primary-dark !important; |
| | | background: $u-type-primary-light !important; |
| | | } |
| | | |
| | | .u-success-plain-hover { |
| | | color: #ffffff !important; |
| | | background: $u-type-success-dark !important; |
| | | } |
| | | |
| | | .u-warning-plain-hover { |
| | | color: #ffffff !important; |
| | | background: $u-type-warning-dark !important; |
| | | } |
| | | |
| | | .u-error-plain-hover { |
| | | color: #ffffff !important; |
| | | background: $u-type-error-dark !important; |
| | | } |
| | | |
| | | .u-info-plain-hover { |
| | | color: #ffffff !important; |
| | | background: $u-type-info-dark !important; |
| | | } |
| | | |
| | | .u-default-hover { |
| | | color: $u-type-primary-dark !important; |
| | | border-color: $u-type-primary-dark !important; |
| | | background-color: $u-type-primary-light !important; |
| | | } |
| | | |
| | | .u-primary-hover { |
| | | background: $u-type-primary-dark !important; |
| | | color: #fff; |
| | | } |
| | | |
| | | .u-success-hover { |
| | | background: $u-type-success-dark !important; |
| | | color: #fff; |
| | | } |
| | | |
| | | .u-info-hover { |
| | | background: $u-type-info-dark !important; |
| | | color: #fff; |
| | | } |
| | | |
| | | .u-warning-hover { |
| | | background: $u-type-warning-dark !important; |
| | | color: #fff; |
| | | } |
| | | |
| | | .u-error-hover { |
| | | background: $u-type-error-dark !important; |
| | | color: #fff; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" |
| | | :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable"> |
| | | <view class="u-calendar"> |
| | | <view class="u-calendar__header"> |
| | | <view class="u-calendar__header__text" v-if="!$slots['tooltip']"> |
| | | {{toolTip}} |
| | | </view> |
| | | <slot v-else name="tooltip" /> |
| | | </view> |
| | | <view class="u-calendar__action u-flex u-row-center"> |
| | | <view class="u-calendar__action__icon"> |
| | | <u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon> |
| | | </view> |
| | | <view class="u-calendar__action__icon"> |
| | | <u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon> |
| | | </view> |
| | | <view class="u-calendar__action__text">{{ showTitle }}</view> |
| | | <view class="u-calendar__action__icon"> |
| | | <u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon> |
| | | </view> |
| | | <view class="u-calendar__action__icon"> |
| | | <u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon> |
| | | </view> |
| | | </view> |
| | | <view class="u-calendar__week-day"> |
| | | <view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view> |
| | | </view> |
| | | <view class="u-calendar__content"> |
| | | <!-- 前置空白部分 --> |
| | | <block v-for="(item, index) in weekdayArr" :key="index"> |
| | | <view class="u-calendar__content__item"></view> |
| | | </block> |
| | | <view class="u-calendar__content__item" :class="{ |
| | | 'u-hover-class':openDisAbled(year,month,index+1), |
| | | 'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date', |
| | | 'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date' |
| | | }" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index" |
| | | @tap="dateClick(index)"> |
| | | <view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}"> |
| | | <view>{{ index + 1 }}</view> |
| | | </view> |
| | | <view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view> |
| | | <view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view> |
| | | </view> |
| | | <view class="u-calendar__content__bg-month">{{month}}</view> |
| | | </view> |
| | | <view class="u-calendar__bottom"> |
| | | <view class="u-calendar__bottom__choose"> |
| | | <text>{{mode == 'date' ? activeDate : startDate}}</text> |
| | | <text v-if="endDate">至{{endDate}}</text> |
| | | </view> |
| | | <view class="u-calendar__bottom__btn"> |
| | | <u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | </template> |
| | | <script> |
| | | /** |
| | | * calendar 日历 |
| | | * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。 |
| | | * @tutorial http://uviewui.com/components/calendar.html |
| | | * @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围 |
| | | * @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起 |
| | | * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false) |
| | | * @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true) |
| | | * @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true) |
| | | * @property {String Number} max-year 可切换的最大年份(默认2050) |
| | | * @property {String Number} min-year 可切换的最小年份(默认1950) |
| | | * @property {String Number} min-date 最小可选日期(默认1950-01-01) |
| | | * @property {String Number} max-date 最大可选日期(默认当前日期) |
| | | * @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20) |
| | | * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true) |
| | | * @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266) |
| | | * @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399) |
| | | * @property {String} color 日期字体的默认颜色(默认#303133) |
| | | * @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff) |
| | | * @property {String Number} z-index 弹出时的z-index值(默认10075) |
| | | * @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff) |
| | | * @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13)) |
| | | * @property {String} range-color 选择范围内字体颜色(默认#2979ff) |
| | | * @property {String} start-text 起始日期底部的提示文字(默认 '开始') |
| | | * @property {String} end-text 结束日期底部的提示文字(默认 '结束') |
| | | * @property {String} btn-type 底部确定按钮的主题(默认 'primary') |
| | | * @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期') |
| | | * @property {Boolean} closeable 是否显示右上角的关闭图标(默认true) |
| | | * @example <u-calendar v-model="show" :mode="mode"></u-calendar> |
| | | */ |
| | | |
| | | export default { |
| | | name: 'u-calendar', |
| | | props: { |
| | | safeAreaInsetBottom: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否允许通过点击遮罩关闭Picker |
| | | maskCloseAble: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 通过双向绑定控制组件的弹出与收起 |
| | | value: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 弹出的z-index值 |
| | | zIndex: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 是否允许切换年份 |
| | | changeYear: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否允许切换月份 |
| | | changeMonth: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // date-单个日期选择,range-开始日期+结束日期选择 |
| | | mode: { |
| | | type: String, |
| | | default: 'date' |
| | | }, |
| | | // 可切换的最大年份 |
| | | maxYear: { |
| | | type: [Number, String], |
| | | default: 2050 |
| | | }, |
| | | // 可切换的最小年份 |
| | | minYear: { |
| | | type: [Number, String], |
| | | default: 1950 |
| | | }, |
| | | // 最小可选日期(不在范围内日期禁用不可选) |
| | | minDate: { |
| | | type: [Number, String], |
| | | default: '1950-01-01' |
| | | }, |
| | | /** |
| | | * 最大可选日期 |
| | | * 默认最大值为今天,之后的日期不可选 |
| | | * 2030-12-31 |
| | | * */ |
| | | maxDate: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 弹窗顶部左右两边的圆角值 |
| | | borderRadius: { |
| | | type: [String, Number], |
| | | default: 20 |
| | | }, |
| | | // 月份切换按钮箭头颜色 |
| | | monthArrowColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // 年份切换按钮箭头颜色 |
| | | yearArrowColor: { |
| | | type: String, |
| | | default: '#909399' |
| | | }, |
| | | // 默认日期字体颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#303133' |
| | | }, |
| | | // 选中|起始结束日期背景色 |
| | | activeBgColor: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | }, |
| | | // 选中|起始结束日期字体颜色 |
| | | activeColor: { |
| | | type: String, |
| | | default: '#ffffff' |
| | | }, |
| | | // 范围内日期背景色 |
| | | rangeBgColor: { |
| | | type: String, |
| | | default: 'rgba(41,121,255,0.13)' |
| | | }, |
| | | // 范围内日期字体颜色 |
| | | rangeColor: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | }, |
| | | // mode=range时生效,起始日期自定义文案 |
| | | startText: { |
| | | type: String, |
| | | default: '开始' |
| | | }, |
| | | // mode=range时生效,结束日期自定义文案 |
| | | endText: { |
| | | type: String, |
| | | default: '结束' |
| | | }, |
| | | //按钮样式类型 |
| | | btnType: { |
| | | type: String, |
| | | default: 'primary' |
| | | }, |
| | | // 当前选中日期带选中效果 |
| | | isActiveCurrent: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 切换年月是否触发事件 mode=date时生效 |
| | | isChange: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示右上角的关闭图标 |
| | | closeable: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 顶部的提示文字 |
| | | toolTip: { |
| | | type: String, |
| | | default: '选择日期' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | // 星期几,值为1-7 |
| | | weekday: 1, |
| | | weekdayArr:[], |
| | | // 当前月有多少天 |
| | | days: 0, |
| | | daysArr:[], |
| | | showTitle: '', |
| | | year: 2020, |
| | | month: 0, |
| | | day: 0, |
| | | startYear: 0, |
| | | startMonth: 0, |
| | | startDay: 0, |
| | | endYear: 0, |
| | | endMonth: 0, |
| | | endDay: 0, |
| | | today: '', |
| | | activeDate: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | isStart: true, |
| | | min: null, |
| | | max: null, |
| | | weekDayZh: ['日', '一', '二', '三', '四', '五', '六'] |
| | | }; |
| | | }, |
| | | computed: { |
| | | dataChange() { |
| | | return `${this.mode}-${this.minDate}-${this.maxDate}`; |
| | | }, |
| | | uZIndex() { |
| | | // 如果用户有传递z-index值,优先使用 |
| | | return this.zIndex ? this.zIndex : this.$u.zIndex.popup; |
| | | } |
| | | }, |
| | | watch: { |
| | | dataChange(val) { |
| | | this.init() |
| | | } |
| | | }, |
| | | created() { |
| | | this.init() |
| | | }, |
| | | methods: { |
| | | getColor(index, type) { |
| | | let color = type == 1 ? '' : this.color; |
| | | let day = index + 1 |
| | | let date = `${this.year}-${this.month}-${day}` |
| | | let timestamp = new Date(date.replace(/\-/g, '/')).getTime(); |
| | | let start = this.startDate.replace(/\-/g, '/') |
| | | let end = this.endDate.replace(/\-/g, '/') |
| | | if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) { |
| | | color = type == 1 ? this.activeBgColor : this.activeColor; |
| | | } else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) { |
| | | color = type == 1 ? this.rangeBgColor : this.rangeColor; |
| | | } |
| | | return color; |
| | | }, |
| | | init() { |
| | | let now = new Date(); |
| | | this.year = now.getFullYear(); |
| | | this.month = now.getMonth() + 1; |
| | | this.day = now.getDate(); |
| | | this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; |
| | | this.activeDate = this.today; |
| | | this.min = this.initDate(this.minDate); |
| | | this.max = this.initDate(this.maxDate || this.today); |
| | | this.startDate = ""; |
| | | this.startYear = 0; |
| | | this.startMonth = 0; |
| | | this.startDay = 0; |
| | | this.endYear = 0; |
| | | this.endMonth = 0; |
| | | this.endDay = 0; |
| | | this.endDate = ""; |
| | | this.isStart = true; |
| | | this.changeData(); |
| | | }, |
| | | //日期处理 |
| | | initDate(date) { |
| | | let fdate = date.split('-'); |
| | | return { |
| | | year: Number(fdate[0] || 1920), |
| | | month: Number(fdate[1] || 1), |
| | | day: Number(fdate[2] || 1) |
| | | } |
| | | }, |
| | | openDisAbled: function(year, month, day) { |
| | | let bool = true; |
| | | let date = `${year}/${month}/${day}`; |
| | | // let today = this.today.replace(/\-/g, '/'); |
| | | let min = `${this.min.year}/${this.min.month}/${this.min.day}`; |
| | | let max = `${this.max.year}/${this.max.month}/${this.max.day}`; |
| | | let timestamp = new Date(date).getTime(); |
| | | if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) { |
| | | bool = false; |
| | | } |
| | | return bool; |
| | | }, |
| | | generateArray: function(start, end) { |
| | | return Array.from(new Array(end + 1).keys()).slice(start); |
| | | }, |
| | | formatNum: function(num) { |
| | | return num < 10 ? '0' + num : num + ''; |
| | | }, |
| | | //一个月有多少天 |
| | | getMonthDay(year, month) { |
| | | let days = new Date(year, month, 0).getDate(); |
| | | return days; |
| | | }, |
| | | getWeekday(year, month) { |
| | | let date = new Date(`${year}/${month}/01 00:00:00`); |
| | | return date.getDay(); |
| | | }, |
| | | checkRange(year) { |
| | | let overstep = false; |
| | | if (year < this.minYear || year > this.maxYear) { |
| | | uni.showToast({ |
| | | title: "日期超出范围啦~", |
| | | icon: 'none' |
| | | }) |
| | | overstep = true; |
| | | } |
| | | return overstep; |
| | | }, |
| | | changeMonthHandler(isAdd) { |
| | | if (isAdd) { |
| | | let month = this.month + 1; |
| | | let year = month > 12 ? this.year + 1 : this.year; |
| | | if (!this.checkRange(year)) { |
| | | this.month = month > 12 ? 1 : month; |
| | | this.year = year; |
| | | this.changeData(); |
| | | } |
| | | |
| | | } else { |
| | | let month = this.month - 1; |
| | | let year = month < 1 ? this.year - 1 : this.year; |
| | | if (!this.checkRange(year)) { |
| | | this.month = month < 1 ? 12 : month; |
| | | this.year = year; |
| | | this.changeData(); |
| | | } |
| | | } |
| | | }, |
| | | changeYearHandler(isAdd) { |
| | | let year = isAdd ? this.year + 1 : this.year - 1; |
| | | if (!this.checkRange(year)) { |
| | | this.year = year; |
| | | this.changeData(); |
| | | } |
| | | }, |
| | | changeData() { |
| | | this.days = this.getMonthDay(this.year, this.month); |
| | | this.daysArr=this.generateArray(1,this.days) |
| | | this.weekday = this.getWeekday(this.year, this.month); |
| | | this.weekdayArr=this.generateArray(1,this.weekday) |
| | | this.showTitle = `${this.year}年${this.month}月`; |
| | | if (this.isChange && this.mode == 'date') { |
| | | this.btnFix(true); |
| | | } |
| | | }, |
| | | dateClick: function(day) { |
| | | day += 1; |
| | | if (!this.openDisAbled(this.year, this.month, day)) { |
| | | this.day = day; |
| | | let date = `${this.year}-${this.month}-${day}`; |
| | | if (this.mode == 'date') { |
| | | this.activeDate = date; |
| | | } else { |
| | | let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime() |
| | | if (this.isStart || compare) { |
| | | this.startDate = date; |
| | | this.startYear = this.year; |
| | | this.startMonth = this.month; |
| | | this.startDay = this.day; |
| | | this.endYear = 0; |
| | | this.endMonth = 0; |
| | | this.endDay = 0; |
| | | this.endDate = ""; |
| | | this.activeDate = ""; |
| | | this.isStart = false; |
| | | } else { |
| | | this.endDate = date; |
| | | this.endYear = this.year; |
| | | this.endMonth = this.month; |
| | | this.endDay = this.day; |
| | | this.isStart = true; |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | close() { |
| | | // 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗 |
| | | this.$emit('input', false); |
| | | }, |
| | | getWeekText(date) { |
| | | date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`); |
| | | let week = date.getDay(); |
| | | return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week]; |
| | | }, |
| | | btnFix(show) { |
| | | if (!show) { |
| | | this.close(); |
| | | } |
| | | if (this.mode == 'date') { |
| | | let arr = this.activeDate.split('-') |
| | | let year = this.isChange ? this.year : Number(arr[0]); |
| | | let month = this.isChange ? this.month : Number(arr[1]); |
| | | let day = this.isChange ? this.day : Number(arr[2]); |
| | | //当前月有多少天 |
| | | let days = this.getMonthDay(year, month); |
| | | let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`; |
| | | let weekText = this.getWeekText(result); |
| | | let isToday = false; |
| | | if (`${year}-${month}-${day}` == this.today) { |
| | | //今天 |
| | | isToday = true; |
| | | } |
| | | this.$emit('change', { |
| | | year: year, |
| | | month: month, |
| | | day: day, |
| | | days: days, |
| | | result: result, |
| | | week: weekText, |
| | | isToday: isToday, |
| | | // switch: show //是否是切换年月操作 |
| | | }); |
| | | } else { |
| | | if (!this.startDate || !this.endDate) return; |
| | | let startMonth = this.formatNum(this.startMonth); |
| | | let startDay = this.formatNum(this.startDay); |
| | | let startDate = `${this.startYear}-${startMonth}-${startDay}`; |
| | | let startWeek = this.getWeekText(startDate) |
| | | |
| | | let endMonth = this.formatNum(this.endMonth); |
| | | let endDay = this.formatNum(this.endDay); |
| | | let endDate = `${this.endYear}-${endMonth}-${endDay}`; |
| | | let endWeek = this.getWeekText(endDate); |
| | | this.$emit('change', { |
| | | startYear: this.startYear, |
| | | startMonth: this.startMonth, |
| | | startDay: this.startDay, |
| | | startDate: startDate, |
| | | startWeek: startWeek, |
| | | endYear: this.endYear, |
| | | endMonth: this.endMonth, |
| | | endDay: this.endDay, |
| | | endDate: endDate, |
| | | endWeek: endWeek |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-calendar { |
| | | color: $u-content-color; |
| | | |
| | | &__header { |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | font-size: 30rpx; |
| | | background-color: #fff; |
| | | color: $u-main-color; |
| | | |
| | | &__text { |
| | | margin-top: 30rpx; |
| | | padding: 0 60rpx; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | } |
| | | |
| | | &__action { |
| | | padding: 40rpx 0 40rpx 0; |
| | | |
| | | &__icon { |
| | | margin: 0 16rpx; |
| | | } |
| | | |
| | | &__text { |
| | | padding: 0 16rpx; |
| | | color: $u-main-color; |
| | | font-size: 32rpx; |
| | | line-height: 32rpx; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | &__week-day { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 6px 0; |
| | | overflow: hidden; |
| | | |
| | | &__text { |
| | | flex: 1; |
| | | text-align: center; |
| | | } |
| | | } |
| | | |
| | | &__content { |
| | | width: 100%; |
| | | @include vue-flex; |
| | | flex-wrap: wrap; |
| | | padding: 6px 0; |
| | | box-sizing: border-box; |
| | | background-color: #fff; |
| | | position: relative; |
| | | |
| | | &--end-date { |
| | | border-top-right-radius: 8rpx; |
| | | border-bottom-right-radius: 8rpx; |
| | | } |
| | | |
| | | &--start-date { |
| | | border-top-left-radius: 8rpx; |
| | | border-bottom-left-radius: 8rpx; |
| | | } |
| | | |
| | | &__item { |
| | | width: 14.2857%; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 6px 0; |
| | | overflow: hidden; |
| | | position: relative; |
| | | z-index: 2; |
| | | |
| | | &__inner { |
| | | height: 84rpx; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | font-size: 32rpx; |
| | | position: relative; |
| | | border-radius: 50%; |
| | | |
| | | &__desc { |
| | | width: 100%; |
| | | font-size: 24rpx; |
| | | line-height: 24rpx; |
| | | transform: scale(0.75); |
| | | transform-origin: center center; |
| | | position: absolute; |
| | | left: 0; |
| | | text-align: center; |
| | | bottom: 2rpx; |
| | | } |
| | | } |
| | | |
| | | &__tips { |
| | | width: 100%; |
| | | font-size: 24rpx; |
| | | line-height: 24rpx; |
| | | position: absolute; |
| | | left: 0; |
| | | transform: scale(0.8); |
| | | transform-origin: center center; |
| | | text-align: center; |
| | | bottom: 8rpx; |
| | | z-index: 2; |
| | | } |
| | | } |
| | | |
| | | &__bg-month { |
| | | position: absolute; |
| | | font-size: 130px; |
| | | line-height: 130px; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | color: #e4e7ed; |
| | | z-index: 1; |
| | | } |
| | | } |
| | | |
| | | &__bottom { |
| | | width: 100%; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | background-color: #fff; |
| | | padding: 0 40rpx 30rpx; |
| | | box-sizing: border-box; |
| | | font-size: 24rpx; |
| | | color: $u-tips-color; |
| | | |
| | | &__choose { |
| | | height: 50rpx; |
| | | } |
| | | |
| | | &__btn { |
| | | width: 100%; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-keyboard" @touchmove.stop.prevent="() => {}"> |
| | | <view class="u-keyboard-grids"> |
| | | <block> |
| | | <view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i"> |
| | | <view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn" |
| | | v-for="(item, j) in group" :key="j"> |
| | | {{ item }} |
| | | </view> |
| | | </view> |
| | | <view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back" |
| | | hover-class="u-hover-class"> |
| | | <u-icon :size="38" name="backspace" :bold="true"></u-icon> |
| | | </view> |
| | | <view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode"> |
| | | <text class="zh" :class="[!abc ? 'active' : 'inactive']">中</text> |
| | | / |
| | | <text class="en" :class="[abc ? 'active' : 'inactive']">英</text> |
| | | </view> |
| | | </block> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: "u-keyboard", |
| | | props: { |
| | | // 是否打乱键盘按键的顺序 |
| | | random: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | // 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称 |
| | | abc: false |
| | | }; |
| | | }, |
| | | computed: { |
| | | areaList() { |
| | | let data = [ |
| | | '京', |
| | | '沪', |
| | | '粤', |
| | | '津', |
| | | '冀', |
| | | '豫', |
| | | '云', |
| | | '辽', |
| | | '黑', |
| | | '湘', |
| | | '皖', |
| | | '鲁', |
| | | '苏', |
| | | '浙', |
| | | '赣', |
| | | '鄂', |
| | | '桂', |
| | | '甘', |
| | | '晋', |
| | | '陕', |
| | | '蒙', |
| | | '吉', |
| | | '闽', |
| | | '贵', |
| | | '渝', |
| | | '川', |
| | | '青', |
| | | '琼', |
| | | '宁', |
| | | '挂', |
| | | '藏', |
| | | '港', |
| | | '澳', |
| | | '新', |
| | | '使', |
| | | '学' |
| | | ]; |
| | | let tmp = []; |
| | | // 打乱顺序 |
| | | if (this.random) data = this.$u.randomArray(data); |
| | | // 切割成二维数组 |
| | | tmp[0] = data.slice(0, 10); |
| | | tmp[1] = data.slice(10, 20); |
| | | tmp[2] = data.slice(20, 30); |
| | | tmp[3] = data.slice(30, 36); |
| | | return tmp; |
| | | }, |
| | | EngKeyBoardList() { |
| | | let data = [ |
| | | 1, |
| | | 2, |
| | | 3, |
| | | 4, |
| | | 5, |
| | | 6, |
| | | 7, |
| | | 8, |
| | | 9, |
| | | 0, |
| | | 'Q', |
| | | 'W', |
| | | 'E', |
| | | 'R', |
| | | 'T', |
| | | 'Y', |
| | | 'U', |
| | | 'I', |
| | | 'O', |
| | | 'P', |
| | | 'A', |
| | | 'S', |
| | | 'D', |
| | | 'F', |
| | | 'G', |
| | | 'H', |
| | | 'J', |
| | | 'K', |
| | | 'L', |
| | | 'Z', |
| | | 'X', |
| | | 'C', |
| | | 'V', |
| | | 'B', |
| | | 'N', |
| | | 'M' |
| | | ]; |
| | | let tmp = []; |
| | | if (this.random) data = this.$u.randomArray(data); |
| | | tmp[0] = data.slice(0, 10); |
| | | tmp[1] = data.slice(10, 20); |
| | | tmp[2] = data.slice(20, 30); |
| | | tmp[3] = data.slice(30, 36); |
| | | return tmp; |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击键盘按钮 |
| | | carInputClick(i, j) { |
| | | let value = ''; |
| | | // 不同模式,获取不同数组的值 |
| | | if (this.abc) value = this.EngKeyBoardList[i][j]; |
| | | else value = this.areaList[i][j]; |
| | | this.$emit('change', value); |
| | | }, |
| | | // 修改汽车牌键盘的输入模式,中文|英文 |
| | | changeCarInputMode() { |
| | | this.abc = !this.abc; |
| | | }, |
| | | // 点击退格键 |
| | | backspaceClick() { |
| | | this.$emit('backspace'); |
| | | clearInterval(this.timer); //再次清空定时器,防止重复注册定时器 |
| | | this.timer = null; |
| | | this.timer = setInterval(() => { |
| | | this.$emit('backspace'); |
| | | }, 250); |
| | | }, |
| | | clearTimer() { |
| | | clearInterval(this.timer); |
| | | this.timer = null; |
| | | }, |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-keyboard-grids { |
| | | background: rgb(215, 215, 217); |
| | | padding: 24rpx 0; |
| | | position: relative; |
| | | } |
| | | |
| | | .u-keyboard-grids-item { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .u-keyboard-grids-btn { |
| | | text-decoration: none; |
| | | width: 62rpx; |
| | | flex: 0 0 64rpx; |
| | | height: 80rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | font-size: 36rpx; |
| | | text-align: center; |
| | | line-height: 80rpx; |
| | | background-color: #fff; |
| | | margin: 8rpx 5rpx; |
| | | border-radius: 8rpx; |
| | | box-shadow: 0 2rpx 0rpx #888992; |
| | | font-weight: 500; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .u-carinput-hover { |
| | | background-color: rgb(185, 188, 195) !important; |
| | | } |
| | | |
| | | .u-keyboard-back { |
| | | position: absolute; |
| | | width: 96rpx; |
| | | right: 22rpx; |
| | | bottom: 32rpx; |
| | | height: 80rpx; |
| | | background-color: rgb(185, 188, 195); |
| | | @include vue-flex; |
| | | align-items: center; |
| | | border-radius: 8rpx; |
| | | justify-content: center; |
| | | box-shadow: 0 2rpx 0rpx #888992; |
| | | } |
| | | |
| | | .u-keyboard-change { |
| | | font-size: 24rpx; |
| | | box-shadow: 0 2rpx 0rpx #888992; |
| | | position: absolute; |
| | | width: 96rpx; |
| | | left: 22rpx; |
| | | line-height: 1; |
| | | bottom: 32rpx; |
| | | height: 80rpx; |
| | | background-color: #ffffff; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | border-radius: 8rpx; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .u-keyboard-change .inactive.zh { |
| | | transform: scale(0.85) translateY(-10rpx); |
| | | } |
| | | |
| | | .u-keyboard-change .inactive.en { |
| | | transform: scale(0.85) translateY(10rpx); |
| | | } |
| | | |
| | | .u-keyboard-change .active { |
| | | color: rgb(237, 112, 64); |
| | | font-size: 30rpx; |
| | | } |
| | | |
| | | .u-keyboard-change .zh { |
| | | transform: translateY(-10rpx); |
| | | } |
| | | |
| | | .u-keyboard-change .en { |
| | | transform: translateY(10rpx); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view |
| | | class="u-card" |
| | | @tap.stop="click" |
| | | :class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': borderRadius > 0 }" |
| | | :style="{ |
| | | borderRadius: borderRadius + 'rpx', |
| | | margin: margin, |
| | | boxShadow: boxShadow |
| | | }" |
| | | > |
| | | <view |
| | | v-if="showHead" |
| | | class="u-card__head" |
| | | :style="[{padding: padding + 'rpx'}, headStyle]" |
| | | :class="{ |
| | | 'u-border-bottom': headBorderBottom |
| | | }" |
| | | @tap="headClick" |
| | | > |
| | | <view v-if="!$slots.head" class="u-flex u-row-between"> |
| | | <view class="u-card__head--left u-flex u-line-1" v-if="title"> |
| | | <image |
| | | :src="thumb" |
| | | class="u-card__head--left__thumb" |
| | | mode="aspectfull" |
| | | v-if="thumb" |
| | | :style="{ |
| | | height: thumbWidth + 'rpx', |
| | | width: thumbWidth + 'rpx', |
| | | borderRadius: thumbCircle ? '100rpx' : '6rpx' |
| | | }" |
| | | ></image> |
| | | <text |
| | | class="u-card__head--left__title u-line-1" |
| | | :style="{ |
| | | fontSize: titleSize + 'rpx', |
| | | color: titleColor |
| | | }" |
| | | > |
| | | {{ title }} |
| | | </text> |
| | | </view> |
| | | <view class="u-card__head--right u-line-1" v-if="subTitle"> |
| | | <text |
| | | class="u-card__head__title__text" |
| | | :style="{ |
| | | fontSize: subTitleSize + 'rpx', |
| | | color: subTitleColor |
| | | }" |
| | | > |
| | | {{ subTitle }} |
| | | </text> |
| | | </view> |
| | | </view> |
| | | <slot name="head" v-else /> |
| | | </view> |
| | | <view @tap="bodyClick" class="u-card__body" :style="[{padding: padding + 'rpx'}, bodyStyle]"><slot name="body" /></view> |
| | | <view |
| | | v-if="showFoot" |
| | | class="u-card__foot" |
| | | @tap="footClick" |
| | | :style="[{padding: $slots.foot ? padding + 'rpx' : 0}, footStyle]" |
| | | :class="{ |
| | | 'u-border-top': footBorderTop |
| | | }" |
| | | > |
| | | <slot name="foot" /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * card 卡片 |
| | | * @description 卡片组件一般用于多个列表条目,且风格统一的场景 |
| | | * @tutorial https://www.uviewui.com/components/card.html |
| | | * @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false) |
| | | * @property {String} title 头部左边的标题 |
| | | * @property {String} title-color 标题颜色(默认#303133) |
| | | * @property {String | Number} title-size 标题字体大小,单位rpx(默认30) |
| | | * @property {String} sub-title 头部右边的副标题 |
| | | * @property {String} sub-title-color 副标题颜色(默认#909399) |
| | | * @property {String | Number} sub-title-size 副标题字体大小(默认26) |
| | | * @property {Boolean} border 是否显示边框(默认true) |
| | | * @property {String | Number} index 用于标识点击了第几个卡片 |
| | | * @property {String} box-shadow 卡片外围阴影,字符串形式(默认none) |
| | | * @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30rpx 20rpx"(默认30rpx) |
| | | * @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认16) |
| | | * @property {Object} head-style 头部自定义样式,对象形式 |
| | | * @property {Object} body-style 中部自定义样式,对象形式 |
| | | * @property {Object} foot-style 底部自定义样式,对象形式 |
| | | * @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true) |
| | | * @property {Boolean} foot-border-top 是否显示底部的上边框(默认true) |
| | | * @property {Boolean} show-head 是否显示头部(默认true) |
| | | * @property {Boolean} show-head 是否显示尾部(默认true) |
| | | * @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径 |
| | | * @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位rpx(默认60) |
| | | * @property {Boolean} thumb-circle 缩略图是否为圆形(默认false) |
| | | * @event {Function} click 整个卡片任意位置被点击时触发 |
| | | * @event {Function} head-click 卡片头部被点击时触发 |
| | | * @event {Function} body-click 卡片主体部分被点击时触发 |
| | | * @event {Function} foot-click 卡片底部部分被点击时触发 |
| | | * @example <u-card padding="30" title="card"></u-card> |
| | | */ |
| | | export default { |
| | | name: 'u-card', |
| | | props: { |
| | | // 与屏幕两侧是否留空隙 |
| | | full: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 标题 |
| | | title: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 标题颜色 |
| | | titleColor: { |
| | | type: String, |
| | | default: '#303133' |
| | | }, |
| | | // 标题字体大小,单位rpx |
| | | titleSize: { |
| | | type: [Number, String], |
| | | default: '30' |
| | | }, |
| | | // 副标题 |
| | | subTitle: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 副标题颜色 |
| | | subTitleColor: { |
| | | type: String, |
| | | default: '#909399' |
| | | }, |
| | | // 副标题字体大小,单位rpx |
| | | subTitleSize: { |
| | | type: [Number, String], |
| | | default: '26' |
| | | }, |
| | | // 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时) |
| | | border: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 用于标识点击了第几个 |
| | | index: { |
| | | type: [Number, String, Object], |
| | | default: '' |
| | | }, |
| | | // 用于隔开上下左右的边距,带单位的写法,如:"30rpx 30rpx","20rpx 20rpx 30rpx 30rpx" |
| | | margin: { |
| | | type: String, |
| | | default: '30rpx' |
| | | }, |
| | | // card卡片的圆角 |
| | | borderRadius: { |
| | | type: [Number, String], |
| | | default: '16' |
| | | }, |
| | | // 头部自定义样式,对象形式 |
| | | headStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 主体自定义样式,对象形式 |
| | | bodyStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 底部自定义样式,对象形式 |
| | | footStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 头部是否下边框 |
| | | headBorderBottom: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 底部是否有上边框 |
| | | footBorderTop: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 标题左边的缩略图 |
| | | thumb: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 缩略图宽高,单位rpx |
| | | thumbWidth: { |
| | | type: [String, Number], |
| | | default: '60' |
| | | }, |
| | | // 缩略图是否为圆形 |
| | | thumbCircle: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 给head,body,foot的内边距 |
| | | padding: { |
| | | type: [String, Number], |
| | | default: '30' |
| | | }, |
| | | // 是否显示头部 |
| | | showHead: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示尾部 |
| | | showFoot: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 卡片外围阴影,字符串形式 |
| | | boxShadow: { |
| | | type: String, |
| | | default: 'none' |
| | | } |
| | | }, |
| | | data() { |
| | | return {}; |
| | | }, |
| | | methods: { |
| | | click() { |
| | | this.$emit('click', this.index); |
| | | }, |
| | | headClick() { |
| | | this.$emit('head-click', this.index); |
| | | }, |
| | | bodyClick() { |
| | | this.$emit('body-click', this.index); |
| | | }, |
| | | footClick() { |
| | | this.$emit('foot-click', this.index); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-card { |
| | | position: relative; |
| | | overflow: hidden; |
| | | font-size: 28rpx; |
| | | background-color: #ffffff; |
| | | box-sizing: border-box; |
| | | |
| | | &-full { |
| | | // 如果是与屏幕之间不留空隙,应该设置左右边距为0 |
| | | margin-left: 0 !important; |
| | | margin-right: 0 !important; |
| | | width: 100%; |
| | | } |
| | | |
| | | &--border:after { |
| | | border-radius: 16rpx; |
| | | } |
| | | |
| | | &__head { |
| | | &--left { |
| | | color: $u-main-color; |
| | | |
| | | &__thumb { |
| | | margin-right: 16rpx; |
| | | } |
| | | |
| | | &__title { |
| | | max-width: 400rpx; |
| | | } |
| | | } |
| | | |
| | | &--right { |
| | | color: $u-tips-color; |
| | | margin-left: 6rpx; |
| | | } |
| | | } |
| | | |
| | | &__body { |
| | | color: $u-content-color; |
| | | } |
| | | |
| | | &__foot { |
| | | color: $u-tips-color; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-cell-box"> |
| | | <view class="u-cell-title" v-if="title" :style="[titleStyle]"> |
| | | {{title}} |
| | | </view> |
| | | <view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}"> |
| | | <slot /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * cellGroup 单元格父组件Group |
| | | * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-item |
| | | * @tutorial https://www.uviewui.com/components/cell.html |
| | | * @property {String} title 分组标题 |
| | | * @property {Boolean} border 是否显示外边框(默认true) |
| | | * @property {Object} title-style 分组标题的的样式,对象形式,如{'font-size': '24rpx'} 或 {'fontSize': '24rpx'} |
| | | * @example <u-cell-group title="设置喜好"> |
| | | */ |
| | | export default { |
| | | name: "u-cell-group", |
| | | props: { |
| | | // 分组标题 |
| | | title: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否显示分组list上下边框 |
| | | border: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 分组标题的样式,对象形式,注意驼峰属性写法 |
| | | // 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'} |
| | | titleStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {}; |
| | | } |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | index: 0, |
| | | } |
| | | }, |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-cell-box { |
| | | width: 100%; |
| | | } |
| | | |
| | | .u-cell-title { |
| | | padding: 30rpx 32rpx 10rpx 32rpx; |
| | | font-size: 30rpx; |
| | | text-align: left; |
| | | color: $u-tips-color; |
| | | } |
| | | |
| | | .u-cell-item-box { |
| | | background-color: #FFFFFF; |
| | | flex-direction: row; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view |
| | | @tap="click" |
| | | class="u-cell" |
| | | :class="{ 'u-border-bottom': borderBottom, 'u-border-top': borderTop, 'u-col-center': center, 'u-cell--required': required }" |
| | | hover-stay-time="150" |
| | | :hover-class="hoverClass" |
| | | :style="{ |
| | | backgroundColor: bgColor |
| | | }" |
| | | > |
| | | <u-icon :size="iconSize" :name="icon" v-if="icon" :custom-style="iconStyle" class="u-cell__left-icon-wrap"></u-icon> |
| | | <view class="u-flex" v-else> |
| | | <slot name="icon"></slot> |
| | | </view> |
| | | <view |
| | | class="u-cell_title" |
| | | :style="[ |
| | | { |
| | | width: titleWidth ? titleWidth + 'rpx' : 'auto' |
| | | }, |
| | | titleStyle |
| | | ]" |
| | | > |
| | | <block v-if="title !== ''">{{ title }}</block> |
| | | <slot name="title" v-else></slot> |
| | | |
| | | <view class="u-cell__label" v-if="label || $slots.label" :style="[labelStyle]"> |
| | | <block v-if="label !== ''">{{ label }}</block> |
| | | <slot name="label" v-else></slot> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="u-cell__value" :style="[valueStyle]"> |
| | | <block class="u-cell__value" v-if="value !== ''">{{ value }}</block> |
| | | <slot v-else></slot> |
| | | </view> |
| | | <view class="u-flex u-cell_right" v-if="$slots['right-icon']"> |
| | | <slot name="right-icon"></slot> |
| | | </view> |
| | | <u-icon v-if="arrow" name="arrow-right" :style="[arrowStyle]" class="u-icon-wrap u-cell__right-icon-wrap"></u-icon> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * cellItem 单元格Item |
| | | * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-group使用 |
| | | * @tutorial https://www.uviewui.com/components/cell.html |
| | | * @property {String} title 左侧标题 |
| | | * @property {String} icon 左侧图标名,只支持uView内置图标,见Icon 图标 |
| | | * @property {Object} icon-style 左边图标的样式,对象形式 |
| | | * @property {String} value 右侧内容 |
| | | * @property {String} label 标题下方的描述信息 |
| | | * @property {Boolean} border-bottom 是否显示cell的下边框(默认true) |
| | | * @property {Boolean} border-top 是否显示cell的上边框(默认false) |
| | | * @property {Boolean} center 是否使内容垂直居中(默认false) |
| | | * @property {String} hover-class 是否开启点击反馈,none为无效果(默认true) |
| | | * // @property {Boolean} border-gap border-bottom为true时,Cell列表中间的条目的下边框是否与左边有一个间隔(默认true) |
| | | * @property {Boolean} arrow 是否显示右侧箭头(默认true) |
| | | * @property {Boolean} required 箭头方向,可选值(默认right) |
| | | * @property {Boolean} arrow-direction 是否显示左边表示必填的星号(默认false) |
| | | * @property {Object} title-style 标题样式,对象形式 |
| | | * @property {Object} value-style 右侧内容样式,对象形式 |
| | | * @property {Object} label-style 标题下方描述信息的样式,对象形式 |
| | | * @property {String} bg-color 背景颜色(默认transparent) |
| | | * @property {String Number} index 用于在click事件回调中返回,标识当前是第几个Item |
| | | * @property {String Number} title-width 标题的宽度,单位rpx |
| | | * @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item> |
| | | */ |
| | | export default { |
| | | name: 'u-cell-item', |
| | | props: { |
| | | // 左侧图标名称(只能uView内置图标),或者图标src |
| | | icon: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 左侧标题 |
| | | title: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 右侧内容 |
| | | value: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 标题下方的描述信息 |
| | | label: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 是否显示下边框 |
| | | borderBottom: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示上边框 |
| | | borderTop: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 多个cell中,中间的cell显示下划线时,下划线是否给一个到左边的距离 |
| | | // 1.4.0版本废除此参数,默认边框由border-top和border-bottom提供,此参数会造成干扰 |
| | | // borderGap: { |
| | | // type: Boolean, |
| | | // default: true |
| | | // }, |
| | | // 是否开启点击反馈,即点击时cell背景为灰色,none为无效果 |
| | | hoverClass: { |
| | | type: String, |
| | | default: 'u-cell-hover' |
| | | }, |
| | | // 是否显示右侧箭头 |
| | | arrow: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 内容是否垂直居中 |
| | | center: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示左边表示必填的星号 |
| | | required: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 标题的宽度,单位rpx |
| | | titleWidth: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 右侧箭头方向,可选值:right|up|down,默认为right |
| | | arrowDirection: { |
| | | type: String, |
| | | default: 'right' |
| | | }, |
| | | // 控制标题的样式 |
| | | titleStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 右侧显示内容的样式 |
| | | valueStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 描述信息的样式 |
| | | labelStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: 'transparent' |
| | | }, |
| | | // 用于识别被点击的是第几个cell |
| | | index: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 是否使用lable插槽 |
| | | useLabelSlot: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 左边图标的大小,单位rpx,只对传入icon字段时有效 |
| | | iconSize: { |
| | | type: [Number, String], |
| | | default: 34 |
| | | }, |
| | | // 左边图标的样式,对象形式 |
| | | iconStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | |
| | | }; |
| | | }, |
| | | computed: { |
| | | arrowStyle() { |
| | | let style = {}; |
| | | if (this.arrowDirection == 'up') style.transform = 'rotate(-90deg)'; |
| | | else if (this.arrowDirection == 'down') style.transform = 'rotate(90deg)'; |
| | | else style.transform = 'rotate(0deg)'; |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | click() { |
| | | this.$emit('click', this.index); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | .u-cell { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | position: relative; |
| | | /* #ifndef APP-NVUE */ |
| | | box-sizing: border-box; |
| | | /* #endif */ |
| | | width: 100%; |
| | | padding: 26rpx 32rpx; |
| | | font-size: 28rpx; |
| | | line-height: 54rpx; |
| | | color: $u-content-color; |
| | | background-color: #fff; |
| | | text-align: left; |
| | | } |
| | | |
| | | .u-cell_title { |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .u-cell__left-icon-wrap { |
| | | margin-right: 10rpx; |
| | | font-size: 32rpx; |
| | | } |
| | | |
| | | .u-cell__right-icon-wrap { |
| | | margin-left: 10rpx; |
| | | color: #969799; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .u-cell__left-icon-wrap, |
| | | .u-cell__right-icon-wrap { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | height: 48rpx; |
| | | } |
| | | |
| | | .u-cell-border:after { |
| | | position: absolute; |
| | | /* #ifndef APP-NVUE */ |
| | | box-sizing: border-box; |
| | | content: ' '; |
| | | pointer-events: none; |
| | | border-bottom: 1px solid $u-border-color; |
| | | /* #endif */ |
| | | right: 0; |
| | | left: 0; |
| | | top: 0; |
| | | transform: scaleY(0.5); |
| | | } |
| | | |
| | | .u-cell-border { |
| | | position: relative; |
| | | } |
| | | |
| | | .u-cell__label { |
| | | margin-top: 6rpx; |
| | | font-size: 26rpx; |
| | | line-height: 36rpx; |
| | | color: $u-tips-color; |
| | | /* #ifndef APP-NVUE */ |
| | | word-wrap: break-word; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .u-cell__value { |
| | | overflow: hidden; |
| | | text-align: right; |
| | | /* #ifndef APP-NVUE */ |
| | | vertical-align: middle; |
| | | /* #endif */ |
| | | color: $u-tips-color; |
| | | font-size: 26rpx; |
| | | } |
| | | |
| | | .u-cell__title, |
| | | .u-cell__value { |
| | | flex: 1; |
| | | } |
| | | |
| | | .u-cell--required { |
| | | /* #ifndef APP-NVUE */ |
| | | overflow: visible; |
| | | /* #endif */ |
| | | @include vue-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-cell--required:before { |
| | | position: absolute; |
| | | /* #ifndef APP-NVUE */ |
| | | content: '*'; |
| | | /* #endif */ |
| | | left: 8px; |
| | | margin-top: 4rpx; |
| | | font-size: 14px; |
| | | color: $u-type-error; |
| | | } |
| | | |
| | | .u-cell_right { |
| | | line-height: 1; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-checkbox-group u-clearfix"> |
| | | <slot></slot> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import Emitter from '../../libs/util/emitter.js'; |
| | | /** |
| | | * checkboxGroup 开关选择器父组件Group |
| | | * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 |
| | | * @tutorial https://www.uviewui.com/components/checkbox.html |
| | | * @property {String Number} max 最多能选中多少个checkbox(默认999) |
| | | * @property {String Number} size 组件整体的大小,单位rpx(默认40) |
| | | * @property {Boolean} disabled 是否禁用所有checkbox(默认false) |
| | | * @property {String Number} icon-size 图标大小,单位rpx(默认20) |
| | | * @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false) |
| | | * @property {String} width 宽度,需带单位 |
| | | * @property {String} width 宽度,需带单位 |
| | | * @property {String} shape 外观形状,shape-方形,circle-圆形(默认circle) |
| | | * @property {Boolean} wrap 是否每个checkbox都换行(默认false) |
| | | * @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认#2979ff) |
| | | * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象 |
| | | * @example <u-checkbox-group></u-checkbox-group> |
| | | */ |
| | | export default { |
| | | name: 'u-checkbox-group', |
| | | mixins: [Emitter], |
| | | props: { |
| | | // 最多能选中多少个checkbox |
| | | max: { |
| | | type: [Number, String], |
| | | default: 999 |
| | | }, |
| | | // 所有选中项的 name |
| | | // value: { |
| | | // default: Array, |
| | | // default() { |
| | | // return [] |
| | | // } |
| | | // }, |
| | | // 是否禁用所有复选框 |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 在表单内提交时的标识符 |
| | | name: { |
| | | type: [Boolean, String], |
| | | default: '' |
| | | }, |
| | | // 是否禁止点击提示语选中复选框 |
| | | labelDisabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 形状,square为方形,circle为原型 |
| | | shape: { |
| | | type: String, |
| | | default: 'square' |
| | | }, |
| | | // 选中状态下的颜色 |
| | | activeColor: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | }, |
| | | // 组件的整体大小 |
| | | size: { |
| | | type: [String, Number], |
| | | default: 34 |
| | | }, |
| | | // 每个checkbox占u-checkbox-group的宽度 |
| | | width: { |
| | | type: String, |
| | | default: 'auto' |
| | | }, |
| | | // 是否每个checkbox都换行 |
| | | wrap: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 图标的大小,单位rpx |
| | | iconSize: { |
| | | type: [String, Number], |
| | | default: 20 |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | } |
| | | }, |
| | | created() { |
| | | // 如果将children定义在data中,在微信小程序会造成循环引用而报错 |
| | | this.children = []; |
| | | }, |
| | | methods: { |
| | | emitEvent() { |
| | | let values = []; |
| | | this.children.map(val => { |
| | | if(val.value) values.push(val.name); |
| | | }) |
| | | this.$emit('change', values); |
| | | // 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证 |
| | | // 由于头条小程序执行迟钝,故需要用几十毫秒的延时 |
| | | setTimeout(() => { |
| | | // 将当前的值发送到 u-form-item 进行校验 |
| | | this.dispatch('u-form-item', 'on-form-change', values); |
| | | }, 60) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-checkbox-group { |
| | | /* #ifndef MP || APP-NVUE */ |
| | | display: inline-flex; |
| | | flex-wrap: wrap; |
| | | /* #endif */ |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-checkbox" :style="[checkboxStyle]"> |
| | | <view class="u-checkbox__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]"> |
| | | <u-icon class="u-checkbox__icon-wrap__icon" name="checkbox-mark" :size="checkboxIconSize" :color="iconColor"/> |
| | | </view> |
| | | <view class="u-checkbox__label" @tap="onClickLabel" :style="{ |
| | | fontSize: $u.addUnit(labelSize) |
| | | }"> |
| | | <slot /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * checkbox 复选框 |
| | | * @description 该组件需要搭配checkboxGroup组件使用,以便用户进行操作时,获得当前复选框组的选中情况。 |
| | | * @tutorial https://www.uviewui.com/components/checkbox.html |
| | | * @property {String Number} icon-size 图标大小,单位rpx(默认20) |
| | | * @property {String Number} label-size label字体大小,单位rpx(默认28) |
| | | * @property {String Number} name checkbox组件的标示符 |
| | | * @property {String} shape 形状,见官网说明(默认circle) |
| | | * @property {Boolean} disabled 是否禁用 |
| | | * @property {Boolean} label-disabled 是否禁止点击文本操作checkbox |
| | | * @property {String} active-color 选中时的颜色,如设置CheckboxGroup的active-color将失效 |
| | | * @event {Function} change 某个checkbox状态发生变化时触发,回调为一个对象 |
| | | * @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox> |
| | | */ |
| | | export default { |
| | | name: "u-checkbox", |
| | | props: { |
| | | // checkbox的名称 |
| | | name: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 形状,square为方形,circle为原型 |
| | | shape: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否为选中状态 |
| | | value: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否禁用 |
| | | disabled: { |
| | | type: [String, Boolean], |
| | | default: '' |
| | | }, |
| | | // 是否禁止点击提示语选中复选框 |
| | | labelDisabled: { |
| | | type: [String, Boolean], |
| | | default: '' |
| | | }, |
| | | // 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值 |
| | | activeColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 图标的大小,单位rpx |
| | | iconSize: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // label的字体大小,rpx单位 |
| | | labelSize: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 组件的整体大小 |
| | | size: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | parentDisabled: false, |
| | | newParams: {}, |
| | | }; |
| | | }, |
| | | created() { |
| | | // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用 |
| | | this.parent = this.$u.$parent.call(this, 'u-checkbox-group'); |
| | | // 如果存在u-checkbox-group,将本组件的this塞进父组件的children中 |
| | | this.parent && this.parent.children.push(this); |
| | | }, |
| | | computed: { |
| | | // 是否禁用,如果父组件u-checkbox-group禁用的话,将会忽略子组件的配置 |
| | | isDisabled() { |
| | | return this.disabled !== '' ? this.disabled : this.parent ? this.parent.disabled : false; |
| | | }, |
| | | // 是否禁用label点击 |
| | | isLabelDisabled() { |
| | | return this.labelDisabled !== '' ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false; |
| | | }, |
| | | // 组件尺寸,对应size的值,默认值为34rpx |
| | | checkboxSize() { |
| | | return this.size ? this.size : (this.parent ? this.parent.size : 34); |
| | | }, |
| | | // 组件的勾选图标的尺寸,默认20 |
| | | checkboxIconSize() { |
| | | return this.iconSize ? this.iconSize : (this.parent ? this.parent.iconSize : 20); |
| | | }, |
| | | // 组件选中激活时的颜色 |
| | | elActiveColor() { |
| | | return this.activeColor ? this.activeColor : (this.parent ? this.parent.activeColor : 'primary'); |
| | | }, |
| | | // 组件的形状 |
| | | elShape() { |
| | | return this.shape ? this.shape : (this.parent ? this.parent.shape : 'square'); |
| | | }, |
| | | iconStyle() { |
| | | let style = {}; |
| | | // 既要判断是否手动禁用,还要判断用户v-model绑定的值,如果绑定为false,那么也无法选中 |
| | | if (this.elActiveColor && this.value && !this.isDisabled) { |
| | | style.borderColor = this.elActiveColor; |
| | | style.backgroundColor = this.elActiveColor; |
| | | } |
| | | style.width = this.$u.addUnit(this.checkboxSize); |
| | | style.height = this.$u.addUnit(this.checkboxSize); |
| | | return style; |
| | | }, |
| | | // checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可 |
| | | iconColor() { |
| | | return this.value ? '#ffffff' : 'transparent'; |
| | | }, |
| | | iconClass() { |
| | | let classes = []; |
| | | classes.push('u-checkbox__icon-wrap--' + this.elShape); |
| | | if (this.value == true) classes.push('u-checkbox__icon-wrap--checked'); |
| | | if (this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled'); |
| | | if (this.value && this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled--checked'); |
| | | // 支付宝小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效 |
| | | return classes.join(' '); |
| | | }, |
| | | checkboxStyle() { |
| | | let style = {}; |
| | | if(this.parent && this.parent.width) { |
| | | style.width = this.parent.width; |
| | | // #ifdef MP |
| | | // 各家小程序因为它们特殊的编译结构,使用float布局 |
| | | style.float = 'left'; |
| | | // #endif |
| | | // #ifndef MP |
| | | // H5和APP使用flex布局 |
| | | style.flex = `0 0 ${this.parent.width}`; |
| | | // #endif |
| | | } |
| | | if(this.parent && this.parent.wrap) { |
| | | style.width = '100%'; |
| | | // #ifndef MP |
| | | // H5和APP使用flex布局,将宽度设置100%,即可自动换行 |
| | | style.flex = '0 0 100%'; |
| | | // #endif |
| | | } |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | onClickLabel() { |
| | | if (!this.isLabelDisabled && !this.isDisabled) { |
| | | this.setValue(); |
| | | } |
| | | }, |
| | | toggle() { |
| | | if (!this.isDisabled) { |
| | | this.setValue(); |
| | | } |
| | | }, |
| | | emitEvent() { |
| | | this.$emit('change', { |
| | | value: !this.value, |
| | | name: this.name |
| | | }) |
| | | // 执行父组件u-checkbox-group的事件方法 |
| | | // 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间 |
| | | setTimeout(() => { |
| | | if(this.parent && this.parent.emitEvent) this.parent.emitEvent(); |
| | | }, 80); |
| | | }, |
| | | // 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值 |
| | | setValue() { |
| | | // 判断是否超过了可选的最大数量 |
| | | let checkedNum = 0; |
| | | if(this.parent && this.parent.children) { |
| | | // 只要父组件的某一个子元素的value为true,就加1(已有的选中数量) |
| | | this.parent.children.map(val => { |
| | | if (val.value) checkedNum++; |
| | | }) |
| | | } |
| | | // 如果原来为选中状态,那么可以取消 |
| | | if (this.value == true) { |
| | | this.emitEvent(); |
| | | this.$emit('input', !this.value); |
| | | } else { |
| | | // 如果超出最多可选项,提示 |
| | | if(this.parent && checkedNum >= this.parent.max) { |
| | | return this.$u.toast(`最多可选${this.parent.max}项`); |
| | | } |
| | | // 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中 |
| | | this.emitEvent(); |
| | | this.$emit('input', !this.value); |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-checkbox { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | overflow: hidden; |
| | | user-select: none; |
| | | line-height: 1.8; |
| | | |
| | | &__icon-wrap { |
| | | color: $u-content-color; |
| | | flex: none; |
| | | display: -webkit-flex; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | width: 42rpx; |
| | | height: 42rpx; |
| | | color: transparent; |
| | | text-align: center; |
| | | transition-property: color, border-color, background-color; |
| | | font-size: 20px; |
| | | border: 1px solid #c8c9cc; |
| | | transition-duration: 0.2s; |
| | | |
| | | /* #ifdef MP-TOUTIAO */ |
| | | // 头条小程序兼容性问题,需要设置行高为0,否则图标偏下 |
| | | &__icon { |
| | | line-height: 0; |
| | | } |
| | | /* #endif */ |
| | | |
| | | &--circle { |
| | | border-radius: 100%; |
| | | } |
| | | |
| | | &--square { |
| | | border-radius: 6rpx; |
| | | } |
| | | |
| | | &--checked { |
| | | color: #fff; |
| | | background-color: $u-type-primary; |
| | | border-color: $u-type-primary; |
| | | } |
| | | |
| | | &--disabled { |
| | | background-color: #ebedf0; |
| | | border-color: #c8c9cc; |
| | | } |
| | | |
| | | &--disabled--checked { |
| | | color: #c8c9cc !important; |
| | | } |
| | | } |
| | | |
| | | &__label { |
| | | word-wrap: break-word; |
| | | margin-left: 10rpx; |
| | | margin-right: 24rpx; |
| | | color: $u-content-color; |
| | | font-size: 30rpx; |
| | | |
| | | &--disabled { |
| | | color: #c8c9cc; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view |
| | | class="u-circle-progress" |
| | | :style="{ |
| | | width: widthPx + 'px', |
| | | height: widthPx + 'px', |
| | | backgroundColor: bgColor |
| | | }" |
| | | > |
| | | <!-- 支付宝小程序不支持canvas-id属性,必须用id属性 --> |
| | | <canvas |
| | | class="u-canvas-bg" |
| | | :canvas-id="elBgId" |
| | | :id="elBgId" |
| | | :style="{ |
| | | width: widthPx + 'px', |
| | | height: widthPx + 'px' |
| | | }" |
| | | ></canvas> |
| | | <canvas |
| | | class="u-canvas" |
| | | :canvas-id="elId" |
| | | :id="elId" |
| | | :style="{ |
| | | width: widthPx + 'px', |
| | | height: widthPx + 'px' |
| | | }" |
| | | ></canvas> |
| | | <slot></slot> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * circleProgress 环形进度条 |
| | | * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度条。注意:此组件的percent值只能动态增加,不能动态减少。 |
| | | * @tutorial https://www.uviewui.com/components/circleProgress.html |
| | | * @property {String Number} percent 圆环进度百分比值,为数值类型,0-100 |
| | | * @property {String} inactive-color 圆环的底色,默认为灰色(该值无法动态变更)(默认#ececec) |
| | | * @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)(默认#19be6b) |
| | | * @property {String Number} width 整个圆环组件的宽度,高度默认等于宽度值,单位rpx(默认200) |
| | | * @property {String Number} border-width 圆环的边框宽度,单位rpx(默认14) |
| | | * @property {String Number} duration 整个圆环执行一圈的时间,单位ms(默认呢1500) |
| | | * @property {String} type 如设置,active-color值将会失效 |
| | | * @property {String} bg-color 整个组件背景颜色,默认为白色 |
| | | * @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress> |
| | | */ |
| | | export default { |
| | | name: 'u-circle-progress', |
| | | props: { |
| | | // 圆环进度百分比值 |
| | | percent: { |
| | | type: Number, |
| | | default: 0, |
| | | // 限制值在0到100之间 |
| | | validator: val => { |
| | | return val >= 0 && val <= 100; |
| | | } |
| | | }, |
| | | // 底部圆环的颜色(灰色的圆环) |
| | | inactiveColor: { |
| | | type: String, |
| | | default: '#ececec' |
| | | }, |
| | | // 圆环激活部分的颜色 |
| | | activeColor: { |
| | | type: String, |
| | | default: '#19be6b' |
| | | }, |
| | | // 圆环线条的宽度,单位rpx |
| | | borderWidth: { |
| | | type: [Number, String], |
| | | default: 14 |
| | | }, |
| | | // 整个圆形的宽度,单位rpx |
| | | width: { |
| | | type: [Number, String], |
| | | default: 200 |
| | | }, |
| | | // 整个圆环执行一圈的时间,单位ms |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 1500 |
| | | }, |
| | | // 主题类型 |
| | | type: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 整个圆环进度区域的背景色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '#ffffff' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | // #ifdef MP-WEIXIN |
| | | elBgId: 'uCircleProgressBgId', // 微信小程序中不能使用this.$u.guid()形式动态生成id值,否则会报错 |
| | | elId: 'uCircleProgressElId', |
| | | // #endif |
| | | // #ifndef MP-WEIXIN |
| | | elBgId: this.$u.guid(), // 非微信端的时候,需用动态的id,否则一个页面多个圆形进度条组件数据会混乱 |
| | | elId: this.$u.guid(), |
| | | // #endif |
| | | widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度 |
| | | borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度 |
| | | startAngle: -Math.PI / 2, // canvas画圆的起始角度,默认为3点钟方向,定位到12点钟方向 |
| | | progressContext: null, // 活动圆的canvas上下文 |
| | | newPercent: 0, // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用 |
| | | oldPercent: 0 // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用 |
| | | }; |
| | | }, |
| | | watch: { |
| | | percent(nVal, oVal = 0) { |
| | | if (nVal > 100) nVal = 100; |
| | | if (nVal < 0) oVal = 0; |
| | | // 此值其实等于this.percent,命名一个新 |
| | | this.newPercent = nVal; |
| | | this.oldPercent = oVal; |
| | | setTimeout(() => { |
| | | // 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值 |
| | | // 将此值减少或者新增到新的百分比值 |
| | | this.drawCircleByProgress(oVal); |
| | | }, 50); |
| | | } |
| | | }, |
| | | created() { |
| | | // 赋值,用于加载后第一个画圆使用 |
| | | this.newPercent = this.percent; |
| | | this.oldPercent = 0; |
| | | }, |
| | | computed: { |
| | | // 有type主题时,优先起作用 |
| | | circleColor() { |
| | | if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type]; |
| | | else return this.activeColor; |
| | | } |
| | | }, |
| | | mounted() { |
| | | // 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7) |
| | | setTimeout(() => { |
| | | this.drawProgressBg(); |
| | | this.drawCircleByProgress(this.oldPercent); |
| | | }, 50); |
| | | }, |
| | | methods: { |
| | | drawProgressBg() { |
| | | let ctx = uni.createCanvasContext(this.elBgId, this); |
| | | ctx.setLineWidth(this.borderWidthPx); // 设置圆环宽度 |
| | | ctx.setStrokeStyle(this.inactiveColor); // 线条颜色 |
| | | ctx.beginPath(); // 开始描绘路径 |
| | | // 设置一个原点(110,110),半径为100的圆的路径到当前路径 |
| | | let radius = this.widthPx / 2; |
| | | ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 2 * Math.PI, false); |
| | | ctx.stroke(); // 对路径进行描绘 |
| | | ctx.draw(); |
| | | }, |
| | | drawCircleByProgress(progress) { |
| | | // 第一次操作进度环时将上下文保存到了this.data中,直接使用即可 |
| | | let ctx = this.progressContext; |
| | | if (!ctx) { |
| | | ctx = uni.createCanvasContext(this.elId, this); |
| | | this.progressContext = ctx; |
| | | } |
| | | // 表示进度的两端为圆形 |
| | | ctx.setLineCap('round'); |
| | | // 设置线条的宽度和颜色 |
| | | ctx.setLineWidth(this.borderWidthPx); |
| | | ctx.setStrokeStyle(this.circleColor); |
| | | // 将总过渡时间除以100,得出每修改百分之一进度所需的时间 |
| | | let time = Math.floor(this.duration / 100); |
| | | // 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的 |
| | | // 3点钟方向开始画图,转为更好理解的12点钟方向开始作图,这需要起始角和终止角同时加上this.startAngle值 |
| | | let endAngle = ((2 * Math.PI) / 100) * progress + this.startAngle; |
| | | ctx.beginPath(); |
| | | // 半径为整个canvas宽度的一半 |
| | | let radius = this.widthPx / 2; |
| | | ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false); |
| | | ctx.stroke(); |
| | | ctx.draw(); |
| | | // 如果变更后新值大于旧值,意味着增大了百分比 |
| | | if (this.newPercent > this.oldPercent) { |
| | | // 每次递增百分之一 |
| | | progress++; |
| | | // 如果新增后的值,大于需要设置的值百分比值,停止继续增加 |
| | | if (progress > this.newPercent) return; |
| | | } else { |
| | | // 同理于上面 |
| | | progress--; |
| | | if (progress < this.newPercent) return; |
| | | } |
| | | setTimeout(() => { |
| | | // 定时器,每次操作间隔为time值,为了让进度条有动画效果 |
| | | this.drawCircleByProgress(progress); |
| | | }, time); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | .u-circle-progress { |
| | | position: relative; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .u-canvas-bg { |
| | | position: absolute; |
| | | } |
| | | |
| | | .u-canvas { |
| | | position: absolute; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-progress" :style="{ |
| | | borderRadius: round ? '100rpx' : 0, |
| | | height: height + 'rpx', |
| | | backgroundColor: inactiveColor |
| | | }"> |
| | | <view :class="[ |
| | | type ? `u-type-${type}-bg` : '', |
| | | striped ? 'u-striped' : '', |
| | | striped && stripedActive ? 'u-striped-active' : '' |
| | | ]" class="u-active" :style="[progressStyle]"> |
| | | <slot v-if="$slots.default || $slots.$default" /> |
| | | <block v-else-if="showPercent"> |
| | | {{percent + '%'}} |
| | | </block> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * lineProgress 线型进度条 |
| | | * @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。 |
| | | * @tutorial https://www.uviewui.com/components/lineProgress.html |
| | | * @property {String Number} percent 进度条百分比值,为数值类型,0-100 |
| | | * @property {Boolean} round 进度条两端是否为半圆(默认true) |
| | | * @property {String} type 如设置,active-color值将会失效 |
| | | * @property {String} active-color 进度条激活部分的颜色(默认#19be6b) |
| | | * @property {String} inactive-color 进度条的底色(默认#ececec) |
| | | * @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值(默认true) |
| | | * @property {String Number} height 进度条的高度,单位rpx(默认28) |
| | | * @property {Boolean} striped 是否显示进度条激活部分的条纹(默认false) |
| | | * @property {Boolean} striped-active 条纹是否具有动态效果(默认false) |
| | | * @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress> |
| | | */ |
| | | export default { |
| | | name: "u-line-progress", |
| | | props: { |
| | | // 两端是否显示半圆形 |
| | | round: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 主题颜色 |
| | | type: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 激活部分的颜色 |
| | | activeColor: { |
| | | type: String, |
| | | default: '#19be6b' |
| | | }, |
| | | inactiveColor: { |
| | | type: String, |
| | | default: '#ececec' |
| | | }, |
| | | // 进度百分比,数值 |
| | | percent: { |
| | | type: Number, |
| | | default: 0 |
| | | }, |
| | | // 是否在进度条内部显示百分比的值 |
| | | showPercent: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 进度条的高度,单位rpx |
| | | height: { |
| | | type: [Number, String], |
| | | default: 28 |
| | | }, |
| | | // 是否显示条纹 |
| | | striped: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 条纹是否显示活动状态 |
| | | stripedActive: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | |
| | | } |
| | | }, |
| | | computed: { |
| | | progressStyle() { |
| | | let style = {}; |
| | | style.width = this.percent + '%'; |
| | | if(this.activeColor) style.backgroundColor = this.activeColor; |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-progress { |
| | | overflow: hidden; |
| | | height: 15px; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | width: 100%; |
| | | border-radius: 100rpx; |
| | | } |
| | | |
| | | .u-active { |
| | | width: 0; |
| | | height: 100%; |
| | | align-items: center; |
| | | @include vue-flex; |
| | | justify-items: flex-end; |
| | | justify-content: space-around; |
| | | font-size: 20rpx; |
| | | color: #ffffff; |
| | | transition: all 0.4s ease; |
| | | } |
| | | |
| | | .u-striped { |
| | | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); |
| | | background-size: 39px 39px; |
| | | } |
| | | |
| | | .u-striped-active { |
| | | animation: progress-stripes 2s linear infinite; |
| | | } |
| | | |
| | | @keyframes progress-stripes { |
| | | 0% { |
| | | background-position: 0 0; |
| | | } |
| | | |
| | | 100% { |
| | | background-position: 39px 0; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-col" :class="[ |
| | | 'u-col-' + span |
| | | ]" :style="{ |
| | | padding: `0 ${Number(gutter)/2 + 'rpx'}`, |
| | | marginLeft: 100 / 12 * offset + '%', |
| | | flex: `0 0 ${100 / 12 * span}%`, |
| | | alignItems: uAlignItem, |
| | | justifyContent: uJustify, |
| | | textAlign: textAlign |
| | | }" |
| | | @tap="click"> |
| | | <slot></slot> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * col 布局单元格 |
| | | * @description 通过基础的 12 分栏,迅速简便地创建布局(搭配<u-row>使用) |
| | | * @tutorial https://www.uviewui.com/components/layout.html |
| | | * @property {String Number} span 栅格占据的列数,总12等分(默认0) |
| | | * @property {String} text-align 文字水平对齐方式(默认left) |
| | | * @property {String Number} offset 分栏左边偏移,计算方式与span相同(默认0) |
| | | * @example <u-col span="3"><view class="demo-layout bg-purple"></view></u-col> |
| | | */ |
| | | export default { |
| | | name: "u-col", |
| | | props: { |
| | | // 占父容器宽度的多少等分,总分为12份 |
| | | span: { |
| | | type: [Number, String], |
| | | default: 12 |
| | | }, |
| | | // 指定栅格左侧的间隔数(总12栏) |
| | | offset: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) |
| | | justify: { |
| | | type: String, |
| | | default: 'start' |
| | | }, |
| | | // 垂直对齐方式,可选值为top、center、bottom |
| | | align: { |
| | | type: String, |
| | | default: 'center' |
| | | }, |
| | | // 文字对齐方式 |
| | | textAlign: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | // 是否阻止事件传播 |
| | | stop: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | gutter: 20, // 给col添加间距,左右边距各占一半,从父组件u-row获取 |
| | | } |
| | | }, |
| | | created() { |
| | | this.parent = false; |
| | | }, |
| | | mounted() { |
| | | // 获取父组件实例,并赋值给对应的参数 |
| | | this.parent = this.$u.$parent.call(this, 'u-row'); |
| | | if (this.parent) { |
| | | this.gutter = this.parent.gutter; |
| | | } |
| | | }, |
| | | computed: { |
| | | uJustify() { |
| | | if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify; |
| | | else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify; |
| | | else return this.justify; |
| | | }, |
| | | uAlignItem() { |
| | | if (this.align == 'top') return 'flex-start'; |
| | | if (this.align == 'bottom') return 'flex-end'; |
| | | else return this.align; |
| | | } |
| | | }, |
| | | methods: { |
| | | click(e) { |
| | | this.$emit('click'); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-col { |
| | | /* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */ |
| | | float: left; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .u-col-0 { |
| | | width: 0; |
| | | } |
| | | |
| | | .u-col-1 { |
| | | width: calc(100%/12); |
| | | } |
| | | |
| | | .u-col-2 { |
| | | width: calc(100%/12 * 2); |
| | | } |
| | | |
| | | .u-col-3 { |
| | | width: calc(100%/12 * 3); |
| | | } |
| | | |
| | | .u-col-4 { |
| | | width: calc(100%/12 * 4); |
| | | } |
| | | |
| | | .u-col-5 { |
| | | width: calc(100%/12 * 5); |
| | | } |
| | | |
| | | .u-col-6 { |
| | | width: calc(100%/12 * 6); |
| | | } |
| | | |
| | | .u-col-7 { |
| | | width: calc(100%/12 * 7); |
| | | } |
| | | |
| | | .u-col-8 { |
| | | width: calc(100%/12 * 8); |
| | | } |
| | | |
| | | .u-col-9 { |
| | | width: calc(100%/12 * 9); |
| | | } |
| | | |
| | | .u-col-10 { |
| | | width: calc(100%/12 * 10); |
| | | } |
| | | |
| | | .u-col-11 { |
| | | width: calc(100%/12 * 11); |
| | | } |
| | | |
| | | .u-col-12 { |
| | | width: calc(100%/12 * 12); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-collapse-item" :style="[itemStyle]"> |
| | | <view :hover-stay-time="200" class="u-collapse-head" @tap.stop="headClick" :hover-class="hoverClass" :style="[headStyle]"> |
| | | <block v-if="!$slots['title-all']"> |
| | | <view v-if="!$slots['title']" class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' }, |
| | | isShow && activeStyle && !arrow ? activeStyle : '']"> |
| | | {{ title }} |
| | | </view> |
| | | <slot v-else name="title" /> |
| | | <view class="u-icon-wrap"> |
| | | <u-icon v-if="arrow" :color="arrowColor" :class="{ 'u-arrow-down-icon-active': isShow }" |
| | | class="u-arrow-down-icon" name="arrow-down"></u-icon> |
| | | </view> |
| | | </block> |
| | | <slot v-else name="title-all" /> |
| | | </view> |
| | | <view class="u-collapse-body" :style="[{ |
| | | height: isShow ? height + 'px' : '0' |
| | | }]"> |
| | | <view class="u-collapse-content" :id="elId" :style="[bodyStyle]"> |
| | | <slot></slot> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * collapseItem 手风琴Item |
| | | * @description 通过折叠面板收纳内容区域(搭配u-collapse使用) |
| | | * @tutorial https://www.uviewui.com/components/collapse.html |
| | | * @property {String} title 面板标题 |
| | | * @property {String Number} index 主要用于事件的回调,标识那个Item被点击 |
| | | * @property {Boolean} disabled 面板是否可以打开或收起(默认false) |
| | | * @property {Boolean} open 设置某个面板的初始状态是否打开(默认false) |
| | | * @property {String Number} name 唯一标识符,如不设置,默认用当前collapse-item的索引值 |
| | | * @property {String} align 标题的对齐方式(默认left) |
| | | * @property {Object} active-style 不显示箭头时,可以添加当前选择的collapse-item活动样式,对象形式 |
| | | * @event {Function} change 某个item被打开或者收起时触发 |
| | | * @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item> |
| | | */ |
| | | export default { |
| | | name: "u-collapse-item", |
| | | props: { |
| | | // 标题 |
| | | title: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 标题的对齐方式 |
| | | align: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | // 是否可以点击收起 |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // collapse显示与否 |
| | | open: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 唯一标识符 |
| | | name: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | //活动样式 |
| | | activeStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 标识当前为第几个 |
| | | index: { |
| | | type: [String, Number], |
| | | default: '' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | isShow: false, |
| | | elId: this.$u.guid(), |
| | | height: 0, // body内容的高度 |
| | | headStyle: {}, // 头部样式,对象形式 |
| | | bodyStyle: {}, // 主体部分样式 |
| | | itemStyle: {}, // 每个item的整体样式 |
| | | arrowColor: '', // 箭头的颜色 |
| | | hoverClass: '', // 头部按下时的效果样式类 |
| | | arrow: true, // 是否显示右侧箭头 |
| | | |
| | | }; |
| | | }, |
| | | watch: { |
| | | open(val) { |
| | | this.isShow = val; |
| | | } |
| | | }, |
| | | created() { |
| | | this.parent = false; |
| | | // 获取u-collapse的信息,放在u-collapse是为了方便,不用每个u-collapse-item写一遍 |
| | | this.isShow = this.open; |
| | | }, |
| | | methods: { |
| | | // 异步获取内容,或者动态修改了内容时,需要重新初始化 |
| | | init() { |
| | | this.parent = this.$u.$parent.call(this, 'u-collapse'); |
| | | if(this.parent) { |
| | | this.nameSync = this.name ? this.name : this.parent.childrens.length; |
| | | this.parent.childrens.push(this); |
| | | this.headStyle = this.parent.headStyle; |
| | | this.bodyStyle = this.parent.bodyStyle; |
| | | this.arrowColor = this.parent.arrowColor; |
| | | this.hoverClass = this.parent.hoverClass; |
| | | this.arrow = this.parent.arrow; |
| | | this.itemStyle = this.parent.itemStyle; |
| | | } |
| | | this.$nextTick(() => { |
| | | this.queryRect(); |
| | | }); |
| | | }, |
| | | // 点击collapsehead头部 |
| | | headClick() { |
| | | if (this.disabled) return; |
| | | if (this.parent && this.parent.accordion == true) { |
| | | this.parent.childrens.map(val => { |
| | | // 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了 |
| | | if (this != val) { |
| | | val.isShow = false; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | this.isShow = !this.isShow; |
| | | // 触发本组件的事件 |
| | | this.$emit('change', { |
| | | index: this.index, |
| | | show: this.isShow |
| | | }) |
| | | // 只有在打开时才发出事件 |
| | | if (this.isShow) this.parent && this.parent.onChange(); |
| | | this.$forceUpdate(); |
| | | }, |
| | | // 查询内容高度 |
| | | queryRect() { |
| | | // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html |
| | | // 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同 |
| | | this.$uGetRect('#' + this.elId).then(res => { |
| | | this.height = res.height; |
| | | }) |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.init(); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-collapse-head { |
| | | position: relative; |
| | | @include vue-flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | color: $u-main-color; |
| | | font-size: 30rpx; |
| | | line-height: 1; |
| | | padding: 24rpx 0; |
| | | text-align: left; |
| | | } |
| | | |
| | | .u-collapse-title { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-arrow-down-icon { |
| | | transition: all 0.3s; |
| | | margin-right: 20rpx; |
| | | margin-left: 14rpx; |
| | | } |
| | | |
| | | .u-arrow-down-icon-active { |
| | | transform: rotate(180deg); |
| | | transform-origin: center center; |
| | | } |
| | | |
| | | .u-collapse-body { |
| | | overflow: hidden; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .u-collapse-content { |
| | | overflow: hidden; |
| | | font-size: 28rpx; |
| | | color: $u-tips-color; |
| | | text-align: left; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-collapse"> |
| | | <slot /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * collapse 手风琴 |
| | | * @description 通过折叠面板收纳内容区域 |
| | | * @tutorial https://www.uviewui.com/components/collapse.html |
| | | * @property {Boolean} accordion 是否手风琴模式(默认true) |
| | | * @property {Boolean} arrow 是否显示标题右侧的箭头(默认true) |
| | | * @property {String} arrow-color 标题右侧箭头的颜色(默认#909399) |
| | | * @property {Object} head-style 标题自定义样式,对象形式 |
| | | * @property {Object} body-style 主体自定义样式,对象形式 |
| | | * @property {String} hover-class 样式类名,按下时有效(默认u-hover-class) |
| | | * @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array) |
| | | * @example <u-collapse></u-collapse> |
| | | */ |
| | | export default { |
| | | name:"u-collapse", |
| | | props: { |
| | | // 是否手风琴模式 |
| | | accordion: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 头部的样式 |
| | | headStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 主体的样式 |
| | | bodyStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 每一个item的样式 |
| | | itemStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 是否显示右侧的箭头 |
| | | arrow: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 箭头的颜色 |
| | | arrowColor: { |
| | | type: String, |
| | | default: '#909399' |
| | | }, |
| | | // 标题部分按压时的样式类,"none"为无效果 |
| | | hoverClass: { |
| | | type: String, |
| | | default: 'u-hover-class' |
| | | } |
| | | }, |
| | | created() { |
| | | this.childrens = [] |
| | | }, |
| | | data() { |
| | | return { |
| | | |
| | | } |
| | | }, |
| | | methods: { |
| | | // 重新初始化一次内部的所有子元素的高度计算,用于异步获取数据渲染的情况 |
| | | init() { |
| | | this.childrens.forEach((vm, index) => { |
| | | vm.init(); |
| | | }) |
| | | }, |
| | | // collapse item被点击,由collapse item调用父组件方法 |
| | | onChange() { |
| | | let activeItem = []; |
| | | this.childrens.forEach((vm, index) => { |
| | | if (vm.isShow) { |
| | | activeItem.push(vm.nameSync); |
| | | } |
| | | }) |
| | | // 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串 |
| | | if (this.accordion) activeItem = activeItem.join(''); |
| | | this.$emit('change', activeItem); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view |
| | | class="u-notice-bar" |
| | | :style="{ |
| | | background: computeBgColor, |
| | | padding: padding |
| | | }" |
| | | :class="[ |
| | | type ? `u-type-${type}-light-bg` : '' |
| | | ]" |
| | | > |
| | | <view class="u-icon-wrap"> |
| | | <u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon> |
| | | </view> |
| | | <swiper :disable-touch="disableTouch" @change="change" :autoplay="autoplay && playState == 'play'" :vertical="vertical" circular :interval="duration" class="u-swiper"> |
| | | <swiper-item v-for="(item, index) in list" :key="index" class="u-swiper-item"> |
| | | <view |
| | | class="u-news-item u-line-1" |
| | | :style="[textStyle]" |
| | | @tap="click(index)" |
| | | :class="['u-type-' + type]" |
| | | > |
| | | {{ item }} |
| | | </view> |
| | | </swiper-item> |
| | | </swiper> |
| | | <view class="u-icon-wrap"> |
| | | <u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon> |
| | | <u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | props: { |
| | | // 显示的内容,数组 |
| | | list: { |
| | | type: Array, |
| | | default() { |
| | | return []; |
| | | } |
| | | }, |
| | | // 显示的主题,success|error|primary|info|warning |
| | | type: { |
| | | type: String, |
| | | default: 'warning' |
| | | }, |
| | | // 是否显示左侧的音量图标 |
| | | volumeIcon: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示右侧的右箭头图标 |
| | | moreIcon: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示右侧的关闭图标 |
| | | closeIcon: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否自动播放 |
| | | autoplay: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 文字颜色,各图标也会使用文字颜色 |
| | | color: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 滚动方向,row-水平滚动,column-垂直滚动 |
| | | direction: { |
| | | type: String, |
| | | default: 'row' |
| | | }, |
| | | // 是否显示 |
| | | show: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 字体大小,单位rpx |
| | | fontSize: { |
| | | type: [Number, String], |
| | | default: 26 |
| | | }, |
| | | // 滚动一个周期的时间长,单位ms |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 2000 |
| | | }, |
| | | // 音量喇叭的大小 |
| | | volumeSize: { |
| | | type: [Number, String], |
| | | default: 34 |
| | | }, |
| | | // 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度 |
| | | speed: { |
| | | type: Number, |
| | | default: 160 |
| | | }, |
| | | // 水平滚动时,是否采用衔接形式滚动 |
| | | isCircular: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 滚动方向,horizontal-水平滚动,vertical-垂直滚动 |
| | | mode: { |
| | | type: String, |
| | | default: 'horizontal' |
| | | }, |
| | | // 播放状态,play-播放,paused-暂停 |
| | | playState: { |
| | | type: String, |
| | | default: 'play' |
| | | }, |
| | | // 是否禁止用手滑动切换 |
| | | // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 |
| | | disableTouch: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 通知的边距 |
| | | padding: { |
| | | type: [Number, String], |
| | | default: '18rpx 24rpx' |
| | | } |
| | | }, |
| | | computed: { |
| | | // 计算字体颜色,如果没有自定义的,就用uview主题颜色 |
| | | computeColor() { |
| | | if (this.color) return this.color; |
| | | // 如果是无主题,就默认使用content-color |
| | | else if(this.type == 'none') return '#606266'; |
| | | else return this.type; |
| | | }, |
| | | // 文字内容的样式 |
| | | textStyle() { |
| | | let style = {}; |
| | | if (this.color) style.color = this.color; |
| | | else if(this.type == 'none') style.color = '#606266'; |
| | | style.fontSize = this.fontSize + 'rpx'; |
| | | return style; |
| | | }, |
| | | // 垂直或者水平滚动 |
| | | vertical() { |
| | | if(this.mode == 'horizontal') return false; |
| | | else return true; |
| | | }, |
| | | // 计算背景颜色 |
| | | computeBgColor() { |
| | | if (this.bgColor) return this.bgColor; |
| | | else if(this.type == 'none') return 'transparent'; |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | // animation: false |
| | | }; |
| | | }, |
| | | methods: { |
| | | // 点击通告栏 |
| | | click(index) { |
| | | this.$emit('click', index); |
| | | }, |
| | | // 点击关闭按钮 |
| | | close() { |
| | | this.$emit('close'); |
| | | }, |
| | | // 点击更多箭头按钮 |
| | | getMore() { |
| | | this.$emit('getMore'); |
| | | }, |
| | | change(e) { |
| | | let index = e.detail.current; |
| | | if(index == this.list.length - 1) { |
| | | this.$emit('end'); |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-notice-bar { |
| | | width: 100%; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-wrap: nowrap; |
| | | padding: 18rpx 24rpx; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-swiper { |
| | | font-size: 26rpx; |
| | | height: 32rpx; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | margin-left: 12rpx; |
| | | } |
| | | |
| | | .u-swiper-item { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-news-item { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-right-icon { |
| | | margin-left: 12rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-left-icon { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-countdown"> |
| | | <view class="u-countdown-item" :style="[itemStyle]" v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"> |
| | | <view class="u-countdown-time" :style="[letterStyle]"> |
| | | {{ d }} |
| | | </view> |
| | | </view> |
| | | <view |
| | | class="u-countdown-colon" |
| | | :style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" |
| | | v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))" |
| | | > |
| | | {{ separator == 'colon' ? ':' : '天' }} |
| | | </view> |
| | | <view class="u-countdown-item" :style="[itemStyle]" v-if="showHours"> |
| | | <view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}"> |
| | | {{ h }} |
| | | </view> |
| | | </view> |
| | | <view |
| | | class="u-countdown-colon" |
| | | :style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" |
| | | v-if="showHours" |
| | | > |
| | | {{ separator == 'colon' ? ':' : '时' }} |
| | | </view> |
| | | <view class="u-countdown-item" :style="[itemStyle]" v-if="showMinutes"> |
| | | <view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}"> |
| | | {{ i }} |
| | | </view> |
| | | </view> |
| | | <view |
| | | class="u-countdown-colon" |
| | | :style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" |
| | | v-if="showMinutes" |
| | | > |
| | | {{ separator == 'colon' ? ':' : '分' }} |
| | | </view> |
| | | <view class="u-countdown-item" :style="[itemStyle]" v-if="showSeconds"> |
| | | <view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}"> |
| | | {{ s }} |
| | | </view> |
| | | </view> |
| | | <view |
| | | class="u-countdown-colon" |
| | | :style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" |
| | | v-if="showSeconds && separator == 'zh'" |
| | | > |
| | | 秒 |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * countDown 倒计时 |
| | | * @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。 |
| | | * @tutorial https://www.uviewui.com/components/countDown.html |
| | | * @property {String Number} timestamp 倒计时,单位为秒 |
| | | * @property {Boolean} autoplay 是否自动开始倒计时,如果为false,需手动调用开始方法。见官网说明(默认true) |
| | | * @property {String} separator 分隔符,colon为英文冒号,zh为中文(默认colon) |
| | | * @property {String Number} separator-size 分隔符的字体大小,单位rpx(默认30) |
| | | * @property {String} separator-color 分隔符的颜色(默认#303133) |
| | | * @property {String Number} font-size 倒计时字体大小,单位rpx(默认30) |
| | | * @property {Boolean} show-border 是否显示倒计时数字的边框(默认false) |
| | | * @property {Boolean} hide-zero-day 当"天"的部分为0时,隐藏该字段 (默认true) |
| | | * @property {String} border-color 数字边框的颜色(默认#303133) |
| | | * @property {String} bg-color 倒计时数字的背景颜色(默认#ffffff) |
| | | * @property {String} color 倒计时数字的颜色(默认#303133) |
| | | * @property {String} height 数字高度值(宽度等同此值),设置边框时看情况是否需要设置此值,单位rpx(默认auto) |
| | | * @property {Boolean} show-days 是否显示倒计时的"天"部分(默认true) |
| | | * @property {Boolean} show-hours 是否显示倒计时的"时"部分(默认true) |
| | | * @property {Boolean} show-minutes 是否显示倒计时的"分"部分(默认true) |
| | | * @property {Boolean} show-seconds 是否显示倒计时的"秒"部分(默认true) |
| | | * @event {Function} end 倒计时结束 |
| | | * @event {Function} change 每秒触发一次,回调为当前剩余的倒计秒数 |
| | | * @example <u-count-down ref="uCountDown" :timestamp="86400" :autoplay="false"></u-count-down> |
| | | */ |
| | | export default { |
| | | name: 'u-count-down', |
| | | props: { |
| | | // 倒计时的时间,秒为单位 |
| | | timestamp: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 是否自动开始倒计时 |
| | | autoplay: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 用英文冒号(colon)或者中文(zh)当做分隔符,false的时候为中文,如:"11:22"或"11时22秒" |
| | | separator: { |
| | | type: String, |
| | | default: 'colon' |
| | | }, |
| | | // 分隔符的大小,单位rpx |
| | | separatorSize: { |
| | | type: [Number, String], |
| | | default: 30 |
| | | }, |
| | | // 分隔符颜色 |
| | | separatorColor: { |
| | | type: String, |
| | | default: "#303133" |
| | | }, |
| | | // 字体颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#303133' |
| | | }, |
| | | // 字体大小,单位rpx |
| | | fontSize: { |
| | | type: [Number, String], |
| | | default: 30 |
| | | }, |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '#fff' |
| | | }, |
| | | // 数字框高度,单位rpx |
| | | height: { |
| | | type: [Number, String], |
| | | default: 'auto' |
| | | }, |
| | | // 是否显示数字框 |
| | | showBorder: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 边框颜色 |
| | | borderColor: { |
| | | type: String, |
| | | default: '#303133' |
| | | }, |
| | | // 是否显示秒 |
| | | showSeconds: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示分钟 |
| | | showMinutes: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示小时 |
| | | showHours: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示“天” |
| | | showDays: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 当"天"的部分为0时,不显示 |
| | | hideZeroDay: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | watch: { |
| | | // 监听时间戳的变化 |
| | | timestamp(newVal, oldVal) { |
| | | // 如果倒计时间发生变化,清除定时器,重新开始倒计时 |
| | | this.clearTimer(); |
| | | this.start(); |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | d: '00', // 天的默认值 |
| | | h: '00', // 小时的默认值 |
| | | i: '00', // 分钟的默认值 |
| | | s: '00', // 秒的默认值 |
| | | timer: null ,// 定时器 |
| | | seconds: 0, // 记录不停倒计过程中变化的秒数 |
| | | }; |
| | | }, |
| | | computed: { |
| | | // 倒计时item的样式,item为分别的时分秒部分的数字 |
| | | itemStyle() { |
| | | let style = {}; |
| | | if(this.height) { |
| | | style.height = this.height + 'rpx'; |
| | | style.width = this.height + 'rpx'; |
| | | } |
| | | if(this.showBorder) { |
| | | style.borderStyle = 'solid'; |
| | | style.borderColor = this.borderColor; |
| | | style.borderWidth = '1px'; |
| | | } |
| | | if(this.bgColor) { |
| | | style.backgroundColor = this.bgColor; |
| | | } |
| | | return style; |
| | | }, |
| | | // 倒计时数字的样式 |
| | | letterStyle() { |
| | | let style = {}; |
| | | if(this.fontSize) style.fontSize = this.fontSize + 'rpx'; |
| | | if(this.color) style.color = this.color; |
| | | return style; |
| | | } |
| | | }, |
| | | mounted() { |
| | | // 如果自动倒计时 |
| | | this.autoplay && this.timestamp && this.start(); |
| | | }, |
| | | methods: { |
| | | // 倒计时 |
| | | start() { |
| | | // 避免可能出现的倒计时重叠情况 |
| | | this.clearTimer(); |
| | | if (this.timestamp <= 0) return; |
| | | this.seconds = Number(this.timestamp); |
| | | this.formatTime(this.seconds); |
| | | this.timer = setInterval(() => { |
| | | this.seconds--; |
| | | // 发出change事件 |
| | | this.$emit('change', this.seconds); |
| | | if (this.seconds < 0) { |
| | | return this.end(); |
| | | } |
| | | this.formatTime(this.seconds); |
| | | }, 1000); |
| | | }, |
| | | // 格式化时间 |
| | | formatTime(seconds) { |
| | | // 小于等于0的话,结束倒计时 |
| | | seconds <= 0 && this.end(); |
| | | let [day, hour, minute, second] = [0, 0, 0, 0]; |
| | | day = Math.floor(seconds / (60 * 60 * 24)); |
| | | // 判断是否显示“天”参数,如果不显示,将天部分的值,加入到小时中 |
| | | // hour为给后面计算秒和分等用的(基于显示天的前提下计算) |
| | | hour = Math.floor(seconds / (60 * 60)) - day * 24; |
| | | // showHour为需要显示的小时 |
| | | let showHour = null; |
| | | if(this.showDays) { |
| | | showHour = hour; |
| | | } else { |
| | | // 如果不显示天数,将“天”部分的时间折算到小时中去 |
| | | showHour = Math.floor(seconds / (60 * 60)); |
| | | } |
| | | minute = Math.floor(seconds / 60) - hour * 60 - day * 24 * 60; |
| | | second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60; |
| | | // 如果小于10,在前面补上一个"0" |
| | | showHour = showHour < 10 ? '0' + showHour : showHour; |
| | | minute = minute < 10 ? '0' + minute : minute; |
| | | second = second < 10 ? '0' + second : second; |
| | | day = day < 10 ? '0' + day : day; |
| | | this.d = day; |
| | | this.h = showHour; |
| | | this.i = minute; |
| | | this.s = second; |
| | | }, |
| | | // 停止倒计时 |
| | | end() { |
| | | this.clearTimer(); |
| | | this.$emit('end', {}); |
| | | }, |
| | | // 清除定时器 |
| | | clearTimer() { |
| | | if(this.timer) { |
| | | // 清除定时器 |
| | | clearInterval(this.timer); |
| | | this.timer = null; |
| | | } |
| | | } |
| | | }, |
| | | beforeDestroy() { |
| | | clearInterval(this.timer); |
| | | this.timer = null; |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-countdown { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-countdown-item { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 2rpx; |
| | | border-radius: 6rpx; |
| | | white-space: nowrap; |
| | | transform: translateZ(0); |
| | | } |
| | | |
| | | .u-countdown-time { |
| | | margin: 0; |
| | | padding: 0; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .u-countdown-colon { |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | padding: 0 5rpx; |
| | | line-height: 1; |
| | | align-items: center; |
| | | padding-bottom: 4rpx; |
| | | } |
| | | |
| | | .u-countdown-scale { |
| | | transform: scale(0.9); |
| | | transform-origin: center center; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view |
| | | class="u-count-num" |
| | | :style="{ |
| | | fontSize: fontSize + 'rpx', |
| | | fontWeight: bold ? 'bold' : 'normal', |
| | | color: color |
| | | }" |
| | | > |
| | | {{ displayValue }} |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * countTo 数字滚动 |
| | | * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。 |
| | | * @tutorial https://www.uviewui.com/components/countTo.html |
| | | * @property {String Number} start-val 开始值 |
| | | * @property {String Number} end-val 结束值 |
| | | * @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000) |
| | | * @property {Boolean} autoplay 是否自动开始滚动(默认true) |
| | | * @property {String Number} decimals 要显示的小数位数,见官网说明(默认0) |
| | | * @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true) |
| | | * @property {String} separator 千位分隔符,见官网说明 |
| | | * @property {String} color 字体颜色(默认#303133) |
| | | * @property {String Number} font-size 字体大小,单位rpx(默认50) |
| | | * @property {Boolean} bold 字体是否加粗(默认false) |
| | | * @event {Function} end 数值滚动到目标值时触发 |
| | | * @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to> |
| | | */ |
| | | export default { |
| | | name: 'u-count-to', |
| | | props: { |
| | | // 开始的数值,默认从0增长到某一个数 |
| | | startVal: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 要滚动的目标数值,必须 |
| | | endVal: { |
| | | type: [Number, String], |
| | | default: 0, |
| | | required: true |
| | | }, |
| | | // 滚动到目标数值的动画持续时间,单位为毫秒(ms) |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 2000 |
| | | }, |
| | | // 设置数值后是否自动开始滚动 |
| | | autoplay: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 要显示的小数位数 |
| | | decimals: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 是否在即将到达目标数值的时候,使用缓慢滚动的效果 |
| | | useEasing: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 十进制分割 |
| | | decimal: { |
| | | type: [Number, String], |
| | | default: '.' |
| | | }, |
| | | // 字体颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#303133' |
| | | }, |
| | | // 字体大小 |
| | | fontSize: { |
| | | type: [Number, String], |
| | | default: 50 |
| | | }, |
| | | // 是否加粗字体 |
| | | bold: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 千位分隔符,类似金额的分割(¥23,321.05中的",") |
| | | separator: { |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | localStartVal: this.startVal, |
| | | displayValue: this.formatNumber(this.startVal), |
| | | printVal: null, |
| | | paused: false, // 是否暂停 |
| | | localDuration: Number(this.duration), |
| | | startTime: null, // 开始的时间 |
| | | timestamp: null, // 时间戳 |
| | | remaining: null, // 停留的时间 |
| | | rAF: null, |
| | | lastTime: 0 // 上一次的时间 |
| | | }; |
| | | }, |
| | | computed: { |
| | | countDown() { |
| | | return this.startVal > this.endVal; |
| | | } |
| | | }, |
| | | watch: { |
| | | startVal() { |
| | | this.autoplay && this.start(); |
| | | }, |
| | | endVal() { |
| | | this.autoplay && this.start(); |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.autoplay && this.start(); |
| | | }, |
| | | methods: { |
| | | easingFn(t, b, c, d) { |
| | | return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b; |
| | | }, |
| | | requestAnimationFrame(callback) { |
| | | const currTime = new Date().getTime(); |
| | | // 为了使setTimteout的尽可能的接近每秒60帧的效果 |
| | | const timeToCall = Math.max(0, 16 - (currTime - this.lastTime)); |
| | | const id = setTimeout(() => { |
| | | callback(currTime + timeToCall); |
| | | }, timeToCall); |
| | | this.lastTime = currTime + timeToCall; |
| | | return id; |
| | | }, |
| | | |
| | | cancelAnimationFrame(id) { |
| | | clearTimeout(id); |
| | | }, |
| | | // 开始滚动数字 |
| | | start() { |
| | | this.localStartVal = this.startVal; |
| | | this.startTime = null; |
| | | this.localDuration = this.duration; |
| | | this.paused = false; |
| | | this.rAF = this.requestAnimationFrame(this.count); |
| | | }, |
| | | // 暂定状态,重新再开始滚动;或者滚动状态下,暂停 |
| | | reStart() { |
| | | if (this.paused) { |
| | | this.resume(); |
| | | this.paused = false; |
| | | } else { |
| | | this.stop(); |
| | | this.paused = true; |
| | | } |
| | | }, |
| | | // 暂停 |
| | | stop() { |
| | | this.cancelAnimationFrame(this.rAF); |
| | | }, |
| | | // 重新开始(暂停的情况下) |
| | | resume() { |
| | | this.startTime = null; |
| | | this.localDuration = this.remaining; |
| | | this.localStartVal = this.printVal; |
| | | this.requestAnimationFrame(this.count); |
| | | }, |
| | | // 重置 |
| | | reset() { |
| | | this.startTime = null; |
| | | this.cancelAnimationFrame(this.rAF); |
| | | this.displayValue = this.formatNumber(this.startVal); |
| | | }, |
| | | count(timestamp) { |
| | | if (!this.startTime) this.startTime = timestamp; |
| | | this.timestamp = timestamp; |
| | | const progress = timestamp - this.startTime; |
| | | this.remaining = this.localDuration - progress; |
| | | if (this.useEasing) { |
| | | if (this.countDown) { |
| | | this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration); |
| | | } else { |
| | | this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration); |
| | | } |
| | | } else { |
| | | if (this.countDown) { |
| | | this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration); |
| | | } else { |
| | | this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration); |
| | | } |
| | | } |
| | | if (this.countDown) { |
| | | this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal; |
| | | } else { |
| | | this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal; |
| | | } |
| | | this.displayValue = this.formatNumber(this.printVal); |
| | | if (progress < this.localDuration) { |
| | | this.rAF = this.requestAnimationFrame(this.count); |
| | | } else { |
| | | this.$emit('end'); |
| | | } |
| | | }, |
| | | // 判断是否数字 |
| | | isNumber(val) { |
| | | return !isNaN(parseFloat(val)); |
| | | }, |
| | | formatNumber(num) { |
| | | // 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错 |
| | | num = Number(num); |
| | | num = num.toFixed(Number(this.decimals)); |
| | | num += ''; |
| | | const x = num.split('.'); |
| | | let x1 = x[0]; |
| | | const x2 = x.length > 1 ? this.decimal + x[1] : ''; |
| | | const rgx = /(\d+)(\d{3})/; |
| | | if (this.separator && !this.isNumber(this.separator)) { |
| | | while (rgx.test(x1)) { |
| | | x1 = x1.replace(rgx, '$1' + this.separator + '$2'); |
| | | } |
| | | } |
| | | return x1 + x2; |
| | | }, |
| | | destroyed() { |
| | | this.cancelAnimationFrame(this.rAF); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-count-num { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | text-align: center; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-divider" :style="{ |
| | | height: height == 'auto' ? 'auto' : height + 'rpx', |
| | | backgroundColor: bgColor, |
| | | marginBottom: marginBottom + 'rpx', |
| | | marginTop: marginTop + 'rpx' |
| | | }" @tap="click"> |
| | | <view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view> |
| | | <view v-if="useSlot" class="u-divider-text" :style="{ |
| | | color: color, |
| | | fontSize: fontSize + 'rpx' |
| | | }"><slot /></view> |
| | | <view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * divider 分割线 |
| | | * @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。 |
| | | * @tutorial https://www.uviewui.com/components/divider.html |
| | | * @property {String Number} half-width 文字左或右边线条宽度,数值或百分比,数值时单位为rpx |
| | | * @property {String} border-color 线条颜色,优先级高于type(默认#dcdfe6) |
| | | * @property {String} color 文字颜色(默认#909399) |
| | | * @property {String Number} fontSize 字体大小,单位rpx(默认26) |
| | | * @property {String} bg-color 整个divider的背景颜色(默认呢#ffffff) |
| | | * @property {String Number} height 整个divider的高度,单位rpx(默认40) |
| | | * @property {String} type 将线条设置主题色(默认primary) |
| | | * @property {Boolean} useSlot 是否使用slot传入内容,如果不传入,中间不会有空隙(默认true) |
| | | * @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0) |
| | | * @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0) |
| | | * @event {Function} click divider组件被点击时触发 |
| | | * @example <u-divider color="#fa3534">长河落日圆</u-divider> |
| | | */ |
| | | export default { |
| | | name: 'u-divider', |
| | | props: { |
| | | // 单一边divider横线的宽度(数值),单位rpx。或者百分比 |
| | | halfWidth: { |
| | | type: [Number, String], |
| | | default: 150 |
| | | }, |
| | | // divider横线的颜色,如设置, |
| | | borderColor: { |
| | | type: String, |
| | | default: '#dcdfe6' |
| | | }, |
| | | // 主题色,可以是primary|info|success|warning|error之一值 |
| | | type: { |
| | | type: String, |
| | | default: 'primary' |
| | | }, |
| | | // 文字颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#909399' |
| | | }, |
| | | // 文字大小,单位rpx |
| | | fontSize: { |
| | | type: [Number, String], |
| | | default: 26 |
| | | }, |
| | | // 整个divider的背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '#ffffff' |
| | | }, |
| | | // 整个divider的高度单位rpx |
| | | height: { |
| | | type: [Number, String], |
| | | default: 'auto' |
| | | }, |
| | | // 上边距 |
| | | marginTop: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 下边距 |
| | | marginBottom: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 是否使用slot传入内容,如果不用slot传入内容,先的中间就不会有空隙 |
| | | useSlot: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | computed: { |
| | | lineStyle() { |
| | | let style = {}; |
| | | if(String(this.halfWidth).indexOf('%') != -1) style.width = this.halfWidth; |
| | | else style.width = this.halfWidth + 'rpx'; |
| | | // borderColor优先级高于type值 |
| | | if(this.borderColor) style.borderColor = this.borderColor; |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | click() { |
| | | this.$emit('click'); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | .u-divider { |
| | | width: 100%; |
| | | position: relative; |
| | | text-align: center; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | overflow: hidden; |
| | | flex-direction: row; |
| | | } |
| | | |
| | | .u-divider-line { |
| | | border-bottom: 1px solid $u-border-color; |
| | | transform: scale(1, 0.5); |
| | | transform-origin: center; |
| | | |
| | | &--bordercolor--primary { |
| | | border-color: $u-type-primary; |
| | | } |
| | | |
| | | &--bordercolor--success { |
| | | border-color: $u-type-success; |
| | | } |
| | | |
| | | &--bordercolor--error { |
| | | border-color: $u-type-primary; |
| | | } |
| | | |
| | | &--bordercolor--info { |
| | | border-color: $u-type-info; |
| | | } |
| | | |
| | | &--bordercolor--warning { |
| | | border-color: $u-type-warning; |
| | | } |
| | | } |
| | | |
| | | .u-divider-text { |
| | | white-space: nowrap; |
| | | padding: 0 16rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-dropdown-item" v-if="active" @touchmove.stop.prevent="() => {}" @tap.stop.prevent="() => {}"> |
| | | <block v-if="!$slots.default && !$slots.$default"> |
| | | <scroll-view scroll-y="true" :style="{ |
| | | height: $u.addUnit(height) |
| | | }"> |
| | | <view class="u-dropdown-item__options"> |
| | | <u-cell-group> |
| | | <u-cell-item @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in options" |
| | | :key="index" :title-style="{ |
| | | color: value == item.value ? activeColor : inactiveColor |
| | | }"> |
| | | <u-icon v-if="value == item.value" name="checkbox-mark" :color="activeColor" size="32"></u-icon> |
| | | </u-cell-item> |
| | | </u-cell-group> |
| | | </view> |
| | | </scroll-view> |
| | | </block> |
| | | <slot v-else /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * dropdown-item 下拉菜单 |
| | | * @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景 |
| | | * @tutorial http://uviewui.com/components/dropdown.html |
| | | * @property {String | Number} v-model 双向绑定选项卡选择值 |
| | | * @property {String} title 菜单项标题 |
| | | * @property {Array[Object]} options 选项数据,如果传入了默认slot,此参数无效 |
| | | * @property {Boolean} disabled 是否禁用此选项卡(默认false) |
| | | * @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300) |
| | | * @property {String | Number} height 弹窗下拉内容的高度(内容超出将会滚动)(默认auto) |
| | | * @example <u-dropdown-item title="标题"></u-dropdown-item> |
| | | */ |
| | | export default { |
| | | name: 'u-dropdown-item', |
| | | props: { |
| | | // 当前选中项的value值 |
| | | value: { |
| | | type: [Number, String, Array], |
| | | default: '' |
| | | }, |
| | | // 菜单项标题 |
| | | title: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 选项数据,如果传入了默认slot,此参数无效 |
| | | options: { |
| | | type: Array, |
| | | default () { |
| | | return [] |
| | | } |
| | | }, |
| | | // 是否禁用此菜单项 |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 下拉弹窗的高度 |
| | | height: { |
| | | type: [Number, String], |
| | | default: 'auto' |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | active: false, // 当前项是否处于展开状态 |
| | | activeColor: '#2979ff', // 激活时左边文字和右边对勾图标的颜色 |
| | | inactiveColor: '#606266', // 未激活时左边文字和右边对勾图标的颜色 |
| | | } |
| | | }, |
| | | computed: { |
| | | // 监听props是否发生了变化,有些值需要传递给父组件u-dropdown,无法双向绑定 |
| | | propsChange() { |
| | | return `${this.title}-${this.disabled}`; |
| | | } |
| | | }, |
| | | watch: { |
| | | propsChange(n) { |
| | | // 当值变化时,通知父组件重新初始化,让父组件执行每个子组件的init()方法 |
| | | // 将所有子组件数据重新整理一遍 |
| | | if (this.parent) this.parent.init(); |
| | | } |
| | | }, |
| | | created() { |
| | | // 父组件的实例 |
| | | this.parent = false; |
| | | }, |
| | | methods: { |
| | | init() { |
| | | // 获取父组件u-dropdown |
| | | let parent = this.$u.$parent.call(this, 'u-dropdown'); |
| | | if (parent) { |
| | | this.parent = parent; |
| | | // 将子组件的激活颜色配置为父组件设置的激活和未激活时的颜色 |
| | | this.activeColor = parent.activeColor; |
| | | this.inactiveColor = parent.inactiveColor; |
| | | // 将本组件的this,放入到父组件的children数组中,让父组件可以操作本(子)组件的方法和属性 |
| | | // push进去前,显判断是否已经存在了本实例,因为在子组件内部数据变化时,会通过父组件重新初始化子组件 |
| | | let exist = parent.children.find(val => { |
| | | return this === val; |
| | | }) |
| | | if (!exist) parent.children.push(this); |
| | | if (parent.children.length == 1) this.active = true; |
| | | // 父组件无法监听children的变化,故将子组件的title,传入父组件的menuList数组中 |
| | | parent.menuList.push({ |
| | | title: this.title, |
| | | disabled: this.disabled |
| | | }); |
| | | } |
| | | }, |
| | | // cell被点击 |
| | | cellClick(value) { |
| | | // 修改通过v-model绑定的值 |
| | | this.$emit('input', value); |
| | | // 通知父组件(u-dropdown)收起菜单 |
| | | this.parent.close(); |
| | | // 发出事件,抛出当前勾选项的value |
| | | this.$emit('change', value); |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.init(); |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-dropdown"> |
| | | <view class="u-dropdown__menu" :style="{ |
| | | height: $u.addUnit(height) |
| | | }" :class="{ |
| | | 'u-border-bottom': borderBottom |
| | | }"> |
| | | <view class="u-dropdown__menu__item" v-for="(item, index) in menuList" :key="index" @tap.stop="menuClick(index)"> |
| | | <view class="u-flex"> |
| | | <text class="u-dropdown__menu__item__text" :style="{ |
| | | color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor, |
| | | fontSize: $u.addUnit(titleSize) |
| | | }">{{item.title}}</text> |
| | | <view class="u-dropdown__menu__item__arrow" :class="{ |
| | | 'u-dropdown__menu__item__arrow--rotate': index === current |
| | | }"> |
| | | <u-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="$u.addUnit(menuIconSize)" :color="index === current || highlightIndex == index ? activeColor : '#c0c4cc'"></u-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="u-dropdown__content" :style="[contentStyle, { |
| | | transition: `opacity ${duration / 1000}s linear`, |
| | | top: $u.addUnit(height), |
| | | height: contentHeight + 'px' |
| | | }]" |
| | | @tap="maskClick" @touchmove.stop.prevent> |
| | | <view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]"> |
| | | <slot></slot> |
| | | </view> |
| | | <view class="u-dropdown__content__mask"></view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * dropdown 下拉菜单 |
| | | * @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景 |
| | | * @tutorial http://uviewui.com/components/dropdown.html |
| | | * @property {String} active-color 标题和选项卡选中的颜色(默认#2979ff) |
| | | * @property {String} inactive-color 标题和选项卡未选中的颜色(默认#606266) |
| | | * @property {Boolean} close-on-click-mask 点击遮罩是否关闭菜单(默认true) |
| | | * @property {Boolean} close-on-click-self 点击当前激活项标题是否关闭菜单(默认true) |
| | | * @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300) |
| | | * @property {String | Number} height 标题菜单的高度,单位任意(默认80) |
| | | * @property {String | Number} border-radius 菜单展开内容下方的圆角值,单位任意(默认0) |
| | | * @property {Boolean} border-bottom 标题菜单是否显示下边框(默认false) |
| | | * @property {String | Number} title-size 标题的字体大小,单位任意,数值默认为rpx单位(默认28) |
| | | * @event {Function} open 下拉菜单被打开时触发 |
| | | * @event {Function} close 下拉菜单被关闭时触发 |
| | | * @example <u-dropdown></u-dropdown> |
| | | */ |
| | | export default { |
| | | name: 'u-dropdown', |
| | | props: { |
| | | // 菜单标题和选项的激活态颜色 |
| | | activeColor: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | }, |
| | | // 菜单标题和选项的未激活态颜色 |
| | | inactiveColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // 点击遮罩是否关闭菜单 |
| | | closeOnClickMask: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 点击当前激活项标题是否关闭菜单 |
| | | closeOnClickSelf: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 过渡时间 |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 300 |
| | | }, |
| | | // 标题菜单的高度,单位任意,数值默认为rpx单位 |
| | | height: { |
| | | type: [Number, String], |
| | | default: 80 |
| | | }, |
| | | // 是否显示下边框 |
| | | borderBottom: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 标题的字体大小 |
| | | titleSize: { |
| | | type: [Number, String], |
| | | default: 28 |
| | | }, |
| | | // 下拉出来的内容部分的圆角值 |
| | | borderRadius: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 菜单右侧的icon图标 |
| | | menuIcon: { |
| | | type: String, |
| | | default: 'arrow-down' |
| | | }, |
| | | // 菜单右侧图标的大小 |
| | | menuIconSize: { |
| | | type: [Number, String], |
| | | default: 26 |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | showDropdown: true, // 是否打开下来菜单, |
| | | menuList: [], // 显示的菜单 |
| | | active: false, // 下拉菜单的状态 |
| | | // 当前是第几个菜单处于激活状态,小程序中此处不能写成false或者"",否则后续将current赋值为0, |
| | | // 无能的TX没有使用===而是使用==判断,导致程序认为前后二者没有变化,从而不会触发视图更新 |
| | | current: 99999, |
| | | // 外层内容的样式,初始时处于底层,且透明 |
| | | contentStyle: { |
| | | zIndex: -1, |
| | | opacity: 0 |
| | | }, |
| | | // 让某个菜单保持高亮的状态 |
| | | highlightIndex: 99999, |
| | | contentHeight: 0 |
| | | } |
| | | }, |
| | | computed: { |
| | | // 下拉出来部分的样式 |
| | | popupStyle() { |
| | | let style = {}; |
| | | // 进行Y轴位移,展开状态时,恢复原位。收齐状态时,往上位移100%,进行隐藏 |
| | | style.transform = `translateY(${this.active ? 0 : '-100%'})` |
| | | style['transition-duration'] = this.duration / 1000 + 's'; |
| | | style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`; |
| | | return style; |
| | | } |
| | | }, |
| | | created() { |
| | | // 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错 |
| | | this.children = []; |
| | | }, |
| | | mounted() { |
| | | this.getContentHeight(); |
| | | }, |
| | | methods: { |
| | | init() { |
| | | // 当某个子组件内容变化时,触发父组件的init,父组件再让每一个子组件重新初始化一遍 |
| | | // 以保证数据的正确性 |
| | | this.menuList = []; |
| | | this.children.map(child => { |
| | | child.init(); |
| | | }) |
| | | }, |
| | | // 点击菜单 |
| | | menuClick(index) { |
| | | // 判断是否被禁用 |
| | | if (this.menuList[index].disabled) return; |
| | | // 如果点击时的索引和当前激活项索引相同,意味着点击了激活项,需要收起下拉菜单 |
| | | if (index === this.current && this.closeOnClickSelf) { |
| | | this.close(); |
| | | // 等动画结束后,再移除下拉菜单中的内容,否则直接移除,也就没有下拉菜单收起的效果了 |
| | | setTimeout(() => { |
| | | this.children[index].active = false; |
| | | }, this.duration) |
| | | return; |
| | | } |
| | | this.open(index); |
| | | }, |
| | | // 打开下拉菜单 |
| | | open(index) { |
| | | // 重置高亮索引,否则会造成多个菜单同时高亮 |
| | | // this.highlightIndex = 9999; |
| | | // 展开时,设置下拉内容的样式 |
| | | this.contentStyle = { |
| | | zIndex: 11, |
| | | } |
| | | // 标记展开状态以及当前展开项的索引 |
| | | this.active = true; |
| | | this.current = index; |
| | | // 历遍所有的子元素,将索引匹配的项标记为激活状态,因为子元素是通过v-if控制切换的 |
| | | // 之所以不是因display: none,是因为nvue没有display这个属性 |
| | | this.children.map((val, idx) => { |
| | | val.active = index == idx ? true : false; |
| | | }) |
| | | this.$emit('open', this.current); |
| | | }, |
| | | // 设置下拉菜单处于收起状态 |
| | | close() { |
| | | this.$emit('close', this.current); |
| | | // 设置为收起状态,同时current归位,设置为空字符串 |
| | | this.active = false; |
| | | this.current = 99999; |
| | | // 下拉内容的样式进行调整,不透明度设置为0 |
| | | this.contentStyle = { |
| | | zIndex: -1, |
| | | opacity: 0 |
| | | } |
| | | }, |
| | | // 点击遮罩 |
| | | maskClick() { |
| | | // 如果不允许点击遮罩,直接返回 |
| | | if (!this.closeOnClickMask) return; |
| | | this.close(); |
| | | }, |
| | | // 外部手动设置某个菜单高亮 |
| | | highlight(index = undefined) { |
| | | this.highlightIndex = index !== undefined ? index : 99999; |
| | | }, |
| | | // 获取下拉菜单内容的高度 |
| | | getContentHeight() { |
| | | // 这里的原理为,因为dropdown组件是相对定位的,它的下拉出来的内容,必须给定一个高度 |
| | | // 才能让遮罩占满菜单一下,直到屏幕底部的高度 |
| | | // this.$u.sys()为uView封装的获取设备信息的方法 |
| | | let windowHeight = this.$u.sys().windowHeight; |
| | | this.$uGetRect('.u-dropdown__menu').then(res => { |
| | | // 这里获取的是dropdown的尺寸,在H5上,uniapp获取尺寸是有bug的(以前提出修复过,后来又出现了此bug,目前hx2.8.11版本) |
| | | // H5端bug表现为元素尺寸的top值为导航栏底部到到元素的上边沿的距离,但是元素的bottom值确是导航栏顶部到元素底部的距离 |
| | | // 二者是互相矛盾的,本质原因是H5端导航栏非原生,uni的开发者大意造成 |
| | | // 这里取菜单栏的botton值合理的,不能用res.top,否则页面会造成滚动 |
| | | this.contentHeight = windowHeight - res.bottom; |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-dropdown { |
| | | flex: 1; |
| | | width: 100%; |
| | | position: relative; |
| | | |
| | | &__menu { |
| | | @include vue-flex; |
| | | position: relative; |
| | | z-index: 11; |
| | | height: 80rpx; |
| | | |
| | | &__item { |
| | | flex: 1; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | |
| | | &__text { |
| | | font-size: 28rpx; |
| | | color: $u-content-color; |
| | | } |
| | | |
| | | &__arrow { |
| | | margin-left: 6rpx; |
| | | transition: transform .3s; |
| | | align-items: center; |
| | | @include vue-flex; |
| | | |
| | | &--rotate { |
| | | transform: rotate(180deg); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | &__content { |
| | | position: absolute; |
| | | z-index: 8; |
| | | width: 100%; |
| | | left: 0px; |
| | | bottom: 0; |
| | | overflow: hidden; |
| | | |
| | | |
| | | &__mask { |
| | | position: absolute; |
| | | z-index: 9; |
| | | background: rgba(0, 0, 0, .3); |
| | | width: 100%; |
| | | left: 0; |
| | | top: 0; |
| | | bottom: 0; |
| | | } |
| | | |
| | | &__popup { |
| | | position: relative; |
| | | z-index: 10; |
| | | transition: all 0.3s; |
| | | transform: translate3D(0, -100%, 0); |
| | | overflow: hidden; |
| | | } |
| | | } |
| | | |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-empty" v-if="show" :style="{ |
| | | marginTop: marginTop + 'rpx' |
| | | }"> |
| | | <u-icon |
| | | :name="src ? src : 'empty-' + mode" |
| | | :custom-style="iconStyle" |
| | | :label="text ? text : icons[mode]" |
| | | label-pos="bottom" |
| | | :label-color="color" |
| | | :label-size="fontSize" |
| | | :size="iconSize" |
| | | :color="iconColor" |
| | | margin-top="14" |
| | | ></u-icon> |
| | | <view class="u-slot-wrap"> |
| | | <slot name="bottom"></slot> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * empty 内容为空 |
| | | * @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。 |
| | | * @tutorial https://www.uviewui.com/components/empty.html |
| | | * @property {String} color 文字颜色(默认#c0c4cc) |
| | | * @property {String} text 文字提示(默认“无内容”) |
| | | * @property {String} src 自定义图标路径,如定义,mode参数会失效 |
| | | * @property {String Number} font-size 提示文字的大小,单位rpx(默认28) |
| | | * @property {String} mode 内置的图标,见官网说明(默认data) |
| | | * @property {String Number} img-width 图标的宽度,单位rpx(默认240) |
| | | * @property {String} img-height 图标的高度,单位rpx(默认auto) |
| | | * @property {String Number} margin-top 组件距离上一个元素之间的距离(默认0) |
| | | * @property {Boolean} show 是否显示组件(默认true) |
| | | * @event {Function} click 点击组件时触发 |
| | | * @event {Function} close 点击关闭按钮时触发 |
| | | * @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty> |
| | | */ |
| | | export default { |
| | | name: "u-empty", |
| | | props: { |
| | | // 图标路径 |
| | | src: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 提示文字 |
| | | text: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 文字颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#c0c4cc' |
| | | }, |
| | | // 图标的颜色 |
| | | iconColor: { |
| | | type: String, |
| | | default: '#c0c4cc' |
| | | }, |
| | | // 图标的大小 |
| | | iconSize: { |
| | | type: [String, Number], |
| | | default: 120 |
| | | }, |
| | | // 文字大小,单位rpx |
| | | fontSize: { |
| | | type: [String, Number], |
| | | default: 26 |
| | | }, |
| | | // 选择预置的图标类型 |
| | | mode: { |
| | | type: String, |
| | | default: 'data' |
| | | }, |
| | | // 图标宽度,单位rpx |
| | | imgWidth: { |
| | | type: [String, Number], |
| | | default: 120 |
| | | }, |
| | | // 图标高度,单位rpx |
| | | imgHeight: { |
| | | type: [String, Number], |
| | | default: 'auto' |
| | | }, |
| | | // 是否显示组件 |
| | | show: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 组件距离上一个元素之间的距离 |
| | | marginTop: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | iconStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | icons: { |
| | | car: '购物车为空', |
| | | page: '页面不存在', |
| | | search: '没有搜索结果', |
| | | address: '没有收货地址', |
| | | wifi: '没有WiFi', |
| | | order: '订单为空', |
| | | coupon: '没有优惠券', |
| | | favor: '暂无收藏', |
| | | permission: '无权限', |
| | | history: '无历史记录', |
| | | news: '无新闻列表', |
| | | message: '消息列表为空', |
| | | list: '列表为空', |
| | | data: '数据为空' |
| | | }, |
| | | // icons: [{ |
| | | // icon: 'car', |
| | | // text: '购物车为空' |
| | | // },{ |
| | | // icon: 'page', |
| | | // text: '页面不存在' |
| | | // },{ |
| | | // icon: 'search', |
| | | // text: '没有搜索结果' |
| | | // },{ |
| | | // icon: 'address', |
| | | // text: '没有收货地址' |
| | | // },{ |
| | | // icon: 'wifi', |
| | | // text: '没有WiFi' |
| | | // },{ |
| | | // icon: 'order', |
| | | // text: '订单为空' |
| | | // },{ |
| | | // icon: 'coupon', |
| | | // text: '没有优惠券' |
| | | // },{ |
| | | // icon: 'favor', |
| | | // text: '暂无收藏' |
| | | // },{ |
| | | // icon: 'permission', |
| | | // text: '无权限' |
| | | // },{ |
| | | // icon: 'history', |
| | | // text: '无历史记录' |
| | | // },{ |
| | | // icon: 'news', |
| | | // text: '无新闻列表' |
| | | // },{ |
| | | // icon: 'message', |
| | | // text: '消息列表为空' |
| | | // },{ |
| | | // icon: 'list', |
| | | // text: '列表为空' |
| | | // },{ |
| | | // icon: 'data', |
| | | // text: '数据为空' |
| | | // }], |
| | | |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-empty { |
| | | @include vue-flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .u-image { |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .u-slot-wrap { |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | margin-top: 20rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-field" :class="{'u-border-top': borderTop, 'u-border-bottom': borderBottom }"> |
| | | <view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPosition]"> |
| | | <view class="u-label" :class="[required ? 'u-required' : '']" :style="{ |
| | | justifyContent: justifyContent, |
| | | flex: labelPosition == 'left' ? `0 0 ${labelWidth}rpx` : '1' |
| | | }"> |
| | | <view class="u-icon-wrap" v-if="icon"> |
| | | <u-icon size="32" :custom-style="iconStyle" :name="icon" :color="iconColor" class="u-icon"></u-icon> |
| | | </view> |
| | | <slot name="icon"></slot> |
| | | <text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text> |
| | | </view> |
| | | <view class="fild-body"> |
| | | <view class="u-flex-1 u-flex" :style="[inputWrapStyle]"> |
| | | <textarea v-if="type == 'textarea'" class="u-flex-1 u-textarea-class" :style="[fieldStyle]" :value="value" |
| | | :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" |
| | | :focus="focus" :autoHeight="autoHeight" :fixed="fixed" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm" |
| | | @tap="fieldClick" /> |
| | | <input |
| | | v-else |
| | | :style="[fieldStyle]" |
| | | :type="type" |
| | | class="u-flex-1 u-field__input-wrap" |
| | | :value="value" |
| | | :password="password || this.type === 'password'" |
| | | :placeholder="placeholder" |
| | | :placeholderStyle="placeholderStyle" |
| | | :disabled="disabled" |
| | | :maxlength="inputMaxlength" |
| | | :focus="focus" |
| | | :confirmType="confirmType" |
| | | @focus="onFocus" |
| | | @blur="onBlur" |
| | | @input="onInput" |
| | | @confirm="onConfirm" |
| | | @tap="fieldClick" |
| | | /> |
| | | </view> |
| | | <u-icon :size="clearSize" v-if="clearable && value != '' && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @click="onClear"/> |
| | | <view class="u-button-wrap"><slot name="right" /></view> |
| | | <u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" /> |
| | | </view> |
| | | </view> |
| | | <view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{ |
| | | paddingLeft: labelWidth + 'rpx' |
| | | }">{{ errorMessage }}</view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * field 输入框 |
| | | * @description 借助此组件,可以实现表单的输入, 有"text"和"textarea"类型的,此外,借助uView的picker和actionSheet组件可以快速实现上拉菜单,时间,地区选择等, 为表单解决方案的利器。 |
| | | * @tutorial https://www.uviewui.com/components/field.html |
| | | * @property {String} type 输入框的类型(默认text) |
| | | * @property {String} icon label左边的图标,限uView的图标名称 |
| | | * @property {Object} icon-style 左边图标的样式,对象形式 |
| | | * @property {Boolean} right-icon 输入框右边的图标名称,限uView的图标名称(默认false) |
| | | * @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false) |
| | | * @property {String} label 输入框左边的文字提示 |
| | | * @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false) |
| | | * @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true) |
| | | * @property {Number String} label-width label的宽度,单位rpx(默认130) |
| | | * @property {String} label-align label的文字对齐方式(默认left) |
| | | * @property {Object} field-style 自定义输入框的样式,对象形式 |
| | | * @property {Number | String} clear-size 清除图标的大小,单位rpx(默认30) |
| | | * @property {String} input-align 输入框内容对齐方式(默认left) |
| | | * @property {Boolean} border-bottom 是否显示field的下边框(默认true) |
| | | * @property {Boolean} border-top 是否显示field的上边框(默认false) |
| | | * @property {String} icon-color 左边通过icon配置的图标的颜色(默认#606266) |
| | | * @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true) |
| | | * @property {String Boolean} error-message 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息 |
| | | * @property {String} placeholder 输入框的提示文字 |
| | | * @property {String} placeholder-style placeholder的样式(内联样式,字符串),如"color: #ddd" |
| | | * @property {Boolean} focus 是否自动获得焦点(默认false) |
| | | * @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false) |
| | | * @property {Boolean} disabled 是否不可输入(默认false) |
| | | * @property {Number String} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140) |
| | | * @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done) |
| | | * @event {Function} input 输入框内容发生变化时触发 |
| | | * @event {Function} focus 输入框获得焦点时触发 |
| | | * @event {Function} blur 输入框失去焦点时触发 |
| | | * @event {Function} confirm 点击完成按钮时触发 |
| | | * @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发 |
| | | * @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发,这样设计是考虑到传递右边的图标,一般都为需要弹出"picker"等操作时的场景,点击倒三角图标,理应发出此事件,见上方说明 |
| | | * @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field> |
| | | */ |
| | | export default { |
| | | name:"u-field", |
| | | props: { |
| | | icon: String, |
| | | rightIcon: String, |
| | | // arrowDirection: { |
| | | // type: String, |
| | | // default: 'right' |
| | | // }, |
| | | required: Boolean, |
| | | label: String, |
| | | password: Boolean, |
| | | clearable: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 左边标题的宽度单位rpx |
| | | labelWidth: { |
| | | type: [Number, String], |
| | | default: 130 |
| | | }, |
| | | // 对齐方式,left|center|right |
| | | labelAlign: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | inputAlign: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | iconColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | autoHeight: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | errorMessage: { |
| | | type: [String, Boolean], |
| | | default: '' |
| | | }, |
| | | placeholder: String, |
| | | placeholderStyle: String, |
| | | focus: Boolean, |
| | | fixed: Boolean, |
| | | value: [Number, String], |
| | | type: { |
| | | type: String, |
| | | default: 'text' |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | maxlength: { |
| | | type: [Number, String], |
| | | default: 140 |
| | | }, |
| | | confirmType: { |
| | | type: String, |
| | | default: 'done' |
| | | }, |
| | | // lable的位置,可选为 left-左边,top-上边 |
| | | labelPosition: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | // 输入框的自定义样式 |
| | | fieldStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | // 清除按钮的大小 |
| | | clearSize: { |
| | | type: [Number, String], |
| | | default: 30 |
| | | }, |
| | | // lable左边的图标样式,对象形式 |
| | | iconStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | // 是否显示上边框 |
| | | borderTop: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示下边框 |
| | | borderBottom: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否自动去除两端的空格 |
| | | trim: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | focused: false, |
| | | itemIndex: 0, |
| | | }; |
| | | }, |
| | | computed: { |
| | | inputWrapStyle() { |
| | | let style = {}; |
| | | style.textAlign = this.inputAlign; |
| | | // 判断lable的位置,如果是left的话,让input左边两边有间隙 |
| | | if(this.labelPosition == 'left') { |
| | | style.margin = `0 8rpx`; |
| | | } else { |
| | | // 如果lable是top的,input的左边就没必要有间隙了 |
| | | style.marginRight = `8rpx`; |
| | | } |
| | | return style; |
| | | }, |
| | | rightIconStyle() { |
| | | let style = {}; |
| | | if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)'; |
| | | if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)'; |
| | | else style.transform = 'roate(0deg)'; |
| | | return style; |
| | | }, |
| | | labelStyle() { |
| | | let style = {}; |
| | | if(this.labelAlign == 'left') style.justifyContent = 'flext-start'; |
| | | if(this.labelAlign == 'center') style.justifyContent = 'center'; |
| | | if(this.labelAlign == 'right') style.justifyContent = 'flext-end'; |
| | | return style; |
| | | }, |
| | | // uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法 |
| | | justifyContent() { |
| | | if(this.labelAlign == 'left') return 'flex-start'; |
| | | if(this.labelAlign == 'center') return 'center'; |
| | | if(this.labelAlign == 'right') return 'flex-end'; |
| | | }, |
| | | // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值 |
| | | inputMaxlength() { |
| | | return Number(this.maxlength) |
| | | }, |
| | | // label的位置 |
| | | fieldInnerStyle() { |
| | | let style = {}; |
| | | if(this.labelPosition == 'left') { |
| | | style.flexDirection = 'row'; |
| | | } else { |
| | | style.flexDirection = 'column'; |
| | | } |
| | | |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | onInput(event) { |
| | | let value = event.detail.value; |
| | | // 判断是否去除空格 |
| | | if(this.trim) value = this.$u.trim(value); |
| | | this.$emit('input', value); |
| | | }, |
| | | onFocus(event) { |
| | | this.focused = true; |
| | | this.$emit('focus', event); |
| | | }, |
| | | onBlur(event) { |
| | | // 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错 |
| | | // 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时 |
| | | setTimeout(() => { |
| | | this.focused = false; |
| | | }, 100) |
| | | this.$emit('blur', event); |
| | | }, |
| | | onConfirm(e) { |
| | | this.$emit('confirm', e.detail.value); |
| | | }, |
| | | onClear(event) { |
| | | this.$emit('input', ''); |
| | | }, |
| | | rightIconClick() { |
| | | this.$emit('right-icon-click'); |
| | | this.$emit('click'); |
| | | }, |
| | | fieldClick() { |
| | | this.$emit('click'); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-field { |
| | | font-size: 28rpx; |
| | | padding: 20rpx 28rpx; |
| | | text-align: left; |
| | | position: relative; |
| | | color: $u-main-color; |
| | | } |
| | | |
| | | .u-field-inner { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-textarea-inner { |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | .u-textarea-class { |
| | | min-height: 96rpx; |
| | | width: auto; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .fild-body { |
| | | @include vue-flex; |
| | | flex: 1; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-arror-right { |
| | | margin-left: 8rpx; |
| | | } |
| | | |
| | | .u-label-text { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .u-label-left-gap { |
| | | margin-left: 6rpx; |
| | | } |
| | | |
| | | .u-label-postion-top { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | .u-label { |
| | | width: 130rpx; |
| | | flex: 1 1 130rpx; |
| | | text-align: left; |
| | | position: relative; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-required::before { |
| | | content: '*'; |
| | | position: absolute; |
| | | left: -16rpx; |
| | | font-size: 14px; |
| | | color: $u-type-error; |
| | | height: 9px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .u-field__input-wrap { |
| | | position: relative; |
| | | overflow: hidden; |
| | | font-size: 28rpx; |
| | | height: 48rpx; |
| | | flex: 1; |
| | | width: auto; |
| | | } |
| | | |
| | | .u-clear-icon { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-error-message { |
| | | color: $u-type-error; |
| | | font-size: 26rpx; |
| | | text-align: left; |
| | | } |
| | | |
| | | .placeholder-style { |
| | | color: rgb(150, 151, 153); |
| | | } |
| | | |
| | | .u-input-class { |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .u-button-wrap { |
| | | margin-left: 8rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-form-item" :class="{'u-border-bottom': elBorderBottom, 'u-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')}"> |
| | | <view class="u-form-item__body" :style="{ |
| | | flexDirection: elLabelPosition == 'left' ? 'row' : 'column' |
| | | }"> |
| | | <!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" --> |
| | | <view class="u-form-item--left" :style="{ |
| | | width: uLabelWidth, |
| | | flex: `0 0 ${uLabelWidth}`, |
| | | marginBottom: elLabelPosition == 'left' ? 0 : '10rpx', |
| | | }"> |
| | | <!-- 为了块对齐 --> |
| | | <view class="u-form-item--left__content" v-if="required || leftIcon || label"> |
| | | <!-- nvue不支持伪元素before --> |
| | | <text v-if="required" class="u-form-item--left__content--required">*</text> |
| | | <view class="u-form-item--left__content__icon" v-if="leftIcon"> |
| | | <u-icon :name="leftIcon" :custom-style="leftIconStyle"></u-icon> |
| | | </view> |
| | | <view class="u-form-item--left__content__label" :style="[elLabelStyle, { |
| | | 'justify-content': elLabelAlign == 'left' ? 'flex-start' : elLabelAlign == 'center' ? 'center' : 'flex-end' |
| | | }]"> |
| | | {{label}} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="u-form-item--right u-flex"> |
| | | <view class="u-form-item--right__content"> |
| | | <view class="u-form-item--right__content__slot "> |
| | | <slot /> |
| | | </view> |
| | | <view class="u-form-item--right__content__icon u-flex" v-if="$slots.right || rightIcon"> |
| | | <u-icon :custom-style="rightIconStyle" v-if="rightIcon" :name="rightIcon"></u-icon> |
| | | <slot name="right" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="u-form-item__message" v-if="validateState === 'error' && showError('message')" :style="{ |
| | | paddingLeft: elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '0', |
| | | }">{{validateMessage}}</view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import Emitter from '../../libs/util/emitter.js'; |
| | | import schema from '../../libs/util/async-validator'; |
| | | // 去除警告信息 |
| | | schema.warning = function() {}; |
| | | |
| | | /** |
| | | * form-item 表单item |
| | | * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。 |
| | | * @tutorial http://uviewui.com/components/form.html |
| | | * @property {String} label 左侧提示文字 |
| | | * @property {Object} prop 表单域model对象的属性名,在使用 validate、resetFields 方法的情况下,该属性是必填的 |
| | | * @property {Boolean} border-bottom 是否显示表单域的下划线边框 |
| | | * @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方 |
| | | * @property {String Number} label-width 提示文字的宽度,单位rpx(默认90) |
| | | * @property {Object} label-style lable的样式,对象形式 |
| | | * @property {String} label-align lable的对齐方式 |
| | | * @property {String} right-icon 右侧自定义字体图标(限uView内置图标)或图片地址 |
| | | * @property {String} left-icon 左侧自定义字体图标(限uView内置图标)或图片地址 |
| | | * @property {Object} left-icon-style 左侧图标的样式,对象形式 |
| | | * @property {Object} right-icon-style 右侧图标的样式,对象形式 |
| | | * @property {Boolean} required 是否显示左边的"*"号,这里仅起展示作用,如需校验必填,请通过rules配置必填规则(默认false) |
| | | * @example <u-form-item label="姓名"><u-input v-model="form.name" /></u-form-item> |
| | | */ |
| | | |
| | | export default { |
| | | name: 'u-form-item', |
| | | mixins: [Emitter], |
| | | inject: { |
| | | uForm: { |
| | | default () { |
| | | return null |
| | | } |
| | | } |
| | | }, |
| | | props: { |
| | | // input的label提示语 |
| | | label: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 绑定的值 |
| | | prop: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否显示表单域的下划线边框 |
| | | borderBottom: { |
| | | type: [String, Boolean], |
| | | default: '' |
| | | }, |
| | | // label的位置,left-左边,top-上边 |
| | | labelPosition: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // label的宽度,单位rpx |
| | | labelWidth: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // lable的样式,对象形式 |
| | | labelStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // lable字体的对齐方式 |
| | | labelAlign: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 右侧图标 |
| | | rightIcon: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 左侧图标 |
| | | leftIcon: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 左侧图标的样式 |
| | | leftIconStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 左侧图标的样式 |
| | | rightIconStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 |
| | | required: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | initialValue: '', // 存储的默认值 |
| | | // isRequired: false, // 是否必填,由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成 |
| | | validateState: '', // 是否校验成功 |
| | | validateMessage: '', // 校验失败的提示语 |
| | | // 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色, |
| | | errorType: ['message'], |
| | | fieldValue: '', // 获取当前子组件input的输入的值 |
| | | // 父组件的参数,在computed计算中,无法得知this.parent发生变化,故将父组件的参数值,放到data中 |
| | | parentData: { |
| | | borderBottom: true, |
| | | labelWidth: 90, |
| | | labelPosition: 'left', |
| | | labelStyle: {}, |
| | | labelAlign: 'left', |
| | | } |
| | | }; |
| | | }, |
| | | watch: { |
| | | validateState(val) { |
| | | this.broadcastInputError(); |
| | | }, |
| | | // 监听u-form组件的errorType的变化 |
| | | "uForm.errorType"(val) { |
| | | this.errorType = val; |
| | | this.broadcastInputError(); |
| | | }, |
| | | }, |
| | | computed: { |
| | | // 计算后的label宽度,由于需要多个判断,故放到computed中 |
| | | uLabelWidth() { |
| | | // 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto |
| | | return this.elLabelPosition == 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.$u.addUnit(this |
| | | .elLabelWidth)) : '100%'; |
| | | }, |
| | | showError() { |
| | | return type => { |
| | | // 如果errorType数组中含有none,或者toast提示类型 |
| | | if (this.errorType.indexOf('none') >= 0) return false; |
| | | else if (this.errorType.indexOf(type) >= 0) return true; |
| | | else return false; |
| | | } |
| | | }, |
| | | // label的宽度 |
| | | elLabelWidth() { |
| | | // label默认宽度为90,优先使用本组件的值,如果没有(如果设置为0,也算是配置了值,依然起效),则用u-form的值 |
| | | return (this.labelWidth != 0 || this.labelWidth != '') ? this.labelWidth : (this.parentData.labelWidth ? this.parentData |
| | | .labelWidth : |
| | | 90); |
| | | }, |
| | | // label的样式 |
| | | elLabelStyle() { |
| | | return Object.keys(this.labelStyle).length ? this.labelStyle : (this.parentData.labelStyle ? this.parentData.labelStyle : |
| | | {}); |
| | | }, |
| | | // label的位置,左侧或者上方 |
| | | elLabelPosition() { |
| | | return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition : |
| | | 'left'); |
| | | }, |
| | | // label的对齐方式 |
| | | elLabelAlign() { |
| | | return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left'); |
| | | }, |
| | | // label的下划线 |
| | | elBorderBottom() { |
| | | // 子组件的borderBottom默认为空字符串,如果不等于空字符串,意味着子组件设置了值,优先使用子组件的值 |
| | | return this.borderBottom !== '' ? this.borderBottom : this.parentData.borderBottom ? this.parentData.borderBottom : |
| | | true; |
| | | } |
| | | }, |
| | | methods: { |
| | | broadcastInputError() { |
| | | // 子组件发出事件,第三个参数为true或者false,true代表有错误 |
| | | this.broadcast('u-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border')); |
| | | }, |
| | | // 判断是否需要required校验 |
| | | setRules() { |
| | | let that = this; |
| | | // 由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成 |
| | | // 从父组件u-form拿到当前u-form-item需要验证 的规则 |
| | | // let rules = this.getRules(); |
| | | // if (rules.length) { |
| | | // this.isRequired = rules.some(rule => { |
| | | // // 如果有必填项,就返回,没有的话,就是undefined |
| | | // return rule.required; |
| | | // }); |
| | | // } |
| | | |
| | | // blur事件 |
| | | this.$on('on-form-blur', that.onFieldBlur); |
| | | // change事件 |
| | | this.$on('on-form-change', that.onFieldChange); |
| | | }, |
| | | |
| | | // 从u-form的rules属性中,取出当前u-form-item的校验规则 |
| | | getRules() { |
| | | // 父组件的所有规则 |
| | | let rules = this.parent.rules; |
| | | rules = rules ? rules[this.prop] : []; |
| | | // 保证返回的是一个数组形式 |
| | | return [].concat(rules || []); |
| | | }, |
| | | |
| | | // blur事件时进行表单校验 |
| | | onFieldBlur() { |
| | | this.validation('blur'); |
| | | }, |
| | | |
| | | // change事件进行表单校验 |
| | | onFieldChange() { |
| | | this.validation('change'); |
| | | }, |
| | | |
| | | // 过滤出符合要求的rule规则 |
| | | getFilteredRule(triggerType = '') { |
| | | let rules = this.getRules(); |
| | | // 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证 |
| | | if (!triggerType) return rules; |
| | | // 历遍判断规则是否有对应的事件,比如blur,change触发等的事件 |
| | | // 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change'] |
| | | // 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性 |
| | | return rules.filter(res => res.trigger && res.trigger.indexOf(triggerType) !== -1); |
| | | }, |
| | | |
| | | // 校验数据 |
| | | validation(trigger, callback = () => {}) { |
| | | // 检验之间,先获取需要校验的值 |
| | | this.fieldValue = this.parent.model[this.prop]; |
| | | // blur和change是否有当前方式的校验规则 |
| | | let rules = this.getFilteredRule(trigger); |
| | | // 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件u-form会因为 |
| | | // 对count变量的统计错误而无法进入上一层的回调 |
| | | if (!rules || rules.length === 0) { |
| | | return callback(''); |
| | | } |
| | | // 设置当前的装填,标识为校验中 |
| | | this.validateState = 'validating'; |
| | | // 调用async-validator的方法 |
| | | let validator = new schema({ |
| | | [this.prop]: rules |
| | | }); |
| | | validator.validate({ |
| | | [this.prop]: this.fieldValue |
| | | }, { |
| | | firstFields: true |
| | | }, (errors, fields) => { |
| | | // 记录状态和报错信息 |
| | | this.validateState = !errors ? 'success' : 'error'; |
| | | this.validateMessage = errors ? errors[0].message : ''; |
| | | // 调用回调方法 |
| | | callback(this.validateMessage); |
| | | }); |
| | | }, |
| | | |
| | | // 清空当前的u-form-item |
| | | resetField() { |
| | | this.parent.model[this.prop] = this.initialValue; |
| | | // 设置为`success`状态,只是为了清空错误标记 |
| | | this.validateState = 'success'; |
| | | } |
| | | }, |
| | | |
| | | // 组件创建完成时,将当前实例保存到u-form中 |
| | | mounted() { |
| | | // 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用 |
| | | this.parent = this.$u.$parent.call(this, 'u-form'); |
| | | if (this.parent) { |
| | | // 历遍parentData中的属性,将parent中的同名属性赋值给parentData |
| | | Object.keys(this.parentData).map(key => { |
| | | this.parentData[key] = this.parent[key]; |
| | | }); |
| | | // 如果没有传入prop,或者uForm为空(如果u-form-input单独使用,就不会有uForm注入),就不进行校验 |
| | | if (this.prop) { |
| | | // 将本实例添加到父组件中 |
| | | this.parent.fields.push(this); |
| | | this.errorType = this.parent.errorType; |
| | | // 设置初始值 |
| | | this.initialValue = this.fieldValue; |
| | | // 添加表单校验,这里必须要写在$nextTick中,因为u-form的rules是通过ref手动传入的 |
| | | // 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给u-form,导致规则为空 |
| | | this.$nextTick(() => { |
| | | this.setRules(); |
| | | }) |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // 组件销毁前,将实例从u-form的缓存中移除 |
| | | beforeDestroy() { |
| | | // 如果当前没有prop的话表示当前不要进行删除(因为没有注入) |
| | | if (this.parent && this.prop) { |
| | | this.parent.fields.map((item, index) => { |
| | | if (item === this) this.parent.fields.splice(index, 1); |
| | | }) |
| | | } |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-form-item { |
| | | @include vue-flex; |
| | | // align-items: flex-start; |
| | | padding: 20rpx 0; |
| | | font-size: 28rpx; |
| | | color: $u-main-color; |
| | | box-sizing: border-box; |
| | | line-height: $u-form-item-height; |
| | | flex-direction: column; |
| | | |
| | | &__border-bottom--error:after { |
| | | border-color: $u-type-error; |
| | | } |
| | | |
| | | &__body { |
| | | @include vue-flex; |
| | | } |
| | | |
| | | &--left { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | |
| | | &__content { |
| | | position: relative; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | padding-right: 10rpx; |
| | | flex: 1; |
| | | |
| | | &__icon { |
| | | margin-right: 8rpx; |
| | | } |
| | | |
| | | &--required { |
| | | position: absolute; |
| | | left: -16rpx; |
| | | vertical-align: middle; |
| | | color: $u-type-error; |
| | | padding-top: 6rpx; |
| | | } |
| | | |
| | | &__label { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | &--right { |
| | | flex: 1; |
| | | |
| | | &__content { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | |
| | | &__slot { |
| | | flex: 1; |
| | | /* #ifndef MP */ |
| | | @include vue-flex; |
| | | align-items: center; |
| | | /* #endif */ |
| | | } |
| | | |
| | | &__icon { |
| | | margin-left: 10rpx; |
| | | color: $u-light-color; |
| | | font-size: 30rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | &__message { |
| | | font-size: 24rpx; |
| | | line-height: 24rpx; |
| | | color: $u-type-error; |
| | | margin-top: 12rpx; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-form"><slot /></view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * form 表单 |
| | | * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。 |
| | | * @tutorial http://uviewui.com/components/form.html |
| | | * @property {Object} model 表单数据对象 |
| | | * @property {Boolean} border-bottom 是否显示表单域的下划线边框 |
| | | * @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方 |
| | | * @property {String Number} label-width 提示文字的宽度,单位rpx(默认90) |
| | | * @property {Object} label-style lable的样式,对象形式 |
| | | * @property {String} label-align lable的对齐方式 |
| | | * @property {Object} rules 通过ref设置,见官网说明 |
| | | * @property {Array} error-type 错误的提示方式,数组形式,见上方说明(默认['message']) |
| | | * @example <u-form :model="form" ref="uForm"></u-form> |
| | | */ |
| | | |
| | | export default { |
| | | name: 'u-form', |
| | | props: { |
| | | // 当前form的需要验证字段的集合 |
| | | model: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 验证规则 |
| | | // rules: { |
| | | // type: [Object, Function, Array], |
| | | // default() { |
| | | // return {}; |
| | | // } |
| | | // }, |
| | | // 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色, |
| | | // border-bottom-下边框呈现红色,none-无提示 |
| | | errorType: { |
| | | type: Array, |
| | | default() { |
| | | return ['message', 'toast'] |
| | | } |
| | | }, |
| | | // 是否显示表单域的下划线边框 |
| | | borderBottom: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // label的位置,left-左边,top-上边 |
| | | labelPosition: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | // label的宽度,单位rpx |
| | | labelWidth: { |
| | | type: [String, Number], |
| | | default: 90 |
| | | }, |
| | | // lable字体的对齐方式 |
| | | labelAlign: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | // lable的样式,对象形式 |
| | | labelStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | }, |
| | | provide() { |
| | | return { |
| | | uForm: this |
| | | }; |
| | | }, |
| | | data() { |
| | | return { |
| | | rules: {} |
| | | }; |
| | | }, |
| | | created() { |
| | | // 存储当前form下的所有u-form-item的实例 |
| | | // 不能定义在data中,否则微信小程序会造成循环引用而报错 |
| | | this.fields = []; |
| | | }, |
| | | methods: { |
| | | setRules(rules) { |
| | | this.rules = rules; |
| | | }, |
| | | // 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法 |
| | | resetFields() { |
| | | this.fields.map(field => { |
| | | field.resetField(); |
| | | }); |
| | | }, |
| | | // 校验全部数据 |
| | | validate(callback) { |
| | | return new Promise(resolve => { |
| | | // 对所有的u-form-item进行校验 |
| | | let valid = true; // 默认通过 |
| | | let count = 0; // 用于标记是否检查完毕 |
| | | let errorArr = []; // 存放错误信息 |
| | | this.fields.map(field => { |
| | | // 调用每一个u-form-item实例的validation的校验方法 |
| | | field.validation('', error => { |
| | | // 如果任意一个u-form-item校验不通过,就意味着整个表单不通过 |
| | | if (error) { |
| | | valid = false; |
| | | errorArr.push(error); |
| | | } |
| | | // 当历遍了所有的u-form-item时,调用promise的then方法 |
| | | if (++count === this.fields.length) { |
| | | resolve(valid); // 进入promise的then方法 |
| | | // 判断是否设置了toast的提示方式,只提示最前面的表单域的第一个错误信息 |
| | | if(this.errorType.indexOf('none') === -1 && this.errorType.indexOf('toast') >= 0 && errorArr.length) { |
| | | this.$u.toast(errorArr[0]); |
| | | } |
| | | // 调用回调方法 |
| | | if (typeof callback == 'function') callback(valid); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <u-modal v-model="show" :show-cancel-button="true" confirm-text="升级" title="发现新版本" @cancel="cancel" @confirm="confirm"> |
| | | <view class="u-update-content"> |
| | | <rich-text :nodes="content"></rich-text> |
| | | </view> |
| | | </u-modal> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | data() { |
| | | return { |
| | | show: false, |
| | | content: ` |
| | | 1. 修复badge组件的size参数无效问题<br> |
| | | 2. 新增Modal模态框组件<br> |
| | | 3. 新增压窗屏组件,可以在APP上以弹窗的形式遮盖导航栏和底部tabbar<br> |
| | | 4. 修复键盘组件在微信小程序上遮罩无效的问题 |
| | | `, |
| | | } |
| | | }, |
| | | onReady() { |
| | | this.show = true; |
| | | }, |
| | | methods: { |
| | | cancel() { |
| | | this.closeModal(); |
| | | }, |
| | | confirm() { |
| | | this.closeModal(); |
| | | }, |
| | | closeModal() { |
| | | uni.navigateBack(); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-full-content { |
| | | background-color: #00C777; |
| | | } |
| | | |
| | | .u-update-content { |
| | | font-size: 26rpx; |
| | | color: $u-content-color; |
| | | line-height: 1.7; |
| | | padding: 30rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-gap" :style="[gapStyle]"></view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * gap 间隔槽 |
| | | * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量 |
| | | * @tutorial https://www.uviewui.com/components/gap.html |
| | | * @property {String} bg-color 背景颜色(默认#f3f4f6) |
| | | * @property {String Number} height 分割槽高度,单位rpx(默认30) |
| | | * @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0) |
| | | * @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0) |
| | | * @example <u-gap height="80" bg-color="#bbb"></u-gap> |
| | | */ |
| | | export default { |
| | | name: "u-gap", |
| | | props: { |
| | | bgColor: { |
| | | type: String, |
| | | default: 'transparent ' // 背景透明 |
| | | }, |
| | | // 高度 |
| | | height: { |
| | | type: [String, Number], |
| | | default: 30 |
| | | }, |
| | | // 与上一个组件的距离 |
| | | marginTop: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 与下一个组件的距离 |
| | | marginBottom: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | }, |
| | | computed: { |
| | | gapStyle() { |
| | | return { |
| | | backgroundColor: this.bgColor, |
| | | height: this.height + 'rpx', |
| | | marginTop: this.marginTop + 'rpx', |
| | | marginBottom: this.marginBottom + 'rpx' |
| | | }; |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-grid-item" :hover-class="parentData.hoverClass" |
| | | :hover-stay-time="200" @tap="click" :style="{ |
| | | background: bgColor, |
| | | width: width, |
| | | }"> |
| | | <view class="u-grid-item-box" :style="[customStyle]" :class="[parentData.border ? 'u-border-right u-border-bottom' : '']"> |
| | | <slot /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * gridItem 提示 |
| | | * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配u-grid使用 |
| | | * @tutorial https://www.uviewui.com/components/grid.html |
| | | * @property {String} bg-color 宫格的背景颜色(默认#ffffff) |
| | | * @property {String Number} index 点击宫格时,返回的值 |
| | | * @property {Object} custom-style 自定义样式,对象形式 |
| | | * @event {Function} click 点击宫格触发 |
| | | * @example <u-grid-item></u-grid-item> |
| | | */ |
| | | export default { |
| | | name: "u-grid-item", |
| | | props: { |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '#ffffff' |
| | | }, |
| | | // 点击时返回的index |
| | | index: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 自定义样式,对象形式 |
| | | customStyle: { |
| | | type: Object, |
| | | default() { |
| | | return { |
| | | padding: '30rpx 0' |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | parentData: { |
| | | hoverClass: '', // 按下去的时候,是否显示背景灰色 |
| | | col: 3, // 父组件划分的宫格数 |
| | | border: true, // 是否显示边框,根据父组件决定 |
| | | } |
| | | }; |
| | | }, |
| | | created() { |
| | | // 父组件的实例 |
| | | this.updateParentData(); |
| | | // this.parent在updateParentData()中定义 |
| | | this.parent.children.push(this); |
| | | }, |
| | | computed: { |
| | | // 每个grid-item的宽度 |
| | | width() { |
| | | return 100 / Number(this.parentData.col) + '%'; |
| | | }, |
| | | }, |
| | | methods: { |
| | | // 获取父组件的参数 |
| | | updateParentData() { |
| | | // 此方法写在mixin中 |
| | | this.getParentData('u-grid'); |
| | | }, |
| | | click() { |
| | | this.$emit('click', this.index); |
| | | this.parent && this.parent.click(this.index); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-grid-item { |
| | | box-sizing: border-box; |
| | | background: #fff; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | flex-direction: column; |
| | | |
| | | /* #ifdef MP */ |
| | | position: relative; |
| | | float: left; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .u-grid-item-hover { |
| | | background: #f7f7f7 !important; |
| | | } |
| | | |
| | | .u-grid-marker-box { |
| | | position: absolute; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | line-height: 0; |
| | | } |
| | | |
| | | .u-grid-marker-wrap { |
| | | position: absolute; |
| | | } |
| | | |
| | | .u-grid-item-box { |
| | | padding: 30rpx 0; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-grid" :class="{'u-border-top u-border-left': border}" :style="[gridStyle]"><slot /></view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * grid 宫格布局 |
| | | * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。 |
| | | * @tutorial https://www.uviewui.com/components/grid.html |
| | | * @property {String Number} col 宫格的列数(默认3) |
| | | * @property {Boolean} border 是否显示宫格的边框(默认true) |
| | | * @property {Boolean} hover-class 点击宫格的时候,是否显示按下的灰色背景(默认false) |
| | | * @event {Function} click 点击宫格触发 |
| | | * @example <u-grid :col="3" @click="click"></u-grid> |
| | | */ |
| | | export default { |
| | | name: 'u-grid', |
| | | props: { |
| | | // 分成几列 |
| | | col: { |
| | | type: [Number, String], |
| | | default: 3 |
| | | }, |
| | | // 是否显示边框 |
| | | border: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 |
| | | align: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | // 宫格按压时的样式类,"none"为无效果 |
| | | hoverClass: { |
| | | type: String, |
| | | default: 'u-hover-class' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | index: 0, |
| | | } |
| | | }, |
| | | watch: { |
| | | // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 |
| | | parentData() { |
| | | if(this.children.length) { |
| | | this.children.map(child => { |
| | | // 判断子组件(u-radio)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值) |
| | | typeof(child.updateParentData) == 'function' && child.updateParentData(); |
| | | }) |
| | | } |
| | | }, |
| | | }, |
| | | created() { |
| | | // 如果将children定义在data中,在微信小程序会造成循环引用而报错 |
| | | this.children = []; |
| | | }, |
| | | computed: { |
| | | // 计算父组件的值是否发生变化 |
| | | parentData() { |
| | | return [this.hoverClass, this.col, this.size, this.border]; |
| | | }, |
| | | // 宫格对齐方式 |
| | | gridStyle() { |
| | | let style = {}; |
| | | switch(this.align) { |
| | | case 'left': |
| | | style.justifyContent = 'flex-start'; |
| | | break; |
| | | case 'center': |
| | | style.justifyContent = 'center'; |
| | | break; |
| | | case 'right': |
| | | style.justifyContent = 'flex-end'; |
| | | break; |
| | | default: style.justifyContent = 'flex-start'; |
| | | }; |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | click(index) { |
| | | this.$emit('click', index); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-grid { |
| | | width: 100%; |
| | | /* #ifdef MP */ |
| | | position: relative; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | /* #endif */ |
| | | |
| | | /* #ifndef MP */ |
| | | @include vue-flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | /* #endif */ |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view :style="[customStyle]" class="u-icon" @tap="click" :class="['u-icon--' + labelPos]"> |
| | | <image class="u-icon__img" v-if="isImg" :src="name" :mode="imgMode" :style="[imgStyle]"></image> |
| | | <text v-else class="u-icon__icon" :class="customClass" :style="[iconStyle]" :hover-class="hoverClass" |
| | | @touchstart="touchstart"> |
| | | <text v-if="showDecimalIcon" :style="[decimalIconStyle]" :class="decimalIconClass" :hover-class="hoverClass" |
| | | class="u-icon__decimal"> |
| | | </text> |
| | | </text> |
| | | <!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 --> |
| | | <text v-if="label !== ''" class="u-icon__label" :style="{ |
| | | color: labelColor, |
| | | fontSize: $u.addUnit(labelSize), |
| | | marginLeft: labelPos == 'right' ? $u.addUnit(marginLeft) : 0, |
| | | marginTop: labelPos == 'bottom' ? $u.addUnit(marginTop) : 0, |
| | | marginRight: labelPos == 'left' ? $u.addUnit(marginRight) : 0, |
| | | marginBottom: labelPos == 'top' ? $u.addUnit(marginBottom) : 0, |
| | | }">{{ label }} |
| | | </text> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * icon 图标 |
| | | * @description 基于字体的图标集,包含了大多数常见场景的图标。 |
| | | * @tutorial https://www.uviewui.com/components/icon.html |
| | | * @property {String} name 图标名称,见示例图标集 |
| | | * @property {String} color 图标颜色(默认inherit) |
| | | * @property {String | Number} size 图标字体大小,单位rpx(默认32) |
| | | * @property {String | Number} label-size label字体大小,单位rpx(默认28) |
| | | * @property {String} label 图标右侧的label文字(默认28) |
| | | * @property {String} label-pos label文字相对于图标的位置,只能right或bottom(默认right) |
| | | * @property {String} label-color label字体颜色(默认#606266) |
| | | * @property {Object} custom-style icon的样式,对象形式 |
| | | * @property {String} custom-prefix 自定义字体图标库时,需要写上此值 |
| | | * @property {String | Number} margin-left label在右侧时与图标的距离,单位rpx(默认6) |
| | | * @property {String | Number} margin-top label在下方时与图标的距离,单位rpx(默认6) |
| | | * @property {String | Number} margin-bottom label在上方时与图标的距离,单位rpx(默认6) |
| | | * @property {String | Number} margin-right label在左侧时与图标的距离,单位rpx(默认6) |
| | | * @property {String} label-pos label相对于图标的位置,只能right或bottom(默认right) |
| | | * @property {String} index 一个用于区分多个图标的值,点击图标时通过click事件传出 |
| | | * @property {String} hover-class 图标按下去的样式类,用法同uni的view组件的hover-class参数,详情见官网 |
| | | * @property {String} width 显示图片小图标时的宽度 |
| | | * @property {String} height 显示图片小图标时的高度 |
| | | * @property {String} top 图标在垂直方向上的定位 |
| | | * @property {String} top 图标在垂直方向上的定位 |
| | | * @property {String} top 图标在垂直方向上的定位 |
| | | * @property {Boolean} show-decimal-icon 是否为DecimalIcon |
| | | * @property {String} inactive-color 背景颜色,可接受主题色,仅Decimal时有效 |
| | | * @property {String | Number} percent 显示的百分比,仅Decimal时有效 |
| | | * @event {Function} click 点击图标时触发 |
| | | * @example <u-icon name="photo" color="#2979ff" size="28"></u-icon> |
| | | */ |
| | | export default { |
| | | name: 'u-icon', |
| | | props: { |
| | | // 图标类名 |
| | | name: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 图标颜色,可接受主题色 |
| | | color: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 字体大小,单位rpx |
| | | size: { |
| | | type: [Number, String], |
| | | default: 'inherit' |
| | | }, |
| | | // 是否显示粗体 |
| | | bold: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 点击图标的时候传递事件出去的index(用于区分点击了哪一个) |
| | | index: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 触摸图标时的类名 |
| | | hoverClass: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 自定义扩展前缀,方便用户扩展自己的图标库 |
| | | customPrefix: { |
| | | type: String, |
| | | default: 'uicon' |
| | | }, |
| | | // 图标右边或者下面的文字 |
| | | label: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // label的位置,只能右边或者下边 |
| | | labelPos: { |
| | | type: String, |
| | | default: 'right' |
| | | }, |
| | | // label的大小 |
| | | labelSize: { |
| | | type: [String, Number], |
| | | default: '28' |
| | | }, |
| | | // label的颜色 |
| | | labelColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // label与图标的距离(横向排列) |
| | | marginLeft: { |
| | | type: [String, Number], |
| | | default: '6' |
| | | }, |
| | | // label与图标的距离(竖向排列) |
| | | marginTop: { |
| | | type: [String, Number], |
| | | default: '6' |
| | | }, |
| | | // label与图标的距离(竖向排列) |
| | | marginRight: { |
| | | type: [String, Number], |
| | | default: '6' |
| | | }, |
| | | // label与图标的距离(竖向排列) |
| | | marginBottom: { |
| | | type: [String, Number], |
| | | default: '6' |
| | | }, |
| | | // 图片的mode |
| | | imgMode: { |
| | | type: String, |
| | | default: 'widthFix' |
| | | }, |
| | | // 自定义样式 |
| | | customStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | // 用于显示图片小图标时,图片的宽度 |
| | | width: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 用于显示图片小图标时,图片的高度 |
| | | height: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 用于解决某些情况下,让图标垂直居中的用途 |
| | | top: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 是否为DecimalIcon |
| | | showDecimalIcon: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 背景颜色,可接受主题色,仅Decimal时有效 |
| | | inactiveColor: { |
| | | type: String, |
| | | default: '#ececec' |
| | | }, |
| | | // 显示的百分比,仅Decimal时有效 |
| | | percent: { |
| | | type: [Number, String], |
| | | default: '50' |
| | | } |
| | | }, |
| | | computed: { |
| | | customClass() { |
| | | let classes = [] |
| | | classes.push(this.customPrefix + '-' + this.name) |
| | | // uView的自定义图标类名为u-iconfont |
| | | if (this.customPrefix == 'uicon') { |
| | | classes.push('u-iconfont') |
| | | } else { |
| | | classes.push(this.customPrefix) |
| | | } |
| | | // 主题色,通过类配置 |
| | | if (this.showDecimalIcon && this.inactiveColor && this.$u.config.type.includes(this.inactiveColor)) { |
| | | classes.push('u-icon__icon--' + this.inactiveColor) |
| | | } else if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color) |
| | | // 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别 |
| | | // 故需将其拆成一个字符串的形式,通过空格隔开各个类名 |
| | | //#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU |
| | | classes = classes.join(' ') |
| | | //#endif |
| | | return classes |
| | | }, |
| | | iconStyle() { |
| | | let style = {} |
| | | style = { |
| | | fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size), |
| | | fontWeight: this.bold ? 'bold' : 'normal', |
| | | // 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中 |
| | | top: this.$u.addUnit(this.top) |
| | | } |
| | | // 非主题色值时,才当作颜色值 |
| | | if (this.showDecimalIcon && this.inactiveColor && !this.$u.config.type.includes(this.inactiveColor)) { |
| | | style.color = this.inactiveColor |
| | | } else if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color |
| | | |
| | | return style |
| | | }, |
| | | // 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式 |
| | | isImg() { |
| | | return this.name.indexOf('/') !== -1 |
| | | }, |
| | | imgStyle() { |
| | | let style = {} |
| | | // 如果设置width和height属性,则优先使用,否则使用size属性 |
| | | style.width = this.width ? this.$u.addUnit(this.width) : this.$u.addUnit(this.size) |
| | | style.height = this.height ? this.$u.addUnit(this.height) : this.$u.addUnit(this.size) |
| | | return style |
| | | }, |
| | | decimalIconStyle() { |
| | | let style = {} |
| | | style = { |
| | | fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size), |
| | | fontWeight: this.bold ? 'bold' : 'normal', |
| | | // 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中 |
| | | top: this.$u.addUnit(this.top), |
| | | width: this.percent + '%' |
| | | } |
| | | // 非主题色值时,才当作颜色值 |
| | | if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color |
| | | return style |
| | | }, |
| | | decimalIconClass() { |
| | | let classes = [] |
| | | classes.push(this.customPrefix + '-' + this.name) |
| | | // uView的自定义图标类名为u-iconfont |
| | | if (this.customPrefix == 'uicon') { |
| | | classes.push('u-iconfont') |
| | | } else { |
| | | classes.push(this.customPrefix) |
| | | } |
| | | // 主题色,通过类配置 |
| | | if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color) |
| | | else classes.push('u-icon__icon--primary') |
| | | // 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别 |
| | | // 故需将其拆成一个字符串的形式,通过空格隔开各个类名 |
| | | //#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU |
| | | classes = classes.join(' ') |
| | | //#endif |
| | | return classes |
| | | } |
| | | }, |
| | | methods: { |
| | | click() { |
| | | this.$emit('click', this.index) |
| | | }, |
| | | touchstart() { |
| | | this.$emit('touchstart', this.index) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | @import '../../iconfont.css'; |
| | | |
| | | .u-icon { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | |
| | | &--left { |
| | | flex-direction: row-reverse; |
| | | align-items: center; |
| | | } |
| | | |
| | | &--right { |
| | | flex-direction: row; |
| | | align-items: center; |
| | | } |
| | | |
| | | &--top { |
| | | flex-direction: column-reverse; |
| | | justify-content: center; |
| | | } |
| | | |
| | | &--bottom { |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | } |
| | | |
| | | &__icon { |
| | | position: relative; |
| | | |
| | | &--primary { |
| | | color: $u-type-primary; |
| | | } |
| | | |
| | | &--success { |
| | | color: $u-type-success; |
| | | } |
| | | |
| | | &--error { |
| | | color: $u-type-error; |
| | | } |
| | | |
| | | &--warning { |
| | | color: $u-type-warning; |
| | | } |
| | | |
| | | &--info { |
| | | color: $u-type-info; |
| | | } |
| | | } |
| | | |
| | | &__decimal { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | display: inline-block; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | &__img { |
| | | height: auto; |
| | | will-change: transform; |
| | | } |
| | | |
| | | &__label { |
| | | line-height: 1; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-image" @tap="onClick" :style="[wrapStyle, backgroundStyle]"> |
| | | <image |
| | | v-if="!isError" |
| | | :src="src" |
| | | :mode="mode" |
| | | @error="onErrorHandler" |
| | | @load="onLoadHandler" |
| | | :lazy-load="lazyLoad" |
| | | class="u-image__image" |
| | | :show-menu-by-longpress="showMenuByLongpress" |
| | | :style="{ |
| | | borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius) |
| | | }" |
| | | ></image> |
| | | <view |
| | | v-if="showLoading && loading" |
| | | class="u-image__loading" |
| | | :style="{ |
| | | borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius), |
| | | backgroundColor: this.bgColor |
| | | }" |
| | | > |
| | | <slot v-if="$slots.loading" name="loading" /> |
| | | <u-icon v-else :name="loadingIcon" :width="width" :height="height"></u-icon> |
| | | </view> |
| | | <view |
| | | v-if="showError && isError && !loading" |
| | | class="u-image__error" |
| | | :style="{ |
| | | borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius) |
| | | }" |
| | | > |
| | | <slot v-if="$slots.error" name="error" /> |
| | | <u-icon v-else :name="errorIcon" :width="width" :height="height"></u-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * Image 图片 |
| | | * @description 此组件为uni-app的image组件的加强版,在继承了原有功能外,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。 |
| | | * @tutorial https://uviewui.com/components/image.html |
| | | * @property {String} src 图片地址 |
| | | * @property {String} mode 裁剪模式,见官网说明 |
| | | * @property {String | Number} width 宽度,单位任意,如果为数值,则为rpx单位(默认100%) |
| | | * @property {String | Number} height 高度,单位任意,如果为数值,则为rpx单位(默认 auto) |
| | | * @property {String} shape 图片形状,circle-圆形,square-方形(默认square) |
| | | * @property {String | Number} border-radius 圆角值,单位任意,如果为数值,则为rpx单位(默认 0) |
| | | * @property {Boolean} lazy-load 是否懒加载,仅微信小程序、App、百度小程序、字节跳动小程序有效(默认 true) |
| | | * @property {Boolean} show-menu-by-longpress 是否开启长按图片显示识别小程序码菜单,仅微信小程序有效(默认 false) |
| | | * @property {String} loading-icon 加载中的图标,或者小图片(默认 photo) |
| | | * @property {String} error-icon 加载失败的图标,或者小图片(默认 error-circle) |
| | | * @property {Boolean} show-loading 是否显示加载中的图标或者自定义的slot(默认 true) |
| | | * @property {Boolean} show-error 是否显示加载错误的图标或者自定义的slot(默认 true) |
| | | * @property {Boolean} fade 是否需要淡入效果(默认 true) |
| | | * @property {String Number} width 传入图片路径时图片的宽度 |
| | | * @property {String Number} height 传入图片路径时图片的高度 |
| | | * @property {Boolean} webp 只支持网络资源,只对微信小程序有效(默认 false) |
| | | * @property {String | Number} duration 搭配fade参数的过渡时间,单位ms(默认 500) |
| | | * @event {Function} click 点击图片时触发 |
| | | * @event {Function} error 图片加载失败时触发 |
| | | * @event {Function} load 图片加载成功时触发 |
| | | * @example <u-image width="100%" height="300rpx" :src="src"></u-image> |
| | | */ |
| | | export default { |
| | | name: 'u-image', |
| | | props: { |
| | | // 图片地址 |
| | | src: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 裁剪模式 |
| | | mode: { |
| | | type: String, |
| | | default: 'aspectFill' |
| | | }, |
| | | // 宽度,单位任意 |
| | | width: { |
| | | type: [String, Number], |
| | | default: '100%' |
| | | }, |
| | | // 高度,单位任意 |
| | | height: { |
| | | type: [String, Number], |
| | | default: 'auto' |
| | | }, |
| | | // 图片形状,circle-圆形,square-方形 |
| | | shape: { |
| | | type: String, |
| | | default: 'square' |
| | | }, |
| | | // 圆角,单位任意 |
| | | borderRadius: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序 |
| | | lazyLoad: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 开启长按图片显示识别微信小程序码菜单 |
| | | showMenuByLongpress: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 加载中的图标,或者小图片 |
| | | loadingIcon: { |
| | | type: String, |
| | | default: 'photo' |
| | | }, |
| | | // 加载失败的图标,或者小图片 |
| | | errorIcon: { |
| | | type: String, |
| | | default: 'error-circle' |
| | | }, |
| | | // 是否显示加载中的图标或者自定义的slot |
| | | showLoading: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示加载错误的图标或者自定义的slot |
| | | showError: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否需要淡入效果 |
| | | fade: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 只支持网络资源,只对微信小程序有效 |
| | | webp: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 过渡时间,单位ms |
| | | duration: { |
| | | type: [String, Number], |
| | | default: 500 |
| | | }, |
| | | // 背景颜色,用于深色页面加载图片时,为了和背景色融合 |
| | | bgColor: { |
| | | type: String, |
| | | default: '#f3f4f6' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | // 图片是否加载错误,如果是,则显示错误占位图 |
| | | isError: false, |
| | | // 初始化组件时,默认为加载中状态 |
| | | loading: true, |
| | | // 不透明度,为了实现淡入淡出的效果 |
| | | opacity: 1, |
| | | // 过渡时间,因为props的值无法修改,故需要一个中间值 |
| | | durationTime: this.duration, |
| | | // 图片加载完成时,去掉背景颜色,因为如果是png图片,就会显示灰色的背景 |
| | | backgroundStyle: {} |
| | | }; |
| | | }, |
| | | watch: { |
| | | src: { |
| | | immediate: true, |
| | | handler (n) { |
| | | if(!n) { |
| | | // 如果传入null或者'',或者false,或者undefined,标记为错误状态 |
| | | this.isError = true; |
| | | this.loading = false; |
| | | } else { |
| | | this.isError = false; |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | computed: { |
| | | wrapStyle() { |
| | | let style = {}; |
| | | // 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位 |
| | | style.width = this.$u.addUnit(this.width); |
| | | style.height = this.$u.addUnit(this.height); |
| | | // 如果是配置了圆形,设置50%的圆角,否则按照默认的配置值 |
| | | style.borderRadius = this.shape == 'circle' ? '50%' : this.$u.addUnit(this.borderRadius); |
| | | // 如果设置圆角,必须要有hidden,否则可能圆角无效 |
| | | style.overflow = this.borderRadius > 0 ? 'hidden' : 'visible'; |
| | | if (this.fade) { |
| | | style.opacity = this.opacity; |
| | | style.transition = `opacity ${Number(this.durationTime) / 1000}s ease-in-out`; |
| | | } |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击图片 |
| | | onClick() { |
| | | this.$emit('click'); |
| | | }, |
| | | // 图片加载失败 |
| | | onErrorHandler(err) { |
| | | this.loading = false; |
| | | this.isError = true; |
| | | this.$emit('error', err); |
| | | }, |
| | | // 图片加载完成,标记loading结束 |
| | | onLoadHandler() { |
| | | this.loading = false; |
| | | this.isError = false; |
| | | this.$emit('load'); |
| | | // 如果不需要动画效果,就不执行下方代码,同时移除加载时的背景颜色 |
| | | // 否则无需fade效果时,png图片依然能看到下方的背景色 |
| | | if (!this.fade) return this.removeBgColor(); |
| | | // 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的灰色),再改成1,是为了获得过渡效果 |
| | | this.opacity = 0; |
| | | // 这里设置为0,是为了图片展示到背景全透明这个过程时间为0,延时之后延时之后重新设置为duration,是为了获得背景透明(灰色) |
| | | // 到图片展示的过程中的淡入效果 |
| | | this.durationTime = 0; |
| | | // 延时50ms,否则在浏览器H5,过渡效果无效 |
| | | setTimeout(() => { |
| | | this.durationTime = this.duration; |
| | | this.opacity = 1; |
| | | setTimeout(() => { |
| | | this.removeBgColor(); |
| | | }, this.durationTime); |
| | | }, 50); |
| | | }, |
| | | // 移除图片的背景色 |
| | | removeBgColor() { |
| | | // 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景 |
| | | this.backgroundStyle = { |
| | | backgroundColor: 'transparent' |
| | | }; |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '../../libs/css/style.components.scss'; |
| | | |
| | | .u-image { |
| | | position: relative; |
| | | transition: opacity 0.5s ease-in-out; |
| | | |
| | | &__image { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__loading, |
| | | &__error { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: $u-bg-color; |
| | | color: $u-tips-color; |
| | | font-size: 46rpx; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸,所以在外面套一个"壳" --> |
| | | <view> |
| | | <view class="u-index-anchor-wrapper" :id="$u.guid()" :style="[wrapperStyle]"> |
| | | <view class="u-index-anchor " :class="[active ? 'u-index-anchor--active' : '']" :style="[customAnchorStyle]"> |
| | | <slot v-if="useSlot" /> |
| | | <block v-else> |
| | | <text>{{ index }}</text> |
| | | </block> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * indexAnchor 索引列表锚点 |
| | | * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用 |
| | | * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props |
| | | * @property {Boolean} use-slot 是否使用自定义内容的插槽(默认false) |
| | | * @property {String Number} index 索引字符,如果定义了use-slot,此参数自动失效 |
| | | * @property {Object} custStyle 自定义样式,对象形式,如"{color: 'red'}" |
| | | * @event {Function} default 锚点位置显示内容,默认为索引字符 |
| | | * @example <u-index-anchor :index="item" /> |
| | | */ |
| | | export default { |
| | | name: "u-index-anchor", |
| | | props: { |
| | | useSlot: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | index: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | customStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | active: false, |
| | | wrapperStyle: {}, |
| | | anchorStyle: {} |
| | | } |
| | | }, |
| | | created() { |
| | | this.parent = false; |
| | | }, |
| | | mounted() { |
| | | this.parent = this.$u.$parent.call(this, 'u-index-list'); |
| | | if(this.parent) { |
| | | this.parent.children.push(this); |
| | | this.parent.updateData(); |
| | | } |
| | | }, |
| | | computed: { |
| | | customAnchorStyle() { |
| | | return Object.assign(this.anchorStyle, this.customStyle); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-index-anchor { |
| | | box-sizing: border-box; |
| | | padding: 14rpx 24rpx; |
| | | color: #606266; |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 28rpx; |
| | | line-height: 1.2; |
| | | background-color: rgb(245, 245, 245); |
| | | } |
| | | |
| | | .u-index-anchor--active { |
| | | right: 0; |
| | | left: 0; |
| | | color: #2979ff; |
| | | background-color: #fff; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸,所以在外面套一个"壳" --> |
| | | <view> |
| | | <view class="u-index-bar"> |
| | | <slot /> |
| | | <view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove" |
| | | @touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop"> |
| | | <view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}" |
| | | :data-index="index"> |
| | | {{ item }} |
| | | </view> |
| | | </view> |
| | | <view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{ |
| | | zIndex: alertZIndex |
| | | }"> |
| | | <text>{{indexList[touchmoveIndex]}}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | var indexList = function() { |
| | | var indexList = []; |
| | | var charCodeOfA = 'A'.charCodeAt(0); |
| | | for (var i = 0; i < 26; i++) { |
| | | indexList.push(String.fromCharCode(charCodeOfA + i)); |
| | | } |
| | | return indexList; |
| | | }; |
| | | |
| | | /** |
| | | * indexList 索引列表 |
| | | * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用 |
| | | * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props |
| | | * @property {Number String} scroll-top 当前滚动高度,自定义组件无法获得滚动条事件,所以依赖接入方传入 |
| | | * @property {Array} index-list 索引字符列表,数组(默认A-Z) |
| | | * @property {Number String} z-index 锚点吸顶时的层级(默认965) |
| | | * @property {Boolean} sticky 是否开启锚点自动吸顶(默认true) |
| | | * @property {Number String} offset-top 锚点自动吸顶时与顶部的距离(默认0) |
| | | * @property {String} highlight-color 锚点和右边索引字符高亮颜色(默认#2979ff) |
| | | * @event {Function} select 选中右边索引字符时触发 |
| | | * @example <u-index-list :scrollTop="scrollTop"></u-index-list> |
| | | */ |
| | | export default { |
| | | name: "u-index-list", |
| | | props: { |
| | | sticky: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | zIndex: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | scrollTop: { |
| | | type: [Number, String], |
| | | default: 0, |
| | | }, |
| | | offsetTop: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | indexList: { |
| | | type: Array, |
| | | default () { |
| | | return indexList() |
| | | } |
| | | }, |
| | | activeColor: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | } |
| | | }, |
| | | created() { |
| | | // #ifdef H5 |
| | | this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44; |
| | | // #endif |
| | | // #ifndef H5 |
| | | this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0; |
| | | // #endif |
| | | // 只能在created生命周期定义children,如果在data定义,会因为循环引用而报错 |
| | | this.children = []; |
| | | }, |
| | | data() { |
| | | return { |
| | | activeAnchorIndex: 0, |
| | | showSidebar: true, |
| | | // children: [], |
| | | touchmove: false, |
| | | touchmoveIndex: 0, |
| | | } |
| | | }, |
| | | watch: { |
| | | scrollTop() { |
| | | this.updateData() |
| | | } |
| | | }, |
| | | computed: { |
| | | // 弹出toast的z-index值 |
| | | alertZIndex() { |
| | | return this.$u.zIndex.toast; |
| | | } |
| | | }, |
| | | methods: { |
| | | updateData() { |
| | | this.timer && clearTimeout(this.timer); |
| | | this.timer = setTimeout(() => { |
| | | this.showSidebar = !!this.children.length; |
| | | this.setRect().then(() => { |
| | | this.onScroll(); |
| | | }); |
| | | }, 0); |
| | | }, |
| | | setRect() { |
| | | return Promise.all([ |
| | | this.setAnchorsRect(), |
| | | this.setListRect(), |
| | | this.setSiderbarRect() |
| | | ]); |
| | | }, |
| | | setAnchorsRect() { |
| | | return Promise.all(this.children.map((anchor, index) => anchor |
| | | .$uGetRect('.u-index-anchor-wrapper') |
| | | .then((rect) => { |
| | | Object.assign(anchor, { |
| | | height: rect.height, |
| | | top: rect.top |
| | | }); |
| | | }))); |
| | | }, |
| | | setListRect() { |
| | | return this.$uGetRect('.u-index-bar').then((rect) => { |
| | | Object.assign(this, { |
| | | height: rect.height, |
| | | top: rect.top + this.scrollTop |
| | | }); |
| | | }); |
| | | }, |
| | | setSiderbarRect() { |
| | | return this.$uGetRect('.u-index-bar__sidebar').then(rect => { |
| | | this.sidebar = { |
| | | height: rect.height, |
| | | top: rect.top |
| | | }; |
| | | }); |
| | | }, |
| | | getActiveAnchorIndex() { |
| | | const { |
| | | children |
| | | } = this; |
| | | const { |
| | | sticky |
| | | } = this; |
| | | for (let i = this.children.length - 1; i >= 0; i--) { |
| | | const preAnchorHeight = i > 0 ? children[i - 1].height : 0; |
| | | const reachTop = sticky ? preAnchorHeight : 0; |
| | | if (reachTop >= children[i].top) { |
| | | return i; |
| | | } |
| | | } |
| | | return -1; |
| | | }, |
| | | onScroll() { |
| | | const { |
| | | children = [] |
| | | } = this; |
| | | if (!children.length) { |
| | | return; |
| | | } |
| | | const { |
| | | sticky, |
| | | stickyOffsetTop, |
| | | zIndex, |
| | | scrollTop, |
| | | activeColor |
| | | } = this; |
| | | const active = this.getActiveAnchorIndex(); |
| | | this.activeAnchorIndex = active; |
| | | if (sticky) { |
| | | let isActiveAnchorSticky = false; |
| | | if (active !== -1) { |
| | | isActiveAnchorSticky = |
| | | children[active].top <= 0; |
| | | } |
| | | children.forEach((item, index) => { |
| | | if (index === active) { |
| | | let wrapperStyle = ''; |
| | | let anchorStyle = { |
| | | color: `${activeColor}` |
| | | }; |
| | | if (isActiveAnchorSticky) { |
| | | wrapperStyle = { |
| | | height: `${children[index].height}px` |
| | | }; |
| | | anchorStyle = { |
| | | position: 'fixed', |
| | | top: `${stickyOffsetTop}px`, |
| | | zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`, |
| | | color: `${activeColor}` |
| | | }; |
| | | } |
| | | item.active = active; |
| | | item.wrapperStyle = wrapperStyle; |
| | | item.anchorStyle = anchorStyle; |
| | | } else if (index === active - 1) { |
| | | const currentAnchor = children[index]; |
| | | const currentOffsetTop = currentAnchor.top; |
| | | const targetOffsetTop = index === children.length - 1 ? |
| | | this.top : |
| | | children[index + 1].top; |
| | | const parentOffsetHeight = targetOffsetTop - currentOffsetTop; |
| | | const translateY = parentOffsetHeight - currentAnchor.height; |
| | | const anchorStyle = { |
| | | position: 'relative', |
| | | transform: `translate3d(0, ${translateY}px, 0)`, |
| | | zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`, |
| | | color: `${activeColor}` |
| | | }; |
| | | item.active = active; |
| | | item.anchorStyle = anchorStyle; |
| | | } else { |
| | | item.active = false; |
| | | item.anchorStyle = ''; |
| | | item.wrapperStyle = ''; |
| | | } |
| | | }); |
| | | } |
| | | }, |
| | | onTouchMove(event) { |
| | | this.touchmove = true; |
| | | const sidebarLength = this.children.length; |
| | | const touch = event.touches[0]; |
| | | const itemHeight = this.sidebar.height / sidebarLength; |
| | | let clientY = 0; |
| | | clientY = touch.clientY; |
| | | let index = Math.floor((clientY - this.sidebar.top) / itemHeight); |
| | | if (index < 0) { |
| | | index = 0; |
| | | } else if (index > sidebarLength - 1) { |
| | | index = sidebarLength - 1; |
| | | } |
| | | this.touchmoveIndex = index; |
| | | this.scrollToAnchor(index); |
| | | }, |
| | | onTouchStop() { |
| | | this.touchmove = false; |
| | | this.scrollToAnchorIndex = null; |
| | | }, |
| | | scrollToAnchor(index) { |
| | | if (this.scrollToAnchorIndex === index) { |
| | | return; |
| | | } |
| | | this.scrollToAnchorIndex = index; |
| | | const anchor = this.children.find((item) => item.index === this.indexList[index]); |
| | | if (anchor) { |
| | | this.$emit('select', anchor.index); |
| | | uni.pageScrollTo({ |
| | | duration: 0, |
| | | scrollTop: anchor.top + this.scrollTop |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-index-bar { |
| | | position: relative |
| | | } |
| | | |
| | | .u-index-bar__sidebar { |
| | | position: fixed; |
| | | top: 50%; |
| | | right: 0; |
| | | @include vue-flex; |
| | | flex-direction: column; |
| | | text-align: center; |
| | | transform: translateY(-50%); |
| | | user-select: none; |
| | | z-index: 99; |
| | | } |
| | | |
| | | .u-index-bar__index { |
| | | font-weight: 500; |
| | | padding: 8rpx 18rpx; |
| | | font-size: 22rpx; |
| | | line-height: 1 |
| | | } |
| | | |
| | | .u-indexed-list-alert { |
| | | position: fixed; |
| | | width: 120rpx; |
| | | height: 120rpx; |
| | | right: 90rpx; |
| | | top: 50%; |
| | | margin-top: -60rpx; |
| | | border-radius: 24rpx; |
| | | font-size: 50rpx; |
| | | color: #fff; |
| | | background-color: rgba(0, 0, 0, 0.65); |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | padding: 0; |
| | | z-index: 9999999; |
| | | } |
| | | |
| | | .u-indexed-list-alert text { |
| | | line-height: 50rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view |
| | | class="u-input" |
| | | :class="{ |
| | | 'u-input--border': border, |
| | | 'u-input--error': validateState |
| | | }" |
| | | :style="{ |
| | | padding: `0 ${border ? 20 : 0}rpx`, |
| | | borderColor: borderColor, |
| | | textAlign: inputAlign |
| | | }" |
| | | @tap.stop="inputClick" |
| | | > |
| | | <textarea |
| | | v-if="type == 'textarea'" |
| | | class="u-input__input u-input__textarea" |
| | | :style="[getStyle]" |
| | | :value="defaultValue" |
| | | :placeholder="placeholder" |
| | | :placeholderStyle="placeholderStyle" |
| | | :disabled="disabled" |
| | | :maxlength="inputMaxlength" |
| | | :fixed="fixed" |
| | | :focus="focus" |
| | | :autoHeight="autoHeight" |
| | | :selection-end="uSelectionEnd" |
| | | :selection-start="uSelectionStart" |
| | | :cursor-spacing="getCursorSpacing" |
| | | :show-confirm-bar="showConfirmbar" |
| | | @input="handleInput" |
| | | @blur="handleBlur" |
| | | @focus="onFocus" |
| | | @confirm="onConfirm" |
| | | /> |
| | | <input |
| | | v-else |
| | | class="u-input__input" |
| | | :type="type == 'password' ? 'text' : type" |
| | | :style="[getStyle]" |
| | | :value="defaultValue" |
| | | :password="type == 'password' && !showPassword" |
| | | :placeholder="placeholder" |
| | | :placeholderStyle="placeholderStyle" |
| | | :disabled="disabled || type === 'select'" |
| | | :maxlength="inputMaxlength" |
| | | :focus="focus" |
| | | :confirmType="confirmType" |
| | | :cursor-spacing="getCursorSpacing" |
| | | :selection-end="uSelectionEnd" |
| | | :selection-start="uSelectionStart" |
| | | :show-confirm-bar="showConfirmbar" |
| | | @focus="onFocus" |
| | | @blur="handleBlur" |
| | | @input="handleInput" |
| | | @confirm="onConfirm" |
| | | /> |
| | | <view class="u-input__right-icon u-flex"> |
| | | <view class="u-input__right-icon__clear u-input__right-icon__item" @tap="onClear" v-if="clearable && value != '' && focused"> |
| | | <u-icon size="32" name="close-circle-fill" color="#c0c4cc"/> |
| | | </view> |
| | | <view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'"> |
| | | <u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/> |
| | | </view> |
| | | <view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{ |
| | | 'u-input__right-icon--select--reverse': selectOpen |
| | | }"> |
| | | <u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import Emitter from '../../libs/util/emitter.js'; |
| | | |
| | | /** |
| | | * input 输入框 |
| | | * @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。 |
| | | * @tutorial http://uviewui.com/components/input.html |
| | | * @property {String} type 模式选择,见官网说明 |
| | | * @property {Boolean} clearable 是否显示右侧的清除图标(默认true) |
| | | * @property {} v-model 用于双向绑定输入框的值 |
| | | * @property {String} input-align 输入框文字的对齐方式(默认left) |
| | | * @property {String} placeholder placeholder显示值(默认 '请输入内容') |
| | | * @property {Boolean} disabled 是否禁用输入框(默认false) |
| | | * @property {String Number} maxlength 输入框的最大可输入长度(默认140) |
| | | * @property {String Number} selection-start 光标起始位置,自动聚焦时有效,需与selection-end搭配使用(默认-1) |
| | | * @property {String Number} maxlength 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认-1) |
| | | * @property {String Number} cursor-spacing 指定光标与键盘的距离,单位px(默认0) |
| | | * @property {String} placeholderStyle placeholder的样式,字符串形式,如"color: red;"(默认 "color: #c0c4cc;") |
| | | * @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type为text时生效(默认done) |
| | | * @property {Object} custom-style 自定义输入框的样式,对象形式 |
| | | * @property {Boolean} focus 是否自动获得焦点(默认false) |
| | | * @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false) |
| | | * @property {Boolean} password-icon type为password时,是否显示右侧的密码查看图标(默认true) |
| | | * @property {Boolean} border 是否显示边框(默认false) |
| | | * @property {String} border-color 输入框的边框颜色(默认#dcdfe6) |
| | | * @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true) |
| | | * @property {String Number} height 高度,单位rpx(text类型时为70,textarea时为100) |
| | | * @example <u-input v-model="value" :type="type" :border="border" /> |
| | | */ |
| | | export default { |
| | | name: 'u-input', |
| | | mixins: [Emitter], |
| | | props: { |
| | | value: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 输入框的类型,textarea,text,number |
| | | type: { |
| | | type: String, |
| | | default: 'text' |
| | | }, |
| | | inputAlign: { |
| | | type: String, |
| | | default: 'left' |
| | | }, |
| | | placeholder: { |
| | | type: String, |
| | | default: '请输入内容' |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | maxlength: { |
| | | type: [Number, String], |
| | | default: 140 |
| | | }, |
| | | placeholderStyle: { |
| | | type: String, |
| | | default: 'color: #c0c4cc;' |
| | | }, |
| | | confirmType: { |
| | | type: String, |
| | | default: 'done' |
| | | }, |
| | | // 输入框的自定义样式 |
| | | customStyle: { |
| | | type: Object, |
| | | default() { |
| | | return {}; |
| | | } |
| | | }, |
| | | // 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true |
| | | fixed: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否自动获得焦点 |
| | | focus: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 密码类型时,是否显示右侧的密码图标 |
| | | passwordIcon: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // input|textarea是否显示边框 |
| | | border: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 输入框的边框颜色 |
| | | borderColor: { |
| | | type: String, |
| | | default: '#dcdfe6' |
| | | }, |
| | | autoHeight: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // type=select时,旋转右侧的图标,标识当前处于打开还是关闭select的状态 |
| | | // open-打开,close-关闭 |
| | | selectOpen: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 高度,单位rpx |
| | | height: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 是否可清空 |
| | | clearable: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 指定光标与键盘的距离,单位 px |
| | | cursorSpacing: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 光标起始位置,自动聚焦时有效,需与selection-end搭配使用 |
| | | selectionStart: { |
| | | type: [Number, String], |
| | | default: -1 |
| | | }, |
| | | // 光标结束位置,自动聚焦时有效,需与selection-start搭配使用 |
| | | selectionEnd: { |
| | | type: [Number, String], |
| | | default: -1 |
| | | }, |
| | | // 是否自动去除两端的空格 |
| | | trim: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示键盘上方带有”完成“按钮那一栏 |
| | | showConfirmbar:{ |
| | | type:Boolean, |
| | | default:true |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | defaultValue: this.value, |
| | | inputHeight: 70, // input的高度 |
| | | textareaHeight: 100, // textarea的高度 |
| | | validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色 |
| | | focused: false, // 当前是否处于获得焦点的状态 |
| | | showPassword: false, // 是否预览密码 |
| | | lastValue: '', // 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input时间 |
| | | }; |
| | | }, |
| | | watch: { |
| | | value(nVal, oVal) { |
| | | this.defaultValue = nVal; |
| | | // 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件 |
| | | if(nVal != oVal && this.type == 'select') this.handleInput({ |
| | | detail: { |
| | | value: nVal |
| | | } |
| | | }) |
| | | }, |
| | | }, |
| | | computed: { |
| | | // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值 |
| | | inputMaxlength() { |
| | | return Number(this.maxlength); |
| | | }, |
| | | getStyle() { |
| | | let style = {}; |
| | | // 如果没有自定义高度,就根据type为input还是textare来分配一个默认的高度 |
| | | style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ? |
| | | this.textareaHeight + 'rpx' : this.inputHeight + 'rpx'; |
| | | style = Object.assign(style, this.customStyle); |
| | | return style; |
| | | }, |
| | | // |
| | | getCursorSpacing() { |
| | | return Number(this.cursorSpacing); |
| | | }, |
| | | // 光标起始位置 |
| | | uSelectionStart() { |
| | | return String(this.selectionStart); |
| | | }, |
| | | // 光标结束位置 |
| | | uSelectionEnd() { |
| | | return String(this.selectionEnd); |
| | | } |
| | | }, |
| | | created() { |
| | | // 监听u-form-item发出的错误事件,将输入框边框变红色 |
| | | this.$on('on-form-item-error', this.onFormItemError); |
| | | }, |
| | | methods: { |
| | | /** |
| | | * change 事件 |
| | | * @param event |
| | | */ |
| | | handleInput(event) { |
| | | let value = event.detail.value; |
| | | // 判断是否去除空格 |
| | | if(this.trim) value = this.$u.trim(value); |
| | | // vue 原生的方法 return 出去 |
| | | this.$emit('input', value); |
| | | // 当前model 赋值 |
| | | this.defaultValue = value; |
| | | // 过一个生命周期再发送事件给u-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上 |
| | | // 尚未更新到u-form-item,导致获取的值为空,从而校验混论 |
| | | // 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱 |
| | | setTimeout(() => { |
| | | // 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理 |
| | | // #ifdef MP-TOUTIAO |
| | | if(this.$u.trim(value) == this.lastValue) return ; |
| | | this.lastValue = value; |
| | | // #endif |
| | | // 将当前的值发送到 u-form-item 进行校验 |
| | | this.dispatch('u-form-item', 'on-form-change', value); |
| | | }, 40) |
| | | }, |
| | | /** |
| | | * blur 事件 |
| | | * @param event |
| | | */ |
| | | handleBlur(event) { |
| | | // 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错 |
| | | // 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时 |
| | | setTimeout(() => { |
| | | this.focused = false; |
| | | }, 100) |
| | | // vue 原生的方法 return 出去 |
| | | this.$emit('blur', event.detail.value); |
| | | setTimeout(() => { |
| | | // 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理 |
| | | // #ifdef MP-TOUTIAO |
| | | if(this.$u.trim(value) == this.lastValue) return ; |
| | | this.lastValue = value; |
| | | // #endif |
| | | // 将当前的值发送到 u-form-item 进行校验 |
| | | this.dispatch('u-form-item', 'on-form-blur', event.detail.value); |
| | | }, 40) |
| | | }, |
| | | onFormItemError(status) { |
| | | this.validateState = status; |
| | | }, |
| | | onFocus(event) { |
| | | this.focused = true; |
| | | this.$emit('focus'); |
| | | }, |
| | | onConfirm(e) { |
| | | this.$emit('confirm', e.detail.value); |
| | | }, |
| | | onClear(event) { |
| | | this.$emit('input', ''); |
| | | }, |
| | | inputClick() { |
| | | this.$emit('click'); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-input { |
| | | position: relative; |
| | | flex: 1; |
| | | @include vue-flex; |
| | | |
| | | &__input { |
| | | //height: $u-form-item-height; |
| | | font-size: 28rpx; |
| | | color: $u-main-color; |
| | | flex: 1; |
| | | } |
| | | |
| | | &__textarea { |
| | | width: auto; |
| | | font-size: 28rpx; |
| | | color: $u-main-color; |
| | | padding: 10rpx 0; |
| | | line-height: normal; |
| | | flex: 1; |
| | | } |
| | | |
| | | &--border { |
| | | border-radius: 6rpx; |
| | | border-radius: 4px; |
| | | border: 1px solid $u-form-item-border-color; |
| | | } |
| | | |
| | | &--error { |
| | | border-color: $u-type-error!important; |
| | | } |
| | | |
| | | &__right-icon { |
| | | |
| | | &__item { |
| | | margin-left: 10rpx; |
| | | } |
| | | |
| | | &--select { |
| | | transition: transform .4s; |
| | | |
| | | &--reverse { |
| | | transform: rotate(-180deg); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <u-popup class="" :mask="mask" :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" |
| | | :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :zIndex="uZIndex"> |
| | | <slot /> |
| | | <view class="u-tooltip" v-if="tooltip"> |
| | | <view class="u-tooltip-item u-tooltip-cancel" hover-class="u-tooltip-cancel-hover" @tap="onCancel"> |
| | | {{cancelBtn ? cancelText : ''}} |
| | | </view> |
| | | <view v-if="showTips" class="u-tooltip-item u-tooltip-tips"> |
| | | {{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}} |
| | | </view> |
| | | <view v-if="confirmBtn" @tap="onConfirm" class="u-tooltip-item u-tooltips-submit" hover-class="u-tooltips-submit-hover"> |
| | | {{confirmBtn ? confirmText : ''}} |
| | | </view> |
| | | </view> |
| | | <block v-if="mode == 'number' || mode == 'card'"> |
| | | <u-number-keyboard :random="random" @backspace="backspace" @change="change" :mode="mode" :dotEnabled="dotEnabled"></u-number-keyboard> |
| | | </block> |
| | | <block v-else> |
| | | <u-car-keyboard :random="random" @backspace="backspace" @change="change"></u-car-keyboard> |
| | | </block> |
| | | </u-popup> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * keyboard 键盘 |
| | | * @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。 |
| | | * @tutorial https://www.uviewui.com/components/keyboard.html |
| | | * @property {String} mode 键盘类型,见官网基本使用的说明(默认number) |
| | | * @property {Boolean} dot-enabled 是否显示"."按键,只在mode=number时有效(默认true) |
| | | * @property {Boolean} tooltip 是否显示键盘顶部工具条(默认true) |
| | | * @property {String} tips 工具条中间的提示文字,见上方基本使用的说明,如不需要,请传""空字符 |
| | | * @property {Boolean} cancel-btn 是否显示工具条左边的"取消"按钮(默认true) |
| | | * @property {Boolean} confirm-btn 是否显示工具条右边的"完成"按钮(默认true) |
| | | * @property {Boolean} mask 是否显示遮罩(默认true) |
| | | * @property {String} confirm-text 确认按钮的文字 |
| | | * @property {String} cancel-text 取消按钮的文字 |
| | | * @property {Number String} z-index 弹出键盘的z-index值(默认1075) |
| | | * @property {Boolean} random 是否打乱键盘按键的顺序(默认false) |
| | | * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false) |
| | | * @property {Boolean} mask-close-able 是否允许点击遮罩收起键盘(默认true) |
| | | * @event {Function} change 按键被点击(不包含退格键被点击) |
| | | * @event {Function} cancel 键盘顶部工具条左边的"取消"按钮被点击 |
| | | * @event {Function} confirm 键盘顶部工具条右边的"完成"按钮被点击 |
| | | * @event {Function} backspace 键盘退格键被点击 |
| | | * @example <u-keyboard mode="number" v-model="show"></u-keyboard> |
| | | */ |
| | | export default { |
| | | name: "u-keyboard", |
| | | props: { |
| | | // 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘 |
| | | mode: { |
| | | type: String, |
| | | default: 'number' |
| | | }, |
| | | // 是否显示键盘的"."符号 |
| | | dotEnabled: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示顶部工具条 |
| | | tooltip: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示工具条中间的提示 |
| | | showTips: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 工具条中间的提示文字 |
| | | tips: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 是否显示工具条左边的"取消"按钮 |
| | | cancelBtn: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示工具条右边的"完成"按钮 |
| | | confirmBtn: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否打乱键盘按键的顺序 |
| | | random: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 |
| | | safeAreaInsetBottom: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否允许通过点击遮罩关闭键盘 |
| | | maskCloseAble: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 通过双向绑定控制键盘的弹出与收起 |
| | | value: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩 |
| | | mask: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // z-index值 |
| | | zIndex: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 取消按钮的文字 |
| | | cancelText: { |
| | | type: String, |
| | | default: '取消' |
| | | }, |
| | | // 确认按钮的文字 |
| | | confirmText: { |
| | | type: String, |
| | | default: '确认' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | //show: false |
| | | } |
| | | }, |
| | | computed: { |
| | | uZIndex() { |
| | | return this.zIndex ? this.zIndex : this.$u.zIndex.popup; |
| | | } |
| | | }, |
| | | methods: { |
| | | change(e) { |
| | | this.$emit('change', e); |
| | | }, |
| | | // 键盘关闭 |
| | | popupClose() { |
| | | // 通过发送input这个特殊的事件名,可以修改父组件传给props的value的变量,也即双向绑定 |
| | | this.$emit('input', false); |
| | | }, |
| | | // 输入完成 |
| | | onConfirm() { |
| | | this.popupClose(); |
| | | this.$emit('confirm'); |
| | | }, |
| | | // 取消输入 |
| | | onCancel() { |
| | | this.popupClose(); |
| | | this.$emit('cancel'); |
| | | }, |
| | | // 退格键 |
| | | backspace() { |
| | | this.$emit('backspace'); |
| | | }, |
| | | // 关闭键盘 |
| | | // close() { |
| | | // this.show = false; |
| | | // }, |
| | | // // 打开键盘 |
| | | // open() { |
| | | // this.show = true; |
| | | // } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-keyboard { |
| | | position: relative; |
| | | z-index: 1003; |
| | | } |
| | | |
| | | .u-tooltip { |
| | | @include vue-flex; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .u-tooltip-item { |
| | | color: #333333; |
| | | flex: 0 0 33.333333%; |
| | | text-align: center; |
| | | padding: 20rpx 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .u-tooltips-submit { |
| | | text-align: right; |
| | | flex-grow: 1; |
| | | flex-wrap: 0; |
| | | padding-right: 40rpx; |
| | | color: $u-type-primary; |
| | | } |
| | | |
| | | .u-tooltip-cancel { |
| | | text-align: left; |
| | | flex-grow: 1; |
| | | flex-wrap: 0; |
| | | padding-left: 40rpx; |
| | | color: #888888; |
| | | } |
| | | |
| | | .u-tooltips-submit-hover { |
| | | color: $u-type-success; |
| | | } |
| | | |
| | | .u-tooltip-cancel-hover { |
| | | color: #333333; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-wrap" :style="{ |
| | | opacity: Number(opacity), |
| | | borderRadius: borderRadius + 'rpx', |
| | | // 因为time值需要改变,所以不直接用duration值(不能改变父组件prop传过来的值) |
| | | transition: `opacity ${time / 1000}s ease-in-out` |
| | | }" |
| | | :class="'u-lazy-item-' + elIndex"> |
| | | <view :class="'u-lazy-item-' + elIndex"> |
| | | <image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" v-if="!isError" class="u-lazy-item" |
| | | :src="isShow ? image : loadingImg" :mode="imgMode" @load="imgLoaded" @error="loadError" @tap="clickImg"></image> |
| | | <image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" class="u-lazy-item error" v-else :src="errorImg" |
| | | :mode="imgMode" @load="errorImgLoaded" @tap="clickImg"></image> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * lazyLoad 懒加载 |
| | | * @description 懒加载使用的场景为:页面有很多图片时,APP会同时加载所有的图片,导致页面卡顿,各个位置的图片出现前后不一致等. |
| | | * @tutorial https://www.uviewui.com/components/lazyLoad.html |
| | | * @property {String Number} index 用户自定义值,在事件触发时回调,用以区分是哪个图片 |
| | | * @property {String} image 图片路径 |
| | | * @property {String} loading-img 预加载时的占位图 |
| | | * @property {String} error-img 图片加载出错时的占位图 |
| | | * @property {String} threshold 触发加载时的位置,见上方说明,单位 rpx(默认300) |
| | | * @property {String Number} duration 图片加载成功时,淡入淡出时间,单位ms(默认) |
| | | * @property {String} effect 图片加载成功时,淡入淡出的css动画效果(默认ease-in-out) |
| | | * @property {Boolean} is-effect 图片加载成功时,是否启用淡入淡出效果(默认true) |
| | | * @property {String Number} border-radius 图片圆角值,单位rpx(默认0) |
| | | * @property {String Number} height 图片高度,注意:实际高度可能受img-mode参数影响(默认450) |
| | | * @property {String Number} mg-mode 图片的裁剪模式,详见image组件裁剪模式(默认widthFix) |
| | | * @event {Function} click 点击图片时触发 |
| | | * @event {Function} load 图片加载成功时触发 |
| | | * @event {Function} error 图片加载失败时触发 |
| | | * @example <u-lazy-load :image="image" :loading-img="loadingImg" :error-img="errorImg"></u-lazy-load> |
| | | */ |
| | | export default { |
| | | name: 'u-lazy-load', |
| | | props: { |
| | | index: { |
| | | type: [Number, String] |
| | | }, |
| | | // 要显示的图片 |
| | | image: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 图片裁剪模式 |
| | | imgMode: { |
| | | type: String, |
| | | default: 'widthFix' |
| | | }, |
| | | // 占位图片路径 |
| | | loadingImg: { |
| | | type: String, |
| | | default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM0QjNBQjkyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM0QjNBQkEyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzRCM0FCNzJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzRCM0FCODJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtRHfPcAAAAzUExURZWVldfX18PDw62trZubm9zc3Li4uKGhoebm5tLS0uHh4c3Nzaenp729vcjIyLKysuvr6141L40AAAcXSURBVHja7NzZlqpGAEBR5lG0//9rIw7IJKJi4or7PGTdtN10wr5SVAEGf/qqArsAiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAg+nmQFMi5Jis+sIniED23jSzIgLTtg2D//iYme/8QBM/9lQ+CAEhbNLM3N9hEHAThX7GPCiBfAxK1b51kD+R7QMLjXg7iCsgWIPUh7pfVozG791oeBPngm48G583uW5GkBvI+SBaM2xXDn1oqum423bX/mgF5FySc2cv93Voug9TdZotsggnkBZB2NzbhrSY5HnoG07jei8dvzsJB/c3W60SALILE46+WCztsbhPR7R2VJq0ukEcT49nyy8QhaKcRa3fYHZD4+ufqOJAcgDz8/59vtw1I3QP5K6JsOG0vm3hce4I8LQp/BaRZGJC3AAn7IKOKXbC+7EdA5vdmmVwOLksgRThqOqiH4XEGsht+peoPUE8U/jJIO5OLH4GEwUslV5G0PTBG5Uiw/Y2jyigO3l9HAHKv9PYb82LloH74dZBoBUgar+l48NsNvtD0fkez9iwrAvIYZDRCl+Xs149Hm/KZmQ+QjUCiO1ei4ru7EsgnQYrkznlQb7thCuRfAzlOAPN72427P4VA/i2Q/DKT/Ls/VR8fvIBsDZIuz7TPF6TCbnk4GJkB2RokejTjuE7/unlgCuSTIO0Cy+Plp6vDfnQlBchy8QtjSHVd3EgmK1bHLm+H6+nXYbz2DuQRSPnqoL7vvq0u70on4zvxgCyWD3b9UyDVdW24PaWaiGTnFZJwPIQAebDpIKheBIm7n124ZthMJipAlkqHO+IZkP1tbfzOJark/A7MgKyvvl60fRqkvXfhuow+t9+q00+0/yyBrK8ZngOtBzldhw2X9tvpNGty0gvkmbPeJ0Cy/r09s/stbmfo0yMWkEdjevgKyOn2t2pxv7UXoibTdCDLje9/Ww1ymqzn87dbp92242ZmMRjI8hASvwKSLq4udqN6ksw8nxXN3tszD9L8Gkg+2mFrQYql5az4tvFj5xOx4VwnSdeBtGdyPwUytxK77pBVlNHdO7OK3rh/eTPUvdutT3fO52tuHMqD4N7llv8pyOQQ//w19YVDfX27+Sfuby9/6nau4pdA8vEdOZuChEH/quHt0Jg+IRJ/5+PrHwKZXfjbDiS73Zo7mu5UkzX7uTsXe0e/7nC3ePf1O69+BUg2XDfZCqSqOu7rGVf8cHBe8zhC2b61dtUHXv0OkGo6ZL4JkpbRYXdUaFevivx2M/1GIOctNh949TtAoumQ+TpIHMX54CJu+8BDd8FkE5BqcZh/59XvAClmTvKfB0nDqIlHo3T70SftyW1eX9dXtgQJqs1f/Q6QaOa/7wmQKtxH8eiGoCRuovODIO3VxOMmruZbHrLyD7z6DSDtGyT7ew1kf9hNn07c986JTovzzem0Id9wUG+Vk/IDr34DSNR7huZJkMFT6vEhqrPx/j5cnlZML8N6/PAzh9Y99Flm5Yde/c9BquDOkvkKkMP58dA4qi9vivE8JOvGz/j8FokfPpr288+pH2ZPOZrLmeGD+7KOh6dqYWJ48ki7yUg0tz0go/fv/LLddfV3sgOLJyaGPY/zrSlh1a36Arkzoue9CyG35ze6E6/dzO2Ga0EGHqdRJIkfn9/8OEjTW8Vq91ZWh39FeehWA7Nu9ft8CpUEk1WWOyDF0OPyEU2Pnzf/bZC0P6IPzmAvu7KauQBVrgKpJ0tG2arHzX8e5Pb3PezNs/PrX+3JMyCLn9XXf37tPFHvt09WfCDDjx+yyn1/p1V11j7GnB/q3leLuVva79S/tzed+db08YpF4uOZtmz/9oXWMq6BCAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiAALELvqt/BBgACqVeUBXxcCkAAAAASUVORK5CYII=' |
| | | }, |
| | | // 加载失败的错误占位图 |
| | | errorImg: { |
| | | type: String, |
| | | default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdDMjhENDYyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdDMjhENDcyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0MyOEQ0NDJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0MyOEQ0NTJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhLwhikAAAAzUExURZWVldfX162trcPDw5ubm7i4uNzc3Obm5s3NzaGhoeHh4cjIyKenp9LS0r29vbKysuvr67sDMEkAAAlpSURBVHja7NzpYqMgAIVRUVHc8/5PO66R1WAbOzX97q+ZtDEpR0AWTR7kVyWhCAAhgABCAAGEAAIIAQQQAggBBBACCCAEEEAIIIAQQAgggBBAACGAAEIAAYQAQgABhAACCAEEEAIIIAQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAsqeX5QWHKIcs/Ptl03lfL4zDFPWfBGmSpPn+IZzSH5KkCL5B+n+oklwz6Iz//R2QzFOabzhEmiRirAmZt/bl0w/dpMbLqeeo4wEdpC7zR5WAPKziHKtO7ql+ReKvIa9BxgNaL5ZtEkpeAGIVp5jKJa09xVo9vgSSzQcszdYvmOqjQNSQ6pHK6rO1n1Xj32788miwHLaZz1Tl9i/yayDlYJ/60/+lp8GSY7OY1B8E4p55bWmfquFk22GLuUUxi78cX+m+BjL2GLkhMrV+/muS6Sfic0CEp5T1Yu2OQdTzsKV0MJV73KVjroyTffxfuv5Tf3fd6iLT9wz8YdVHgUzF2Is9/Xhi5sYJqP1w/GUpjOiHVbaI0w2L+pg3GZzvtokcgHxWDXHaiy78l3sPke01qphamT5c+dqyeAGSumdL/mkggauTam0e3L/mPEiqtzKDbl0Z1Wn8xOa4ySo8X/7TQIJnY/seEKWf12UmC72CKP9xYjr19RPT7NNA+oMO+R0gwmlotAry+C6I0f59ch8yXVQOr0BKYcXt1IUYRyCt+Ur9HGsrQKI79WY9sY9ARPKlzFOFdb41ioD8b5Bp+mqeeRKAxINkESBFGpOpKhgv9OuYpH8A8l4Qa3qp60Kl2/k+rG2sWafuuyCBafb2j4JkgZUob3nWcmicpkxEgmTLLGejTxnWSWCi8lPmsk6DlIHFJv24ojiYyYoGacwL8zXTLEAVaDI/Ybb3NIgKDSv2oXpmHkvNs+PTpMASEdlk7fOZeRk37fwJ6yGnQarQsGIfqqcvx43rTOXY6jf7uKXdRzdLDRPbjIrx1cIj3Kr4KyBFezzgUGuR5893qkOQ19fR2uVBaU+r16LphJNOiatK7PeBZK/Kb+tUn71rcQjSvARpghfH/yG/D2RetTuI3N5QrMWdP46brP7FmKZ//CGQ9At9SL01DLkzY/Vs8Z97fQZ7gelw7jHqCz+/Wile5J4g3Vc79eb5a6oLSue+Ve83gaSv2jp5PxCzjzwFUm9zw9MllSMil1kS4d2E9SaQ1xNo9wMxx0+nQNLnew/WDHvveMAHYm08mofl3TFI/8pD3Q6kMAv6DIi2jTCwRJUvNdDYrrJum9oHhusCbWALonwxBRk1vXMnEGWuT5wAmfYuVGUYpJ7fUZujCN92hvzwWlrFgxSfANKb10DxIMbShnfrynyZZV30imA7P43ArXXHbvBVkTCIuGy25AdBrHmNeBCpL214QdLp9LZarG3IMWrmW0ehtuO7F2PS09UcgqS3B7FKPhpknrStD0HGF/vQRne37LwLG8EbHT4WxN7/Fg0yD9Yr/3br4nnstA+0Il6QxzdBmg8A6a2/IRbkcK9h/uzV8zywF/oSkOyageCPglRWgcWClHnEzs9q/t/SENVXgFijlsq3VtXdCsRp4qObrLLLgjuzSq3fX89ZZW6AfxNIzF6X9FYgThN/fk093KkvHX/hbWd+DqS/FUhlf+G3gohEXzVs3g9iDluWoaW8fL73QhB34u+tIHIf19nLuF4Q98a09Eynnl56q+ePgEhnX+dbQOp6H5XnJ0ACd8dFgkwf12nTOTcEqd2pom+CFF02TIPw6dKmrLS5qOtBpo8b5quUtrwrSGbuqPkeSJqllTFHO02NPxdMrm+y5LKdWyWXjw4vA5nGEtnjuyCFyHqNYvEolzmASm3zK1Eg5zr13lhqV1tlksnVw8Pkwgri7O07AVKLJkutRYw87bPlRpBpNXE8xGb+fhBlvEGrGPLqViu5sILIx9dAmqF1705sxF4M8+R8P5dOdQwi12fMnATpjJ2JSt/POIvU9wPJEs/jduJAjLvU0yFT0i64Yb1bsVi79dA4pEy3TzoHMq2O7Re4vXm5O9+l290NpE4CU+YRIMNye2iaqbVS2AUnn2fsekthYKReVNutVedA5juttyIXrT38mOds+ps9DWhwL7GWc61/DVKPzVN9UHDarf1icU98IOU8tm6L031Iq63t1tKzj3fe/FCpO4F0/i0Z2+yvA1KeGBjqj1qYx8/zoxpKZ1Yl367I1k+sfcft/QPy9csXy/32qX1qLZsrryG5BGQaRj0vc/b7N54XXq293TCLB5HO42Fy517obW19b+qjl3CHp0fdLJcWvmdy1etESi/uAdJrs1hTaUklHuW8qSDdC3UfXVR5cnD3rAFSSqtFb7z7eapErx7rC739jCXfbK3aWiipjXo8UbmxXPa7QQq9R289j2Gr88N7Ag5AlHPRKc37pNZv0CZtX1tVMG6rm8qW1/KlCgQvcMss933ybwXZz3dReW5yce4ByZtHFIhwT9kmjxg8BzbKDUe1PB9edBJqSN7/KM1LmqyuMZ5BpeTUw1aD/uDI0relPfSHa/Wn8Pxq1BNfxy/h3IdwOJqIKumb9CHvTqMefyY82RoQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAGEAAIIAQQQAgggBBBACCAEEEAIIIAQQAAhgABCACGAAEIAAYQAAggBBBACCAEEEAIIIAQQQAggfyL/BBgA8PgLdH0TBtkAAAAASUVORK5CYII=' |
| | | }, |
| | | // 图片进入可见区域前多少像素时,单位rpx,开始加载图片 |
| | | // 负数为图片超出屏幕底部多少距离后触发懒加载,正数为图片顶部距离屏幕底部多少距离时触发(图片还没出现在屏幕上) |
| | | threshold: { |
| | | type: [Number, String], |
| | | default: 100 |
| | | }, |
| | | // 淡入淡出动画的过渡时间 |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 500 |
| | | }, |
| | | // 渡效果的速度曲线,各个之间差别不大,因为这是淡入淡出,且时间很短,不是那些变形或者移动的情况,会明显 |
| | | // linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n); |
| | | effect: { |
| | | type: String, |
| | | default: 'ease-in-out' |
| | | }, |
| | | // 是否使用过渡效果 |
| | | isEffect: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 圆角值 |
| | | borderRadius: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 图片高度,单位rpx |
| | | height: { |
| | | type: [Number, String], |
| | | default: '450' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | isShow: false, |
| | | opacity: 1, |
| | | time: this.duration, |
| | | loadStatus: '', // 默认是懒加载中的状态 |
| | | isError: false, // 图片加载失败 |
| | | elIndex: this.$u.guid() |
| | | } |
| | | }, |
| | | computed: { |
| | | // 将threshold从rpx转为px |
| | | getThreshold() { |
| | | // 先取绝对值,因为threshold可能是负数,最后根据this.threshold是正数或者负数,重新还原 |
| | | let thresholdPx = uni.upx2px(Math.abs(this.threshold)); |
| | | return this.threshold < 0 ? -thresholdPx : thresholdPx; |
| | | }, |
| | | // 计算图片的高度,可能为auto,带%,或者直接数值 |
| | | imgHeight() { |
| | | return this.$u.addUnit(this.height); |
| | | } |
| | | }, |
| | | created() { |
| | | // 由于一些特殊原因,不能将此变量放到data中定义 |
| | | this.observer = {}; |
| | | }, |
| | | watch: { |
| | | isShow(nVal) { |
| | | // 如果是不开启过渡效果,直接返回 |
| | | if (!this.isEffect) return; |
| | | this.time = 0; |
| | | // 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的白色),再改成1,是为了获得过渡效果 |
| | | this.opacity = 0; |
| | | // 延时30ms,否则在浏览器H5,过渡效果无效 |
| | | setTimeout(() => { |
| | | this.time = this.duration; |
| | | this.opacity = 1; |
| | | }, 30) |
| | | }, |
| | | // 图片路径发生变化时,需要重新标记一些变量,否则会一直卡在某一个状态,比如isError |
| | | image(n) { |
| | | if(!n) { |
| | | // 如果传入null或者'',或者undefined,标记为错误状态 |
| | | this.isError = true; |
| | | } else { |
| | | this.init(); |
| | | this.isError = false; |
| | | } |
| | | } |
| | | }, |
| | | methods: { |
| | | // 用于重新初始化 |
| | | init() { |
| | | this.isError = false; |
| | | this.loadStatus = ''; |
| | | }, |
| | | // 点击图片触发的事件,loadlazy-还是懒加载中状态,loading-图片正在加载,loaded-图片加加载完成 |
| | | clickImg() { |
| | | let whichImg = ''; |
| | | // 如果isShow为false,意味着图片还没开始加载,点击的只能是最开始的占位图 |
| | | if (this.isShow == false) whichImg = 'lazyImg'; |
| | | // 如果isError为true,意味着图片加载失败,这是只剩下错误的占位图,所以点击的只能是错误占位图 |
| | | // 当然,也可以给错误的占位图元素绑定点击事件,看你喜欢~ |
| | | else if (this.isError == true) whichImg = 'errorImg'; |
| | | // 总共三张图片,除了两个占位图,剩下的只能是正常的那张图片了 |
| | | else whichImg = 'realImg'; |
| | | // 只通知当前图片的index |
| | | this.$emit('click', this.index); |
| | | }, |
| | | // 图片加载完成事件,可能是加载占位图时触发,也可能是加载真正的图片完成时触发,通过isShow区分 |
| | | imgLoaded() { |
| | | // 占位图加载完成 |
| | | if (this.loadStatus == '') { |
| | | this.loadStatus = 'lazyed'; |
| | | } |
| | | // 真正的图片加载完成 |
| | | else if (this.loadStatus == 'lazyed') { |
| | | this.loadStatus = 'loaded'; |
| | | this.$emit('load', this.index); |
| | | } |
| | | }, |
| | | // 错误的图片加载完成 |
| | | errorImgLoaded() { |
| | | this.$emit('error', this.index); |
| | | }, |
| | | // 图片加载失败 |
| | | loadError() { |
| | | this.isError = true; |
| | | }, |
| | | disconnectObserver(observerName) { |
| | | const observer = this[observerName]; |
| | | observer && observer.disconnect(); |
| | | }, |
| | | }, |
| | | beforeDestroy() { |
| | | // 销毁页面时,可能还没触发某张很底部的懒加载图片,所以把这个事件给去掉 |
| | | //observer.disconnect(); |
| | | }, |
| | | mounted() { |
| | | // 此uOnReachBottom事件由mixin.js发出,目的是让页面到底时,保证所有图片都进行加载,做到绝对稳定且可靠 |
| | | this.$nextTick(() => { |
| | | uni.$once('uOnReachBottom', () => { |
| | | if (!this.isShow) this.isShow = true; |
| | | }); |
| | | }) |
| | | // mounted的时候,不一定挂载了这个元素,延时30ms,否则会报错或者不报错,但是也没有效果 |
| | | setTimeout(() => { |
| | | // 这里是组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver |
| | | this.disconnectObserver('contentObserver'); |
| | | const contentObserver = uni.createIntersectionObserver(this); |
| | | // 要理解这里怎么计算的,请看这个: |
| | | // https://blog.csdn.net/qq_25324335/article/details/83687695 |
| | | contentObserver.relativeToViewport({ |
| | | bottom: this.getThreshold, |
| | | }).observe('.u-lazy-item-' + this.elIndex, (res) => { |
| | | if (res.intersectionRatio > 0) { |
| | | // 懒加载状态改变 |
| | | this.isShow = true; |
| | | // 如果图片已经加载,去掉监听,减少性能的消耗 |
| | | this.disconnectObserver('contentObserver'); |
| | | } |
| | | }) |
| | | this.contentObserver = contentObserver; |
| | | }, 30) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-wrap { |
| | | background-color: #eee; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-lazy-item { |
| | | width: 100%; |
| | | // 骗系统开启硬件加速 |
| | | transform: transition3d(0, 0, 0); |
| | | // 防止图片加载“闪一下” |
| | | will-change: transform; |
| | | /* #ifndef APP-NVUE */ |
| | | display: block; |
| | | /* #endif */ |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-progress" :style="{ |
| | | borderRadius: round ? '100rpx' : 0, |
| | | height: height + 'rpx', |
| | | backgroundColor: inactiveColor |
| | | }"> |
| | | <view :class="[ |
| | | type ? `u-type-${type}-bg` : '', |
| | | striped ? 'u-striped' : '', |
| | | striped && stripedActive ? 'u-striped-active' : '' |
| | | ]" class="u-active" :style="[progressStyle]"> |
| | | <slot v-if="$slots.default || $slots.$default" /> |
| | | <block v-else-if="showPercent"> |
| | | {{percent + '%'}} |
| | | </block> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * lineProgress 线型进度条 |
| | | * @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。 |
| | | * @tutorial https://www.uviewui.com/components/lineProgress.html |
| | | * @property {String Number} percent 进度条百分比值,为数值类型,0-100 |
| | | * @property {Boolean} round 进度条两端是否为半圆(默认true) |
| | | * @property {String} type 如设置,active-color值将会失效 |
| | | * @property {String} active-color 进度条激活部分的颜色(默认#19be6b) |
| | | * @property {String} inactive-color 进度条的底色(默认#ececec) |
| | | * @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值(默认true) |
| | | * @property {String Number} height 进度条的高度,单位rpx(默认28) |
| | | * @property {Boolean} striped 是否显示进度条激活部分的条纹(默认false) |
| | | * @property {Boolean} striped-active 条纹是否具有动态效果(默认false) |
| | | * @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress> |
| | | */ |
| | | export default { |
| | | name: "u-line-progress", |
| | | props: { |
| | | // 两端是否显示半圆形 |
| | | round: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 主题颜色 |
| | | type: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 激活部分的颜色 |
| | | activeColor: { |
| | | type: String, |
| | | default: '#19be6b' |
| | | }, |
| | | inactiveColor: { |
| | | type: String, |
| | | default: '#ececec' |
| | | }, |
| | | // 进度百分比,数值 |
| | | percent: { |
| | | type: Number, |
| | | default: 0 |
| | | }, |
| | | // 是否在进度条内部显示百分比的值 |
| | | showPercent: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 进度条的高度,单位rpx |
| | | height: { |
| | | type: [Number, String], |
| | | default: 28 |
| | | }, |
| | | // 是否显示条纹 |
| | | striped: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 条纹是否显示活动状态 |
| | | stripedActive: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | |
| | | } |
| | | }, |
| | | computed: { |
| | | progressStyle() { |
| | | let style = {}; |
| | | style.width = this.percent + '%'; |
| | | if(this.activeColor) style.backgroundColor = this.activeColor; |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-progress { |
| | | overflow: hidden; |
| | | height: 15px; |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | width: 100%; |
| | | border-radius: 100rpx; |
| | | } |
| | | |
| | | .u-active { |
| | | width: 0; |
| | | height: 100%; |
| | | align-items: center; |
| | | @include vue-flex; |
| | | justify-items: flex-end; |
| | | justify-content: space-around; |
| | | font-size: 20rpx; |
| | | color: #ffffff; |
| | | transition: all 0.4s ease; |
| | | } |
| | | |
| | | .u-striped { |
| | | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); |
| | | background-size: 39px 39px; |
| | | } |
| | | |
| | | .u-striped-active { |
| | | animation: progress-stripes 2s linear infinite; |
| | | } |
| | | |
| | | @keyframes progress-stripes { |
| | | 0% { |
| | | background-position: 0 0; |
| | | } |
| | | |
| | | 100% { |
| | | background-position: 39px 0; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-line" :style="[lineStyle]"> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * line 线条 |
| | | * @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单 |
| | | * @tutorial https://www.uviewui.com/components/line.html |
| | | * @property {String} color 线条的颜色(默认#e4e7ed) |
| | | * @property {String} length 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带rpx单位的值等 |
| | | * @property {String} direction 线条的方向,row-横向,col-竖向(默认row) |
| | | * @property {String} border-style 线条的类型,solid-实线,dashed-方形虚线,dotted-圆点虚线(默认solid) |
| | | * @property {Boolean} hair-line 是否显示细线条(默认true) |
| | | * @property {String} margin 线条与上下左右元素的间距,字符串形式,如"30rpx" |
| | | * @example <u-line color="red"></u-line> |
| | | */ |
| | | export default { |
| | | name: 'u-line', |
| | | props: { |
| | | color: { |
| | | type: String, |
| | | default: '#e4e7ed' |
| | | }, |
| | | // 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带rpx单位的值等 |
| | | length: { |
| | | type: String, |
| | | default: '100%' |
| | | }, |
| | | // 线条方向,col-竖向,row-横向 |
| | | direction: { |
| | | type: String, |
| | | default: 'row' |
| | | }, |
| | | // 是否显示细边框 |
| | | hairLine: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 线条与上下左右元素的间距,字符串形式,如"30rpx"、"20rpx 30rpx" |
| | | margin: { |
| | | type: String, |
| | | default: '0' |
| | | }, |
| | | // 线条的类型,solid-实线,dashed-方形虚线,dotted-圆点虚线 |
| | | borderStyle: { |
| | | type: String, |
| | | default: 'solid' |
| | | } |
| | | }, |
| | | computed: { |
| | | lineStyle() { |
| | | let style = {}; |
| | | style.margin = this.margin; |
| | | // 如果是水平线条,边框高度为1px,再通过transform缩小一半,就是0.5px了 |
| | | if(this.direction == 'row') { |
| | | // 此处采用兼容分开写,兼容nvue的写法 |
| | | style.borderBottomWidth = '1px'; |
| | | style.borderBottomStyle = this.borderStyle; |
| | | style.width = this.$u.addUnit(this.length); |
| | | if(this.hairLine) style.transform = 'scaleY(0.5)'; |
| | | } else { |
| | | // 如果是竖向线条,边框宽度为1px,再通过transform缩小一半,就是0.5px了 |
| | | style.borderLeftWidth = '1px'; |
| | | style.borderLeftStyle = this.borderStyle; |
| | | style.height = this.$u.addUnit(this.length); |
| | | if(this.hairLine) style.transform = 'scaleX(0.5)'; |
| | | } |
| | | style.borderColor = this.color; |
| | | return style; |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-line { |
| | | vertical-align: middle; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <text class="u-link" @tap.stop="openLink" :style="{ |
| | | color: color, |
| | | fontSize: fontSize + 'rpx', |
| | | borderBottom: underLine ? `1px solid ${lineColor ? lineColor : color}` : 'none', |
| | | paddingBottom: underLine ? '0rpx' : '0' |
| | | }"> |
| | | <slot></slot> |
| | | </text> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * link 超链接 |
| | | * @description 该组件为超链接组件,在不同平台有不同表现形式:在APP平台会通过plus环境打开内置浏览器,在小程序中把链接复制到粘贴板,同时提示信息,在H5中通过window.open打开链接。 |
| | | * @tutorial https://www.uviewui.com/components/link.html |
| | | * @property {String} color 文字颜色(默认#606266) |
| | | * @property {String Number} font-size 字体大小,单位rpx(默认28) |
| | | * @property {Boolean} under-line 是否显示下划线(默认false) |
| | | * @property {String} href 跳转的链接,要带上http(s) |
| | | * @property {String} line-color 下划线颜色,默认同color参数颜色 |
| | | * @property {String} mp-tips 各个小程序平台把链接复制到粘贴板后的提示语(默认“链接已复制,请在浏览器打开”) |
| | | * @example <u-link href="http://www.uviewui.com">蜀道难,难于上青天</u-link> |
| | | */ |
| | | export default { |
| | | name: "u-link", |
| | | props: { |
| | | // 文字颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | }, |
| | | // 字体大小,单位rpx |
| | | fontSize: { |
| | | type: [String, Number], |
| | | default: 28 |
| | | }, |
| | | // 是否显示下划线 |
| | | underLine: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 要跳转的链接 |
| | | href: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 小程序中复制到粘贴板的提示语 |
| | | mpTips: { |
| | | type: String, |
| | | default: '链接已复制,请在浏览器打开' |
| | | }, |
| | | // 下划线颜色 |
| | | lineColor: { |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }, |
| | | methods: { |
| | | openLink() { |
| | | // #ifdef APP-PLUS |
| | | plus.runtime.openURL(this.href) |
| | | // #endif |
| | | // #ifdef H5 |
| | | window.open(this.href) |
| | | // #endif |
| | | // #ifdef MP |
| | | uni.setClipboardData({ |
| | | data: this.href, |
| | | success: () => { |
| | | uni.hideToast(); |
| | | this.$nextTick(() => { |
| | | this.$u.toast(this.mpTips); |
| | | }) |
| | | } |
| | | }); |
| | | // #endif |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-link { |
| | | line-height: 1; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-loading-page"> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | props: { |
| | | |
| | | }, |
| | | data() { |
| | | return { |
| | | |
| | | } |
| | | }, |
| | | methods: { |
| | | |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view v-if="show" class="u-loading" :class="mode == 'circle' ? 'u-loading-circle' : 'u-loading-flower'" :style="[cricleStyle]"> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * loading 加载动画 |
| | | * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。 |
| | | * @tutorial https://www.uviewui.com/components/loading.html |
| | | * @property {String} mode 模式选择,见官网说明(默认circle) |
| | | * @property {String} color 动画活动区域的颜色,只对 mode = flower 模式有效(默认#c7c7c7) |
| | | * @property {String Number} size 加载图标的大小,单位rpx(默认34) |
| | | * @property {Boolean} show 是否显示动画(默认true) |
| | | * @example <u-loading mode="circle"></u-loading> |
| | | */ |
| | | export default { |
| | | name: "u-loading", |
| | | props: { |
| | | // 动画的类型 |
| | | mode: { |
| | | type: String, |
| | | default: 'circle' |
| | | }, |
| | | // 动画的颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#c7c7c7' |
| | | }, |
| | | // 加载图标的大小,单位rpx |
| | | size: { |
| | | type: [String, Number], |
| | | default: '34' |
| | | }, |
| | | // 是否显示动画 |
| | | show: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | computed: { |
| | | // 加载中圆圈动画的样式 |
| | | cricleStyle() { |
| | | let style = {}; |
| | | style.width = this.size + 'rpx'; |
| | | style.height = this.size + 'rpx'; |
| | | if (this.mode == 'circle') style.borderColor = `#e4e4e4 #e4e4e4 #e4e4e4 ${this.color ? this.color : '#c7c7c7'}`; |
| | | return style; |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-loading-circle { |
| | | /* #ifndef APP-NVUE */ |
| | | display: inline-flex; |
| | | /* #endif */ |
| | | vertical-align: middle; |
| | | width: 28rpx; |
| | | height: 28rpx; |
| | | background: 0 0; |
| | | border-radius: 50%; |
| | | border: 2px solid; |
| | | border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e; |
| | | animation: u-circle 1s linear infinite; |
| | | } |
| | | |
| | | .u-loading-flower { |
| | | width: 20px; |
| | | height: 20px; |
| | | display: inline-block; |
| | | vertical-align: middle; |
| | | -webkit-animation: a 1s steps(12) infinite; |
| | | animation: u-flower 1s steps(12) infinite; |
| | | background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat; |
| | | background-size: 100%; |
| | | } |
| | | |
| | | @keyframes u-flower { |
| | | 0% { |
| | | -webkit-transform: rotate(0deg); |
| | | transform: rotate(0deg); |
| | | } |
| | | |
| | | to { |
| | | -webkit-transform: rotate(1turn); |
| | | transform: rotate(1turn); |
| | | } |
| | | } |
| | | |
| | | @-webkit-keyframes u-circle { |
| | | 0% { |
| | | transform: rotate(0); |
| | | } |
| | | |
| | | 100% { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-load-more-wrap" :style="{ |
| | | backgroundColor: bgColor, |
| | | marginBottom: marginBottom + 'rpx', |
| | | marginTop: marginTop + 'rpx', |
| | | height: $u.addUnit(height) |
| | | }"> |
| | | <u-line color="#d4d4d4" length="50"></u-line> |
| | | <!-- 加载中和没有更多的状态才显示两边的横线 --> |
| | | <view :class="status == 'loadmore' || status == 'nomore' ? 'u-more' : ''" class="u-load-more-inner"> |
| | | <view class="u-loadmore-icon-wrap"> |
| | | <u-loading class="u-loadmore-icon" :color="iconColor" :mode="iconType == 'circle' ? 'circle' : 'flower'" :show="status == 'loading' && icon"></u-loading> |
| | | </view> |
| | | <!-- 如果没有更多的状态下,显示内容为dot(粗点),加载特定样式 --> |
| | | <view class="u-line-1" :style="[loadTextStyle]" :class="[(status == 'nomore' && isDot == true) ? 'u-dot-text' : 'u-more-text']" @tap="loadMore"> |
| | | {{ showText }} |
| | | </view> |
| | | </view> |
| | | <u-line color="#d4d4d4" length="50"></u-line> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * loadmore 加载更多 |
| | | * @description 此组件一般用于标识页面底部加载数据时的状态。 |
| | | * @tutorial https://www.uviewui.com/components/loadMore.html |
| | | * @property {String} status 组件状态(默认loadmore) |
| | | * @property {String} bg-color 组件背景颜色,在页面是非白色时会用到(默认#ffffff) |
| | | * @property {Boolean} icon 加载中时是否显示图标(默认true) |
| | | * @property {String} icon-type 加载中时的图标类型(默认circle) |
| | | * @property {String} icon-color icon-type为circle时有效,加载中的动画图标的颜色(默认#b7b7b7) |
| | | * @property {Boolean} is-dot status为nomore时,内容显示为一个"●"(默认false) |
| | | * @property {String} color 字体颜色(默认#606266) |
| | | * @property {String Number} margin-top 到上一个相邻元素的距离 |
| | | * @property {String Number} margin-bottom 到下一个相邻元素的距离 |
| | | * @property {Object} load-text 自定义显示的文字,见上方说明示例 |
| | | * @event {Function} loadmore status为loadmore时,点击组件会发出此事件 |
| | | * @example <u-loadmore :status="status" icon-type="iconType" load-text="loadText" /> |
| | | */ |
| | | export default { |
| | | name: "u-loadmore", |
| | | props: { |
| | | // 组件背景色 |
| | | bgColor: { |
| | | type: String, |
| | | default: 'transparent' |
| | | }, |
| | | // 是否显示加载中的图标 |
| | | icon: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 字体大小 |
| | | fontSize: { |
| | | type: String, |
| | | default: '28' |
| | | }, |
| | | // 字体颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // 组件状态,loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态 |
| | | status: { |
| | | type: String, |
| | | default: 'loadmore' |
| | | }, |
| | | // 加载中状态的图标,flower-花朵状图标,circle-圆圈状图标 |
| | | iconType: { |
| | | type: String, |
| | | default: 'circle' |
| | | }, |
| | | // 显示的文字 |
| | | loadText: { |
| | | type: Object, |
| | | default () { |
| | | return { |
| | | loadmore: '加载更多', |
| | | loading: '正在加载...', |
| | | nomore: '没有更多了' |
| | | } |
| | | } |
| | | }, |
| | | // 在“没有更多”状态下,是否显示粗点 |
| | | isDot: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 加载中显示圆圈动画时,动画的颜色 |
| | | iconColor: { |
| | | type: String, |
| | | default: '#b7b7b7' |
| | | }, |
| | | // 上边距 |
| | | marginTop: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 下边距 |
| | | marginBottom: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | }, |
| | | // 高度,单位rpx |
| | | height: { |
| | | type: [String, Number], |
| | | default: 'auto' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | // 粗点 |
| | | dotText: "●" |
| | | } |
| | | }, |
| | | computed: { |
| | | // 加载的文字显示的样式 |
| | | loadTextStyle() { |
| | | return { |
| | | color: this.color, |
| | | fontSize: this.fontSize + 'rpx', |
| | | position: 'relative', |
| | | zIndex: 1, |
| | | backgroundColor: this.bgColor, |
| | | // 如果是加载中状态,动画和文字需要距离近一点 |
| | | } |
| | | }, |
| | | // 加载中圆圈动画的样式 |
| | | cricleStyle() { |
| | | return { |
| | | borderColor: `#e5e5e5 #e5e5e5 #e5e5e5 ${this.circleColor}` |
| | | } |
| | | }, |
| | | // 加载中花朵动画形式 |
| | | // 动画由base64图片生成,暂不支持修改 |
| | | flowerStyle() { |
| | | return { |
| | | } |
| | | }, |
| | | // 显示的提示文字 |
| | | showText() { |
| | | let text = ''; |
| | | if(this.status == 'loadmore') text = this.loadText.loadmore; |
| | | else if(this.status == 'loading') text = this.loadText.loading; |
| | | else if(this.status == 'nomore' && this.isDot) text = this.dotText; |
| | | else text = this.loadText.nomore; |
| | | return text; |
| | | } |
| | | }, |
| | | methods: { |
| | | loadMore() { |
| | | // 只有在“加载更多”的状态下才发送点击事件,内容不满一屏时无法触发底部上拉事件,所以需要点击来触发 |
| | | if(this.status == 'loadmore') this.$emit('loadmore'); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | /* #ifdef MP */ |
| | | // 在mp.scss中,赋予了u-line为flex: 1,这里需要一个明确的长度,所以重置掉它 |
| | | // 在组件内部,把组件名(u-line)当做选择器,在微信开发工具会提示不合法,但不影响使用 |
| | | u-line { |
| | | flex: none; |
| | | } |
| | | /* #endif */ |
| | | |
| | | .u-load-more-wrap { |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-load-more-inner { |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | padding: 0 12rpx; |
| | | } |
| | | |
| | | .u-more { |
| | | position: relative; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .u-dot-text { |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .u-loadmore-icon-wrap { |
| | | margin-right: 8rpx; |
| | | } |
| | | |
| | | .u-loadmore-icon { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-mask" hover-stop-propagation :style="[maskStyle, zoomStyle]" @tap="click" @touchmove.stop.prevent="() => {}" :class="{ |
| | | 'u-mask-zoom': zoom, |
| | | 'u-mask-show': show |
| | | }"> |
| | | <slot /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * mask 遮罩 |
| | | * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景 |
| | | * @tutorial https://www.uviewui.com/components/mask.html |
| | | * @property {Boolean} show 是否显示遮罩(默认false) |
| | | * @property {String Number} z-index z-index 层级(默认1070) |
| | | * @property {Object} custom-style 自定义样式对象,见上方说明 |
| | | * @property {String Number} duration 动画时长,单位毫秒(默认300) |
| | | * @property {Boolean} zoom 是否使用scale对遮罩进行缩放(默认true) |
| | | * @property {Boolean} mask-click-able 遮罩是否可点击,为false时点击不会发送click事件(默认true) |
| | | * @event {Function} click mask-click-able为true时,点击遮罩发送此事件 |
| | | * @example <u-mask :show="show" @click="show = false"></u-mask> |
| | | */ |
| | | export default { |
| | | name: "u-mask", |
| | | props: { |
| | | // 是否显示遮罩 |
| | | show: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 层级z-index |
| | | zIndex: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 用户自定义样式 |
| | | customStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 遮罩的动画样式, 是否使用使用zoom进行scale进行缩放 |
| | | zoom: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 遮罩的过渡时间,单位为ms |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 300 |
| | | }, |
| | | // 是否可以通过点击遮罩进行关闭 |
| | | maskClickAble: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | zoomStyle: { |
| | | transform: '' |
| | | }, |
| | | scale: 'scale(1.2, 1.2)' |
| | | } |
| | | }, |
| | | watch: { |
| | | show(n) { |
| | | if(n && this.zoom) { |
| | | // 当展示遮罩的时候,设置scale为1,达到缩小(原来为1.2)的效果 |
| | | this.zoomStyle.transform = 'scale(1, 1)'; |
| | | } else if(!n && this.zoom) { |
| | | // 当隐藏遮罩的时候,设置scale为1.2,达到放大(因为显示遮罩时已重置为1)的效果 |
| | | this.zoomStyle.transform = this.scale; |
| | | } |
| | | } |
| | | }, |
| | | computed: { |
| | | maskStyle() { |
| | | let style = {}; |
| | | style.backgroundColor = "rgba(0, 0, 0, 0.6)"; |
| | | if(this.show) style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.mask; |
| | | else style.zIndex = -1; |
| | | style.transition = `all ${this.duration / 1000}s ease-in-out`; |
| | | // 判断用户传递的对象是否为空,不为空就进行合并 |
| | | if (Object.keys(this.customStyle).length) style = { |
| | | ...style, |
| | | ...this.customStyle |
| | | }; |
| | | return style; |
| | | } |
| | | }, |
| | | methods: { |
| | | click() { |
| | | if (!this.maskClickAble) return; |
| | | this.$emit('click'); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-mask { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | opacity: 0; |
| | | transition: transform 0.3s; |
| | | } |
| | | |
| | | .u-mask-show { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .u-mask-zoom { |
| | | transform: scale(1.2, 1.2); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-char-box"> |
| | | <view class="u-char-flex"> |
| | | <input :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxlength" class="u-input" @input="getVal"/> |
| | | <view v-for="(item, index) in loopCharArr" :key="index"> |
| | | <view :class="[breathe && charArrLength == index ? 'u-breathe' : '', 'u-char-item', |
| | | charArrLength === index && mode == 'box' ? 'u-box-active' : '', |
| | | mode === 'box' ? 'u-box' : '']" :style="{ |
| | | fontWeight: bold ? 'bold' : 'normal', |
| | | fontSize: fontSize + 'rpx', |
| | | width: width + 'rpx', |
| | | height: width + 'rpx', |
| | | color: inactiveColor, |
| | | borderColor: charArrLength === index && mode == 'box' ? activeColor : inactiveColor |
| | | }"> |
| | | <view class="u-placeholder-line" :style="{ |
| | | display: charArrLength === index ? 'block' : 'none', |
| | | height: width * 0.5 +'rpx' |
| | | }" |
| | | v-if="mode !== 'middleLine'" |
| | | ></view> |
| | | <view v-if="mode === 'middleLine' && charArrLength <= index" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-middle-line-active' : '']" |
| | | class="u-middle-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view> |
| | | <view v-if="mode === 'bottomLine'" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-buttom-line-active' : '']" |
| | | class="u-bottom-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view> |
| | | <block v-if="!dotFill"> {{ charArr[index] ? charArr[index] : ''}}</block> |
| | | <block v-else> |
| | | <text class="u-dot">{{ charArr[index] ? '●' : ''}}</text> |
| | | </block> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * messageInput 验证码输入框 |
| | | * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用 |
| | | * @tutorial https://www.uviewui.com/components/messageInput.html |
| | | * @property {String Number} maxlength 输入字符个数(默认4) |
| | | * @property {Boolean} dot-fill 是否用圆点填充(默认false) |
| | | * @property {String} mode 模式选择,见上方"基本使用"说明(默认box) |
| | | * @property {String Number} value 预置值 |
| | | * @property {Boolean} breathe 是否开启呼吸效果,见上方说明(默认true) |
| | | * @property {Boolean} focus 是否自动获取焦点(默认false) |
| | | * @property {Boolean} bold 字体和输入横线是否加粗(默认true) |
| | | * @property {String Number} font-size 字体大小,单位rpx(默认60) |
| | | * @property {String} active-color 当前激活输入框的样式(默认#2979ff) |
| | | * @property {String} inactive-color 非激活输入框的样式,文字颜色同此值(默认#606266) |
| | | * @property {String | Number} width 输入框宽度,单位rpx,高等于宽(默认80) |
| | | * @property {Boolean} disabled-keyboard 禁止点击输入框唤起系统键盘(默认false) |
| | | * @event {Function} change 输入内容发生改变时触发,具体见官网说明 |
| | | * @event {Function} finish 输入字符个数达maxlength值时触发,见官网说明 |
| | | * @example <u-message-input mode="bottomLine"></u-message-input> |
| | | */ |
| | | export default { |
| | | name: "u-message-input", |
| | | props: { |
| | | // 最大输入长度 |
| | | maxlength: { |
| | | type: [Number, String], |
| | | default: 4 |
| | | }, |
| | | // 是否用圆点填充 |
| | | dotFill: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 显示模式,box-盒子模式,bottomLine-横线在底部模式,middleLine-横线在中部模式 |
| | | mode: { |
| | | type: String, |
| | | default: "box" |
| | | }, |
| | | // 预置值 |
| | | value: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 当前激活输入item,是否带有呼吸效果 |
| | | breathe: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否自动获取焦点 |
| | | focus: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 字体是否加粗 |
| | | bold: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 字体大小 |
| | | fontSize: { |
| | | type: [String, Number], |
| | | default: 60 |
| | | }, |
| | | // 激活样式 |
| | | activeColor: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | }, |
| | | // 未激活的样式 |
| | | inactiveColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // 输入框的大小,单位rpx,宽等于高 |
| | | width: { |
| | | type: [Number, String], |
| | | default: '80' |
| | | }, |
| | | // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true |
| | | disabledKeyboard: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | watch: { |
| | | // maxlength: { |
| | | // // 此值设置为true,会在组件加载后无需maxlength变化就会执行一次本监听函数,无需再created生命周期中处理 |
| | | // immediate: true, |
| | | // handler(val) { |
| | | // this.maxlength = Number(val); |
| | | // } |
| | | // }, |
| | | value: { |
| | | immediate: true, |
| | | handler(val) { |
| | | // 转为字符串 |
| | | val = String(val); |
| | | // 超出部分截掉 |
| | | this.valueModel = val.substring(0, this.maxlength); |
| | | } |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | valueModel: "" |
| | | } |
| | | }, |
| | | computed: { |
| | | // 是否显示呼吸灯效果 |
| | | animationClass() { |
| | | return (index) => { |
| | | if (this.breathe && this.charArr.length == index) return 'u-breathe'; |
| | | else return ''; |
| | | } |
| | | }, |
| | | // 用于显示字符 |
| | | charArr() { |
| | | return this.valueModel.split(''); |
| | | }, |
| | | charArrLength() { |
| | | return this.charArr.length; |
| | | }, |
| | | // 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for |
| | | loopCharArr() { |
| | | return new Array(this.maxlength); |
| | | } |
| | | }, |
| | | methods: { |
| | | getVal(e) { |
| | | let { |
| | | value |
| | | } = e.detail |
| | | this.valueModel = value; |
| | | // 判断长度是否超出了maxlength值,理论上不会发生,因为input组件设置了maxlength属性值 |
| | | if (String(value).length > this.maxlength) return; |
| | | // 未达到maxlength之前,发送change事件,达到后发送finish事件 |
| | | this.$emit('change', value); |
| | | if (String(value).length == this.maxlength) { |
| | | this.$emit('finish', value); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | @keyframes breathe { |
| | | 0% { |
| | | opacity: 0.3; |
| | | } |
| | | |
| | | 50% { |
| | | opacity: 1; |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 0.3; |
| | | } |
| | | } |
| | | |
| | | .u-char-box { |
| | | text-align: center; |
| | | } |
| | | |
| | | .u-char-flex { |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | position: relative; |
| | | } |
| | | |
| | | .u-input { |
| | | position: absolute; |
| | | top: 0; |
| | | left: -100%; |
| | | width: 200%; |
| | | height: 100%; |
| | | text-align: left; |
| | | z-index: 9; |
| | | opacity: 0; |
| | | background: none; |
| | | } |
| | | |
| | | .u-char-item { |
| | | position: relative; |
| | | width: 90rpx; |
| | | height: 90rpx; |
| | | margin: 10rpx 10rpx; |
| | | font-size: 60rpx; |
| | | font-weight: bold; |
| | | color: $u-main-color; |
| | | line-height: 90rpx; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-middle-line { |
| | | border: none; |
| | | } |
| | | |
| | | .u-box { |
| | | box-sizing: border-box; |
| | | border: 2rpx solid #cccccc; |
| | | border-radius: 6rpx; |
| | | } |
| | | |
| | | .u-box-active { |
| | | overflow: hidden; |
| | | animation-timing-function: ease-in-out; |
| | | animation-duration: 1500ms; |
| | | animation-iteration-count: infinite; |
| | | animation-direction: alternate; |
| | | border: 2rpx solid $u-type-primary; |
| | | } |
| | | |
| | | .u-middle-line-active { |
| | | background: $u-type-primary; |
| | | } |
| | | |
| | | .u-breathe { |
| | | animation: breathe 2s infinite ease; |
| | | } |
| | | |
| | | .u-placeholder-line { |
| | | /* #ifndef APP-NVUE */ |
| | | display: none; |
| | | /* #endif */ |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 2rpx; |
| | | height: 40rpx; |
| | | background: #333333; |
| | | animation: twinkling 1.5s infinite ease; |
| | | } |
| | | |
| | | .u-animation-breathe { |
| | | animation-name: breathe; |
| | | } |
| | | |
| | | .u-dot { |
| | | font-size: 34rpx; |
| | | line-height: 34rpx; |
| | | } |
| | | |
| | | .u-middle-line { |
| | | height: 4px; |
| | | background: #000000; |
| | | width: 80%; |
| | | position: absolute; |
| | | border-radius: 2px; |
| | | top: 50%; |
| | | left: 50%; |
| | | transform: translate(-50%, -50%); |
| | | } |
| | | |
| | | .u-buttom-line-active { |
| | | background: $u-type-primary; |
| | | } |
| | | |
| | | .u-bottom-line { |
| | | height: 4px; |
| | | background: #000000; |
| | | width: 80%; |
| | | position: absolute; |
| | | border-radius: 2px; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translate(-50%); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view> |
| | | <u-popup :zoom="zoom" mode="center" :popup="false" :z-index="uZIndex" v-model="value" :length="width" |
| | | :mask-close-able="maskCloseAble" :border-radius="borderRadius" @close="popupClose" :negative-top="negativeTop"> |
| | | <view class="u-model"> |
| | | <view v-if="showTitle" class="u-model__title u-line-1" :style="[titleStyle]">{{ title }}</view> |
| | | <view class="u-model__content"> |
| | | <view :style="[contentStyle]" v-if="$slots.default || $slots.$default"> |
| | | <slot /> |
| | | </view> |
| | | <view v-else class="u-model__content__message" :style="[contentStyle]">{{ content }}</view> |
| | | </view> |
| | | <view class="u-model__footer u-border-top" v-if="showCancelButton || showConfirmButton"> |
| | | <view v-if="showCancelButton" :hover-stay-time="100" hover-class="u-model__btn--hover" class="u-model__footer__button" |
| | | :style="[cancelBtnStyle]" @tap="cancel"> |
| | | {{cancelText}} |
| | | </view> |
| | | <view v-if="showConfirmButton || $slots['confirm-button']" :hover-stay-time="100" :hover-class="asyncClose ? 'none' : 'u-model__btn--hover'" |
| | | class="u-model__footer__button hairline-left" :style="[confirmBtnStyle]" @tap="confirm"> |
| | | <slot v-if="$slots['confirm-button']" name="confirm-button"></slot> |
| | | <block v-else> |
| | | <u-loading mode="circle" :color="confirmColor" v-if="loading"></u-loading> |
| | | <block v-else> |
| | | {{confirmText}} |
| | | </block> |
| | | </block> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * modal 模态框 |
| | | * @description 弹出模态框,常用于消息提示、消息确认、在当前页面内完成特定的交互操作 |
| | | * @tutorial https://www.uviewui.com/components/modal.html |
| | | * @property {Boolean} value 是否显示模态框 |
| | | * @property {String | Number} z-index 层级 |
| | | * @property {String} title 模态框标题(默认"提示") |
| | | * @property {String | Number} width 模态框宽度(默认600) |
| | | * @property {String} content 模态框内容(默认"内容") |
| | | * @property {Boolean} show-title 是否显示标题(默认true) |
| | | * @property {Boolean} async-close 是否异步关闭,只对确定按钮有效(默认false) |
| | | * @property {Boolean} show-confirm-button 是否显示确认按钮(默认true) |
| | | * @property {Stringr | Number} negative-top modal往上偏移的值 |
| | | * @property {Boolean} show-cancel-button 是否显示取消按钮(默认false) |
| | | * @property {Boolean} mask-close-able 是否允许点击遮罩关闭modal(默认false) |
| | | * @property {String} confirm-text 确认按钮的文字内容(默认"确认") |
| | | * @property {String} cancel-text 取消按钮的文字内容(默认"取消") |
| | | * @property {String} cancel-color 取消按钮的颜色(默认"#606266") |
| | | * @property {String} confirm-color 确认按钮的文字内容(默认"#2979ff") |
| | | * @property {String | Number} border-radius 模态框圆角值,单位rpx(默认16) |
| | | * @property {Object} title-style 自定义标题样式,对象形式 |
| | | * @property {Object} content-style 自定义内容样式,对象形式 |
| | | * @property {Object} cancel-style 自定义取消按钮样式,对象形式 |
| | | * @property {Object} confirm-style 自定义确认按钮样式,对象形式 |
| | | * @property {Boolean} zoom 是否开启缩放模式(默认true) |
| | | * @event {Function} confirm 确认按钮被点击 |
| | | * @event {Function} cancel 取消按钮被点击 |
| | | * @example <u-modal :src="title" :content="content"></u-modal> |
| | | */ |
| | | export default { |
| | | name: 'u-modal', |
| | | props: { |
| | | // 是否显示Modal |
| | | value: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 层级z-index |
| | | zIndex: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 标题 |
| | | title: { |
| | | type: [String], |
| | | default: '提示' |
| | | }, |
| | | // 弹窗宽度,可以是数值(rpx),百分比,auto等 |
| | | width: { |
| | | type: [Number, String], |
| | | default: 600 |
| | | }, |
| | | // 弹窗内容 |
| | | content: { |
| | | type: String, |
| | | default: '内容' |
| | | }, |
| | | // 是否显示标题 |
| | | showTitle: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示确认按钮 |
| | | showConfirmButton: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否显示取消按钮 |
| | | showCancelButton: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 确认文案 |
| | | confirmText: { |
| | | type: String, |
| | | default: '确认' |
| | | }, |
| | | // 取消文案 |
| | | cancelText: { |
| | | type: String, |
| | | default: '取消' |
| | | }, |
| | | // 确认按钮颜色 |
| | | confirmColor: { |
| | | type: String, |
| | | default: '#2979ff' |
| | | }, |
| | | // 取消文字颜色 |
| | | cancelColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // 圆角值 |
| | | borderRadius: { |
| | | type: [Number, String], |
| | | default: 16 |
| | | }, |
| | | // 标题的样式 |
| | | titleStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 内容的样式 |
| | | contentStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 取消按钮的样式 |
| | | cancelStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 确定按钮的样式 |
| | | confirmStyle: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | // 是否开启缩放效果 |
| | | zoom: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否异步关闭,只对确定按钮有效 |
| | | asyncClose: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否允许点击遮罩关闭modal |
| | | maskCloseAble: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 给一个负的margin-top,往上偏移,避免和键盘重合的情况 |
| | | negativeTop: { |
| | | type: [String, Number], |
| | | default: 0 |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | loading: false, // 确认按钮是否正在加载中 |
| | | } |
| | | }, |
| | | computed: { |
| | | cancelBtnStyle() { |
| | | return Object.assign({ |
| | | color: this.cancelColor |
| | | }, this.cancelStyle); |
| | | }, |
| | | confirmBtnStyle() { |
| | | return Object.assign({ |
| | | color: this.confirmColor |
| | | }, this.confirmStyle); |
| | | }, |
| | | uZIndex() { |
| | | return this.zIndex ? this.zIndex : this.$u.zIndex.popup; |
| | | } |
| | | }, |
| | | watch: { |
| | | // 如果是异步关闭时,外部修改v-model的值为false时,重置内部的loading状态 |
| | | // 避免下次打开的时候,状态混乱 |
| | | value(n) { |
| | | if (n === true) this.loading = false; |
| | | } |
| | | }, |
| | | methods: { |
| | | confirm() { |
| | | // 异步关闭 |
| | | if (this.asyncClose) { |
| | | this.loading = true; |
| | | } else { |
| | | this.$emit('input', false); |
| | | } |
| | | this.$emit('confirm'); |
| | | }, |
| | | cancel() { |
| | | this.$emit('cancel'); |
| | | this.$emit('input', false); |
| | | // 目前popup弹窗关闭有一个延时操作,此处做一个延时 |
| | | // 避免确认按钮文字变成了"确定"字样,modal还没消失,造成视觉不好的效果 |
| | | setTimeout(() => { |
| | | this.loading = false; |
| | | }, 300); |
| | | }, |
| | | // 点击遮罩关闭modal,设置v-model的值为false,否则无法第二次弹起modal |
| | | popupClose() { |
| | | this.$emit('input', false); |
| | | }, |
| | | // 清除加载中的状态 |
| | | clearLoading() { |
| | | this.loading = false; |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-model { |
| | | height: auto; |
| | | overflow: hidden; |
| | | font-size: 32rpx; |
| | | background-color: #fff; |
| | | |
| | | &__btn--hover { |
| | | background-color: rgb(230, 230, 230); |
| | | } |
| | | |
| | | &__title { |
| | | padding-top: 48rpx; |
| | | font-weight: 500; |
| | | text-align: center; |
| | | color: $u-main-color; |
| | | } |
| | | |
| | | &__content { |
| | | &__message { |
| | | padding: 48rpx; |
| | | font-size: 30rpx; |
| | | text-align: center; |
| | | color: $u-content-color; |
| | | } |
| | | } |
| | | |
| | | &__footer { |
| | | @include vue-flex; |
| | | |
| | | &__button { |
| | | flex: 1; |
| | | height: 100rpx; |
| | | line-height: 100rpx; |
| | | font-size: 32rpx; |
| | | box-sizing: border-box; |
| | | cursor: pointer; |
| | | text-align: center; |
| | | border-radius: 4rpx; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class=""> |
| | | <view class="u-navbar" :style="[navbarStyle]" :class="{ 'u-navbar-fixed': isFixed, 'u-border-bottom': borderBottom }"> |
| | | <view class="u-status-bar" :style="{ height: statusBarHeight + 'px' }"></view> |
| | | <view class="u-navbar-inner" :style="[navbarInnerStyle]"> |
| | | <view class="u-back-wrap" v-if="isBack" @tap="goBack"> |
| | | <view class="u-icon-wrap"> |
| | | <u-icon :name="backIconName" :color="backIconColor" :size="backIconSize"></u-icon> |
| | | </view> |
| | | <view class="u-icon-wrap u-back-text u-line-1" v-if="backText" :style="[backTextStyle]">{{ backText }}</view> |
| | | </view> |
| | | <view class="u-navbar-content-title" v-if="title" :style="[titleStyle]"> |
| | | <view |
| | | class="u-title u-line-1" |
| | | :style="{ |
| | | color: titleColor, |
| | | fontSize: titleSize + 'rpx', |
| | | fontWeight: titleBold ? 'bold' : 'normal' |
| | | }"> |
| | | {{ title }} |
| | | </view> |
| | | </view> |
| | | <view class="u-slot-content"> |
| | | <slot></slot> |
| | | </view> |
| | | <view class="u-slot-right"> |
| | | <slot name="right"></slot> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- 解决fixed定位后导航栏塌陷的问题 --> |
| | | <view class="u-navbar-placeholder" v-if="isFixed && !immersive" :style="{ width: '100%', height: Number(navbarHeight) + statusBarHeight + 'px' }"></view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | // 获取系统状态栏的高度 |
| | | let systemInfo = uni.getSystemInfoSync(); |
| | | let menuButtonInfo = {}; |
| | | // 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容) |
| | | // #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ |
| | | menuButtonInfo = uni.getMenuButtonBoundingClientRect(); |
| | | // #endif |
| | | /** |
| | | * navbar 自定义导航栏 |
| | | * @description 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用uniapp自带的导航栏。 |
| | | * @tutorial https://www.uviewui.com/components/navbar.html |
| | | * @property {String Number} height 导航栏高度(不包括状态栏高度在内,内部自动加上),注意这里的单位是px(默认44) |
| | | * @property {String} back-icon-color 左边返回图标的颜色(默认#606266) |
| | | * @property {String} back-icon-name 左边返回图标的名称,只能为uView自带的图标(默认arrow-left) |
| | | * @property {String Number} back-icon-size 左边返回图标的大小,单位rpx(默认30) |
| | | * @property {String} back-text 返回图标右边的辅助提示文字 |
| | | * @property {Object} back-text-style 返回图标右边的辅助提示文字的样式,对象形式(默认{ color: '#606266' }) |
| | | * @property {String} title 导航栏标题,如设置为空字符,将会隐藏标题占位区域 |
| | | * @property {String Number} title-width 导航栏标题的最大宽度,内容超出会以省略号隐藏,单位rpx(默认250) |
| | | * @property {String} title-color 标题的颜色(默认#606266) |
| | | * @property {String Number} title-size 导航栏标题字体大小,单位rpx(默认32) |
| | | * @property {Function} custom-back 自定义返回逻辑方法 |
| | | * @property {String Number} z-index 固定在顶部时的z-index值(默认980) |
| | | * @property {Boolean} is-back 是否显示导航栏左边返回图标和辅助文字(默认true) |
| | | * @property {Object} background 导航栏背景设置,见官网说明(默认{ background: '#ffffff' }) |
| | | * @property {Boolean} is-fixed 导航栏是否固定在顶部(默认true) |
| | | * @property {Boolean} immersive 沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效(默认false) |
| | | * @property {Boolean} border-bottom 导航栏底部是否显示下边框,如定义了较深的背景颜色,可取消此值(默认true) |
| | | * @example <u-navbar back-text="返回" title="剑未配妥,出门已是江湖"></u-navbar> |
| | | */ |
| | | export default { |
| | | name: "u-navbar", |
| | | props: { |
| | | // 导航栏高度,单位px,非rpx |
| | | height: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 返回箭头的颜色 |
| | | backIconColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // 左边返回的图标 |
| | | backIconName: { |
| | | type: String, |
| | | default: 'nav-back' |
| | | }, |
| | | // 左边返回图标的大小,rpx |
| | | backIconSize: { |
| | | type: [String, Number], |
| | | default: '44' |
| | | }, |
| | | // 返回的文字提示 |
| | | backText: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 返回的文字的 样式 |
| | | backTextStyle: { |
| | | type: Object, |
| | | default () { |
| | | return { |
| | | color: '#606266' |
| | | } |
| | | } |
| | | }, |
| | | // 导航栏标题 |
| | | title: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 标题的宽度,如果需要自定义右侧内容,且右侧内容很多时,可能需要减少这个宽度,单位rpx |
| | | titleWidth: { |
| | | type: [String, Number], |
| | | default: '250' |
| | | }, |
| | | // 标题的颜色 |
| | | titleColor: { |
| | | type: String, |
| | | default: '#606266' |
| | | }, |
| | | // 标题字体是否加粗 |
| | | titleBold: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 标题的字体大小 |
| | | titleSize: { |
| | | type: [String, Number], |
| | | default: 32 |
| | | }, |
| | | isBack: { |
| | | type: [Boolean, String], |
| | | default: true |
| | | }, |
| | | // 对象形式,因为用户可能定义一个纯色,或者线性渐变的颜色 |
| | | background: { |
| | | type: Object, |
| | | default () { |
| | | return { |
| | | background: '#ffffff' |
| | | } |
| | | } |
| | | }, |
| | | // 导航栏是否固定在顶部 |
| | | isFixed: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效 |
| | | immersive: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示导航栏的下边框 |
| | | borderBottom: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | zIndex: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | // 自定义返回逻辑 |
| | | customBack: { |
| | | type: Function, |
| | | default: null |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | menuButtonInfo: menuButtonInfo, |
| | | statusBarHeight: systemInfo.statusBarHeight |
| | | }; |
| | | }, |
| | | computed: { |
| | | // 导航栏内部盒子的样式 |
| | | navbarInnerStyle() { |
| | | let style = {}; |
| | | // 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离 |
| | | style.height = this.navbarHeight + 'px'; |
| | | // // 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度 |
| | | // #ifdef MP |
| | | let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left; |
| | | style.marginRight = rightButtonWidth + 'px'; |
| | | // #endif |
| | | return style; |
| | | }, |
| | | // 整个导航栏的样式 |
| | | navbarStyle() { |
| | | let style = {}; |
| | | style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.navbar; |
| | | // 合并用户传递的背景色对象 |
| | | Object.assign(style, this.background); |
| | | return style; |
| | | }, |
| | | // 导航中间的标题的样式 |
| | | titleStyle() { |
| | | let style = {}; |
| | | // #ifndef MP |
| | | style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px'; |
| | | style.right = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px'; |
| | | // #endif |
| | | // #ifdef MP |
| | | // 此处是为了让标题显示区域即使在小程序有右侧胶囊的情况下也能处于屏幕的中间,是通过绝对定位实现的 |
| | | let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left; |
| | | style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px'; |
| | | style.right = rightButtonWidth - (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + rightButtonWidth + |
| | | 'px'; |
| | | // #endif |
| | | style.width = uni.upx2px(this.titleWidth) + 'px'; |
| | | return style; |
| | | }, |
| | | // 转换字符数值为真正的数值 |
| | | navbarHeight() { |
| | | // #ifdef APP-PLUS || H5 |
| | | return this.height ? this.height : 44; |
| | | // #endif |
| | | // #ifdef MP |
| | | // 小程序特别处理,让导航栏高度 = 胶囊高度 + 两倍胶囊顶部与状态栏底部的距离之差(相当于同时获得了导航栏底部与胶囊底部的距离) |
| | | // 此方法有缺陷,暂不用(会导致少了几个px),采用直接固定值的方式 |
| | | // return menuButtonInfo.height + (menuButtonInfo.top - this.statusBarHeight) * 2;//导航高度 |
| | | let height = systemInfo.platform == 'ios' ? 44 : 48; |
| | | return this.height ? this.height : height; |
| | | // #endif |
| | | } |
| | | }, |
| | | created() {}, |
| | | methods: { |
| | | goBack() { |
| | | // 如果自定义了点击返回按钮的函数,则执行,否则执行返回逻辑 |
| | | if (typeof this.customBack === 'function') { |
| | | // 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this |
| | | // 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文 |
| | | this.customBack.bind(this.$u.$parent.call(this))(); |
| | | } else { |
| | | uni.navigateBack(); |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-navbar { |
| | | width: 100%; |
| | | } |
| | | |
| | | .u-navbar-fixed { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | top: 0; |
| | | z-index: 991; |
| | | } |
| | | |
| | | .u-status-bar { |
| | | width: 100%; |
| | | } |
| | | |
| | | .u-navbar-inner { |
| | | @include vue-flex; |
| | | justify-content: space-between; |
| | | position: relative; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-back-wrap { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | flex-grow: 0; |
| | | padding: 14rpx 14rpx 14rpx 24rpx; |
| | | } |
| | | |
| | | .u-back-text { |
| | | padding-left: 4rpx; |
| | | font-size: 30rpx; |
| | | } |
| | | |
| | | .u-navbar-content-title { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex: 1; |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | height: 60rpx; |
| | | text-align: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .u-navbar-centent-slot { |
| | | flex: 1; |
| | | } |
| | | |
| | | .u-title { |
| | | line-height: 60rpx; |
| | | font-size: 32rpx; |
| | | flex: 1; |
| | | } |
| | | |
| | | .u-navbar-right { |
| | | flex: 1; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .u-slot-content { |
| | | flex: 1; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-no-network" v-if="!isConnected" :style="{'z-index': uZIndex}" @touchmove.stop.prevent="() => {}"> |
| | | <view class="u-inner"> |
| | | <image class="u-error-icon" :src="image" mode="widthFix"></image> |
| | | <view class="u-tips"> |
| | | {{tips}} |
| | | </view> |
| | | <!-- 只有APP平台,才能跳转设置页,因为需要调用plus环境 --> |
| | | <!-- #ifdef APP-PLUS --> |
| | | <view class="u-to-setting"> |
| | | 请检查网络,或前往<text class="u-setting-btn" @tap="openSettings">设置</text> |
| | | </view> |
| | | <!-- #endif --> |
| | | <view class="u-retry" :hover-stay-time="150" @tap="retry" hover-class="u-retry-hover"> |
| | | 重试 |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * noNetwork 无网络提示 |
| | | * @description 该组件无需任何配置,引入即可,内部自动处理所有功能和事件。 |
| | | * @tutorial https://www.uviewui.com/components/noNetwork.html |
| | | * @property {String} tips 没有网络时的提示语(默认哎呀,网络信号丢失) |
| | | * @property {String Number} zIndex 组件的z-index值(默认1080) |
| | | * @property {String} image 无网络的图片提示,可用的src地址或base64图片 |
| | | * @event {Function} retry 用户点击页面的"重试"按钮时触发 |
| | | * @example <u-no-network></u-no-network> |
| | | */ |
| | | export default { |
| | | name: "u-no-network", |
| | | props: { |
| | | // 页面文字提示 |
| | | tips: { |
| | | type: String, |
| | | default: '哎呀,网络信号丢失' |
| | | }, |
| | | // 一个z-index值,用于设置没有网络这个组件的层次,因为页面可能会有其他定位的元素层级过高,导致此组件被覆盖 |
| | | zIndex: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // image 没有网络的图片提示 |
| | | image: { |
| | | type: String, |
| | | default: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEYCAMAAABFglBLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0U3MjVFMzQwNEY1MTFFQUE4MTNDOUEzMTVBREMxQjIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0U3MjVFMzUwNEY1MTFFQUE4MTNDOUEzMTVBREMxQjIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozRTcyNUUzMjA0RjUxMUVBQTgxM0M5QTMxNUFEQzFCMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozRTcyNUUzMzA0RjUxMUVBQTgxM0M5QTMxNUFEQzFCMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkHIU9QAAAMAUExURdHW2OWiou7u7tve4dnc3/vw8N3g4sPCwvjn5+jo6M7Q0u6+vtyEhPXY2Li+wuikpPXW1uXo6dba3Pbg4Na5u+qurqqyt/HJydjb3fjo6LrAxO7Bwey1td6MjOrs7fbc3OTn6Maytf7+/vz19eqqqrzCxvzz87a8wLO6vuqxsf78/PDFxenr7L/FyNTY26+2u/z09MjMzqy0udnZ2dvb27G4vMjN0O7v8P339+Ll5u3v8NDS1ODj5frt7bC3vP76+u24uOKamtTW2MTIy9zc3N7e3vPQ0OCTk8DGyfDDw9LR0c7S1LussNbY2fPOztLU1cLHyrK6vvji4vrv78bKzPX29/np6crP0vjk5Ozu78bLzuepqczR1MHFyMDEyOq1tfTS0vP09cLIy9DU173Ex8bGxvHy88/U18vO0LK4vM7OzsTKzcPJzPLMzM/T1sbMz8HJy+7FxbW8wNTW18XKzvb3+MjLzczP0d3e3+Dh4r3Dxtna29zd3rK5vdPU1cfM0Prq6uOenuPm58HGyfHz8/ro6Lq/w8nO0bzCxcrKytjZ2uXm5vTU1LvBxbW7v+rp6eefn/39/eLi4u3t7Ozs7Pj4+Pr6+vX19fHx8erq6vPz8+Xl5ff39+fn5/v7+/z8/PLx8fX09Pb29ri4uOTk5PHw8OPj4/Py8ubm5vn5+e3s7Pb19fTz8/f29tfX1+7t7cvQ0+zr6/Ly8u3t7fr5+efm5quzuOvq6vT19uvr6/j5+a61uuy6uvb39/T09Ojn5+jq6621uvn6+t/i5KyzuPn4+Le+wvv6+s3S1fj4+fDw8OHk5vv7/Pr7++Xk5LS7v7y8vObl5fz9/ff4+OTj48DEx8XJy+Hk5euysu/x8re9wfn5+v38/Pv8/Pr6++vt7uLi4+Pi4ubl5uXk5dPV1tbV1fHMzNPX2e7u7ejn6PT19fX19PDw7/b29f39/vnr6+Hh4a+3u+7t7q61uePi48PHyf35+enp6fLz9PPz8qmxtuDg4N/f3/Dv7////////1cfN/UAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAlqElEQVR42uydCXwUVZrAk5CEJBAQshACCGyjIRwBJR0It6JBGZZBkRtm5PBgFXAXHBghqAMz6LjGTrd9n7lvwn0JQhARxYNxVJTMwcwOM+7uzK6Z3VXcrnTXVr2q6q7urrvfq26c+n4egXTSVfXv993veym4JkklKdoj0IBoogHRgGiiAdGAaKIB0YBoogHRgGiiAdFEA6IBUVuwx+vqet/v1YAkhxwmcNT9gvjnsAYkGcT7eN1TtThe+1TdL2o1IEkg+rrvUV90MF9oQBIqv6ijF4a3d51XA5J4jVVXx3z587ojGpDEm/QwkO9pQJJB2CvksAYk8fJUXT31xZG63ppRTwI5QgcgR35Rd1MDkgxSX1f31P33P1V3i3u936HUyc3edWSsjuEakKRRW3r9kVv+JrRsrwbklpKTnXs+z0wfONOtAUm8rJu55oqfkZ0TOzUgCZS3Zh7s54+S5RqQhNGYMsnPISkakCSiQcjkNzUgasvUDaVsBF1fb9GXd//B0ziliPzjEA2IujLqGRaMST+cawgLNpn4q30aEDV1Va+dYRqzNy00RMoYUmdpQNTzcb8sCtuKH8w9Z4iWLuIbVzQgapmOBedDOO6c0WjgENK2vKwBUUUmvBSiUbrqxwZuOU18d5MGhEe/dI6a2WvIxA0HX1hfvH7V8gUpA9Mz3x/VuVTRLxvwdAhH5aJjBj4hv39QAxIl743ITHm6azBPnOCfVLwlc6pR3upYFbLjvIuDlEUqRYZo38JYS4gVt5rNZhMQO46byksoMeG4w8QIjtvMpJA/wP1EdZ0bKvkitkh555mBA3zSLnBlSFnNnlJvEJJ95ItW3pJAamvtZhy3m/T0g7fhthJGiG+EviYgmEJ/wHFP6GsbnlZSoicoucxmGwHVguPTBz543C9LHky/Jnql1w5KxWGo8KsThsAFYneYMOb5mlnP11ZCrwOPjfgGI8RqsZuZtUOsFmbllFhZP+zEbc8vGcqzMibv7qos3rd7Mg+TrQv2vCVkiLYcl4rDYLhKvi79FgFirDU7TPpyHHexINg9ZnOazWazyPxlVhsBzcKAKrl84cHo5zx758H0ASufWGrVBWkxup8YMepCespDV6Jfe3z9kOnc7/OjLydJx2FoJF983norAEkLqSbiw4+ZHGY7+UQhyYCDEZml8+ufzLz+RlBAPpieGYPloczYdaIb8rAMHAbD1yr5WHEBqTW7XIwZwEykBoIcrH3JrkgcL554PShRPpie/hDbE9v97oTIXz2zUhYOw0Lw4glJDMRiNtGrIo1AYUFwYZlsVbVzwyhfUJ4YO1MqWb/hsfRw6rzzPnk4DNlk2sQ/BU9aIGZaS2EOI6LLunBn+Fk+fWFpUJmcvPBSWH2dXz4A/OqTB2XiMBimgJf/NCmBGO0OykM1mW3ILur9cNq1cuATwbjk801htC8QcUQmk0NcIhWHoWKwPBdr9KwVuWvz//nVxYXIgTg9wCu1OZxGdJ+RacWhxNKazmD8snJL2DN44Z/oL/btMkgWsFr3Sr+BvmF5oP+fMpABMaaBSKHcgnTJ7tnLPLz1mW8E4ch76S9Hec47pOOgPCz/COm38EDfCEkdn4UCiNVDGg69Ay2OqSEFU7wnCFNGvcB2hHtk8Lhbdn/D6r7RMigLARDSbjgRW7QhTNBdeSEIW6ZPYXgUzZXBg3IB9n0o4y6+iAHS9/Zhc2ACMToIS27yoA5URzCe7ju9gigk5O767/sXqTz04PUPT5VzH31uv3f1o30eTb2djeQnr8IDkqYnLTlqMX7JfIAHeoOI5P3HmDSXRJv+8WC5BoQtZSv++SdhJMMgAbGSISDy1YEPYFzdJ91BhJL5Dv020sw6VdgdEMd9zfrTallERIE4ieWB2VDj+GALE1F3BtHKB0N209E/Js6Dyt28H+fNzbtXBhExIGaqiIHaetDLY/LAIHo5STtcs2+I8ZgNXtdLtqYadiY1NfXMsGEFob+QTkQEiIPwdJEvD7zX29Qj2jQ9qIqk087c1X8XzGBRL3pX9u3khZ2rsfOovyqgF8m8eIFYSjAjahxvLKdzsr2CaslK2p/rJ1BFP0ZXHvE4gJCxOsXgK4rIA7PiAkJYcidyHp1DqTufsi6ooqTQXREL+Xj8gY5PFdxRQWQM8kABi8igeIDYS+zItRX+Pm3NBwbVlVF0sSWHm8f/Ut8d40pTkJqIDgvzWUT+pByIhXCvkPPIpG78yij2w7K6/7PFiJrIe3QafjsXj266+Z1M3uld1jiB9B1L/m0uZVaWKQaClegtyM05nUf8ll1asr9Oihv5IhlCvfneWB4Y7WQ4HRhVhrN77DJ0dx75n4y8M/dGEukDvjyjFEhaSUka8moMdeMb2I9J92+vU/Jn5EToj8NQzp4fv/8FkEBwknkjFyjHyU7l5aWyg8Jx4E+3FyoEokevsOgezkjvyv46I1bkRKZR/m9RJI/7qct6ie3+0z1KLrmf0TwmF58bcr/GKwNiJ3t50ApdiBoVWQsP8Xjdjt60X6PC9vNsHrQmWxPlcaa5yPpDOeF4ii6Ugrxh8/LoqLCMzpw8UMYk5u9VBgQj3xup0LHAyshH1BIGElDB2dL1iybyCnVZWziu2GbGPDheTi4UQTt/hkJwhioWjg0rrXngq1eVADEityB0AuPNqCdkCQN5XRX/l0oAb43s+BEoSBmpDg/MzO/xMJkSyoCX0badwFMGMvKrlQCxlSPOuL9L3XjM87GqqrJIWUPlGikec6nLmiiYvqB8rxKXKJC+95KLhMIA6DwKlo4yo46WB138+DD2+QRCQP5TpRhxA9WFEtp0QESpojUJuwuokHIPl0GZF45A7iVNR35fhsNI8NVoGCVcJPEHV8NVaIn8RbWonSrubjQY6CKApI4fo9OKO8m4MZYJO5kFTDjlahHhyV3MF7KBODwofaxOqhD3U87HY6ECkb8YVQMSpPy9nDV+mfl2C+j8iGFSlpeXN+wBVt4kP6Sz7hVLn/ABQVoFWbeVy78Ke75/tgf+zx1UU7pYPSmZsu7FSTHhyPrl0yl4QmllUP1AjMv1qAIgGEIgur0c8UdiZWl4B8pM2fdDMNEbcTuWFs6ugCgkL1wDoWJ04ov55P9TFQAx8fsQccuahKR3RSokDI9pim7JQn6ECb+LUl15hFuVWsB4W6tDkQmxVnIfzV02DlcExISKBxUJrwkml9BVmT2Kb8sOfGG9w4pnADf39gzGmFPq6/bUM1K6SlN4a+moMlkDqERRkvHo56f8jAVx3JnVDDJerjOhDO8ZJiLMyJXa4pvCn+tFUys8CeznC8mF463TCk16jDkhwhMHneJdTZZAHlg9LLdMVgKcvzqFpm8UNO8O/W1S8fiW5WOVXo7v/qxp1tUhY15WJvvnU/iz7w50FZBRScVjBIXi9ELgaj0d9z0W3B6K0XF4QFxIgMykMndqPm4sm+5Mbf8om/MFM+kOFAzL8cPZ/FxAKK0HMnC4QKwoTMhvgQG587/UBPK93r3/mO1tr/h5794Y1/cH0lXkYCOGgW7sSZfjv9GMAsUqRChbg0hhdQZVBsIIFxC61YHwwrMxrGcMHKUVzyPij3VMethELoMOxYnBJALyULgJyYxh2CK/WhMbFKTfOdJZRmNcywdkVe/UJQ+QT+he+JmgmE8AwfbCUloIVJapJGaJuDFqu6fX6VAQN+6hevuDSQPkGtXjMJiaSGAjgVQUJVhpCQCxxS4RN3nNjsvN5P/kvxdocd+iMo/2x8NA/hi1o4reIERXZVrIu4LmaaEw6rFLBAChRVlRqus9lYE81ZslH7G/c4Hezhjqd6Du6zPQ+P1eMgKxxJSN4wHyVldIW6soFWwevR9vj+lbXB/+GxO4r12zJRVxEwGE3B1igwZkInmfz6rMw9w7Up7yRhbS/ctZL66nbmwjaDb+VTICMeqjmrPYQGS6WSfBB++6ujy8j0cB6V1BfcNH75GOyBlk03d2RaWp4vKB4M4SV8Rzb2YBkZlZeRIkt9VOU3mz/8jWWCVm6q9P0k166REvdtB3tgMYO28yAsEjNVYAY4usbPBUstxQejIBqUNvNm3XK8yh8iBVQj8/LfKVR5g7G5NAR0u0DcgaXiPuCB6YSc5naE0Cq7ZuoLhKwnt1qPCjK1p/hj5vwPXtZ0xKIFZ9yNMymiKBYM3S3+YJct7k0P9KDBBg2sPmnN4j9FhMU5gzdGf9FO2+VWeFOELhodsRBSS7RfLbbInV2GpKSe/eIYd3IsWDw9+zYRFLZGhyAiG7KRhLYnSGmcgambWOnIjw1x8lDIj35x9FdvJyuhcslbwPxsAAREAI35e1tc1IOVoyZ/uBtPu7CSwKMm13S5+lm6mDwkDAEtmUnEDInFZ5+Pl7SUtSL4/Hm2Ck24jEF2sn0NndzKAIEOw18mXTkxMIuZmKFQaSpq9F3pukgx34iefRixpl3cVTIWMDAWPLnkxSICzDDv6EBWS+yYP8n0o1ZQE9LOs/guJA9GRj0O5fJSkQ3ONhGxW5Xb/XQHLog0T3+tDFwSf5DQ3bhSxOkOcreX+IUXnz9RDVO004ZBQ1wfe4wEKNAHIDxCrJC8SIKZ+zAXbxTU8sj3S612dCUCIQDKRXOpMZiFIiU8Eg0ITi8NEDh559MygZyEbVjr9VqLIUE5mYcJM+9TG/lAa9SCA/JvsYd+uSFohyIo8l2qS/T022nvRcUA4QqpbbmbxAABEFe9fXJXo3CJ28qhQtjp3FYnXWl0kMBBDxyH4H0NvwfsJwvEcPKHhBfG754Ugg+oT4WbK2RRNE5E/8W8U1r0E1uV4pkLziq4ew61RvJTMQ3GiSzeM3pALfmSgemdTg3dLPpbw4LQrI18r3HKoFBETt8qCMApo4QTzoeVyPTZVWx4oCsohjKFDyAYnMNIoLaLeZkBAcJ+mjFpZ/KO31N6OAgCPUt6J9/uMgrBCCiIzRf8WcI2bUkE461z5E6g9kRwMBO3OXIgWiix8IOexa+jDGX05md2uqmmunttUWST+HpDkayEbkRkQHwYYArSU5RLwODgBUH4eOniPzoIwjrBqjgSxEfiDxXVCAgAH9EkPEXonJmzxBH5y0Rs5mlO5oIGA46XqUQArhAMEt+hK9tHlaz5D3NFVtHnSuXV6fizGGB0ZOZNwNmcE3rK9HwwJChIgSpzc9nAibTje2b5W3N6glFgiIKt9A51e9WAYLCDAlke6v2+l0x7zmLb/6Nv0kfWT9s/8h7+dssUDug3/iKpvH6Kw5EIFYIt3fNHJCXkP0i8CQnSmq8pi2VeEweXsskBz4Tb6PsL7+KksHc4VEuL9HqRmS0e2+M9XedvvLJ+nSoPytjGmxQBZGD1SO389lASnLehGHCYR0f/WM++umgBzlKk5NUzGXSB/V85KCLklHLBDgZu2D6laxBjzMyvoKLhDg/tojVkg0kCnqOlnp1FE9gxV1ETdj3G6WHyaQrHDkMScr6xHIQEj3lx6JojtB8jgRrRPBMbJela15pbIeSRMHkH2Q3azRWWG/6pGsrELYQHBLqBXb0mowtMbsGAF1aZWt+cG3FP04RxiCYeAM3W/hAXkx7FeNy8qCbUOogEQoZPeBU4NUteanlZ5k1cIFBAT816HxKMti+VWzCCKjoQMhR2xYeCskb6q285ax5sUrlf4GGxeQq2C6FzQgs7JY5+voCCDL4AMBm6wwnjzKNbVSi7Q19z+pfPqynQvIxvin/rGEMONsv+ougshdCICAww658/ET1CkXMta8NJ4zps1cQHKgDhEgzHjWuIgFk1WgQwAEBCScRPaospONseYPXYvnt7i4gNyAuiuBMCFZ7IMMSbteiAIIb4UkU4VpGow1j7Od28fFA3sFbqieQRD4JkppjUMBBAQkHESGoJ/HxFjzojjzAZw2HauAXBFZFmlF8K+ysjKQAAFEYjPyE5F3ODDWfFO854SmcQLBlJ72KaC0yqKU1hwkQHBLOUdTI2ogjDWHkL/08AOBWTPMiIoGC6OXCLyKsdEYu6kHMRDGmr8Tv1bkNiEUEKi7caOiwWVRVgXqCTtGLLr1FymQkDWfAqFRtVYAyAswgRBKq8AYocIilwhUIK7oJjqUQEbQ1vw8FLc6IABkCtwKFZsAGYkU4qiAUE10RnWA0JsM/A+thPLrsgWAHIRbVGfZ8W+yspaNxhECiSaCDAhzQj2saqSxWwAI3PZeMvZ4JGTiZ83BkQIBRKyRcQiCyfsDqQkA/p2wYPOZEAz+/ADSjH9Fub6/zspAkVyMlDR2GiUTSZvc1PVMbP5LWL/yshCQFMhGPWsW7VcVjsbRA8HtLKW1R1azs9RY8DzdxwBx6TULAZkI8/G8SNh0OqGo4zqMCkHnqt0aOiZ0AvyRAdfoM7/9774B75da+Hjsgj3PoSzGiqMHQloSWmtdg77hs9dfKRxXPof5W/mcXirbuwfik5n11RxcfSA2xo6AiuEqeA/u22eYWHAp1GXHq7HAgNKVEBeI6DEvaFaITV+it8GvqWeW0oUoyH6Cm48Htc0Q4pad0XhigITS8WTXyRVYmcSX6OXx9BOQ3YQ0XiCg2xreNIdxeKKAMETIvqzjcJ7ahSIKx2T4FUgXLxDyPUtxNQXZ/iCKCOhchOEOvUmPj/E/uBI6D36NBbZ9xjuddHOf/NvyZpUlGgjY1GOcCKmV9POtcFMlEjUWlIJhnyogdwxPMBCCiJPqfo87gHuDnu3q34nkPDF+jbVNaW7xxe/nz+9zT5/NuSsK8UEUkKrViQZC9tDNhBGqT6P3N/s3+FDwqOXXWBsVBeovzmMY0NJncd7aqqqErxAyaAfDPTfF9bys9H5afyWi80GP8ANRNKFpflW05BMBCIElCYBQg/Vmx7U8+jGZRB8aHsw5LlwCLNeH8pK5d8TwqFpLROgAS6KBEDwGx+dmrWOcq2JklXknPw/sbdlO1m1VHDISx/Oqql5NOBCmqKd8Wlavh+nYA+FBFzf5eSyUbdOXcfGoysLxV6uqFsMAUhgHD6D7qWMBFdY96EHt/k0IR5oKmHT5rdZzOHlUZeD4eMKGjP9+bt5XZfEBmaecB1A2Y7D/IWNdZWVBuu5R2iuIUMwCQEAma538kCNK+o/D8TPhPw26Z9jm3MVlioBk9f9GKQ/w6X6t2wB6QxS0FU4oZhK736LkYRXgATa0SRrPNKdwVsHikbfN414gY4lXEI5wlC88TwGQWf0l+mqxAh5n0e8MBqC4ZG8T+JAePebvQtyqnSYEZLbEpqzRVYJCPsPUqjuIwCxj8W35Zxgwt8kFMgs41HfcVqh0fexuNBgMPUoikWlDmbLgJ2h5+BoFeGwTGRsw7pGfzXqksOybccuEgYwnXkrEhbmLmVp62bKR8+4gCclcIYWkSpyvhAc4pLy0HuyUniQ7Ell6EHEoKKG5ITRYg7s6tTi/zxf9IxTQq7l5+TxACOWUQX2VOmj4/M0jV5CP9J6qKgU2pA/pQitIIYNw8A/U1vWdcvOLmVdQh4Lhdqx6ISCkh1jJSaN/zEMn9+GM5QFCPMOsyL9JHTSIL3IXBvLNWMXx+WkXxcOQI28X1ROrkIeCYbEL8bjBPUh57SCuhz6aUCh8KmsFjo8kgpGR88afGcRC+aIStzdLAQ+QUDzfTPMwgBT2UMk9PpPQh4Ih0bkwMad3RPTqGA4e5j3z543MKsTLCr9akZe7lvg7XcwqiAhDCP+LVv3Gwp+NXDu+zz19limKQxR4veCU8sHnDCEplX4I7oi9KoSCYTmMiWmsqDknZePJBzxoc5RdvYPwffILeIGMIxOOqRILwdBTJ0ujeRhWST2i7Q36BGf/FZVmAjYL8QBTezdE3NsK0krMXxFzz6nguX/Bw4P0poZXDUpQxfA34Ika2AIc39m/EU9cMVXBd1UaTC68QH4Qk3pfS64OLi/nrs2DBLze4WAN3ZMgICBZ3h0BxCDpZPsJDzHGvFMdHEGfoIuFkZq2KyKNRFY2+DIeGd8fzgdkGI7rQDCiHEhq3zyFPECnzsJIHoZF4t1ZS5ki7Wz1zmc9IsgDtMhtieKRK3TvGf25gXwfOGDzEgLkS5B8iuJh+HdwOrPQrv703TSP5T9VjYdbkAd1PvGASB4iPudm3jCkQIQlKiAgAFliiJEfCPeLDHiMicxVnD0nVAchZAZ5PXdG8hB7KDwprSwQhqxIAJALYItZLA8qFCni69dlioLHv/SpyMMpvEBeZuexRhf0qZKidIbxhSGEO7D6izP580b+7C4VgVwnNdNOA5dc4e0G0g2k29n9z65UEYdI0gRbSJaeS98bnZW7edjw1ULp8oiokS8MYWW5+o89k782L0MFIB+SbaNdn3ICyeGbmzXtThrHVpXHkacJLxAQpf8920rPl/IMcrmAgJxg6qD+MSl5xEBIzXO6gpOH4V/e5jwueuoUpuq+5RN1ediEeVSQlRB/6k8ifFdJbaOxPMiI8AsQHZbNyruNWHBUW8pmSUCG9Y0UuQZ9roFHXuY4XPKTlLeZft1OdXEEjc3CQLaTV/UPgMegM+PX5r0otTMXj40QiYgw2L9q+G15BYVGJp21bBBP8l0ciKkEc5md4oP3J5D7Ynfw8TD0DI6xIr2YLHtRr6DaIqKwekBrxt/l5y5+ZJxcRXEHR3mqMFROH94n/9W8grtI869QZZlKgOC4RfAMl/fIEt99Bn75LGoI/LTHQtpK/RPcRBQWtgQUjxVuC5kf2yXHVVEcpBCI0+whmJhIMnqX2SIUoe8T4GFofJu9Q3olU/Twr5da9bCeaq4/ZVVFYeknxbX3dmTkIskDXXLLRv9s5GZ2lTEvHqNuIVQWtVL0rjQu9UUe2zK5QgiIAXzqroAZlUuZqTH+rZK11ZGSDlJOqaCwqLRivw8U5/PmrJg3PKKW+GqoGoIbM1bk5vNXQ2R4WUanmVJf5QSgqLGwb5DmYIYgD8O5SXQjvG7IwwyPJ92SeXSUHLG5D3d3ZMfP46wID9Db4L8QX5J1DvHcCYcqtaqqjAxD+ku2RfLcXqcDI0eLl5eYIrQXWUPfaxARkK3764/er2RwbLou+RlaS0qoKL67wxZ3J1ajCJBKiEPLxlZV5S6b00dyNURBHGK04lZKe3ns9EKZSnhYRY1iQAwggcjUPPxdciLBU8zKsMW/RG6K8ABBLKxTXFaHfV+UuSxLGu18UYP4p3Ck3Dnkhj8sk76U1RHf2HGY/qqjG7EBoU7AhTUASFacH1eByuj0lIOj2vSeCt4UVpSMYXAc3yKztzSsqTrK0Xq8GNhiNHsdJCB3FYxcm39mbP985ECohWLFnSUlr5HGWgKPuUwacbnsXaCnGPfK3dEcFw9vvZhFf5t/lrWvZX9bTXtN2/4WI45M4i3hWhxkz36xOI5tTGB+XsHESlvH/ZRRbw7pLmXiEFsg4MCQSo4H7mtrCISlyf3rJAVCtY1WiOFY1BW2H0qG0WR39DiJ9dHc0YjWgIBoieM4BF9bIFpsviQFUuT3fyZM419n0NZjE7V/c4/8J+nLBnFhR2NcNazDYjxuHOceWHaxIRArDZakBDJCLCbsuXs2jWMAvYunn5JRPrbm8vLm+PRVbbcYEFBI3xmjsNoC3FKTfECMblJjYfw4Fv7QH8aB4yeBJbkvmAixihl06viW2BCkJhBQj0hcQIy1DQHiQ1XKr6v6ReDA6SmMkvoYoTfy3hRVWIM5k4r8PFAQiQdISzVxSZOIKJ0bh/4qrav8q1hWcoEfxRxGCWIW41HxGqiiRd+lOyAkliQCYgNXtIm4iX/lshyv0TRKNzxHxPSYyWQ2m624zUQtmglq87gsxoNqNDk/PeouvYI8Ag2+pAFCr+QhHJXbj3P6hVJWMz7BXSWM2PC0kudB2vevKvOwi/L42s+5g61GGEjAnSxAqukL+u/TZLOiJwTjd6/k3BkKOR6kktg2p9lsMplIIBaTB0z6lL5jRCUeVL/Flujb1DWIADmcJEDCVwQ+WVt/uGrGK4vu/uzK8XAA2O/gcwHOYvxAv1qHt0ktgdCdiv5VsZmIgJhcSgog1eELat7n55DXCBqEVHNmfabEN3ADdhsvc9aUv99vcakhCDKzrgxIxGW2boqmMXvKc8J+IVWkUqv5xyvKo4e6bo4zVp2iQNqSAEhL1DXt6sdoquNjijfO7WF/7yLnb6B2Eg5QB4gojwrq4kdwXGiDKJD22qPGRANpirmqtN/p5y6Y23Mz9nqrObtpPvErzWrJ7zER5UF5GdxTZsSBgOTvfl8igbQE5IibNwWG4uQEBfpqG12l4bzOdqm32WTRJQxIkywg3EsEX+lXJWRvkehf8Z2NVyvjRlsSBMQbCEBYIgwRtCfl1oryyBHkIZI4iTYn1oQAccsEwpeAu049igUJjT9oHg/x3ayuGsaHDy2QdplAmiJ/PGj0tlzcX1vjbHqOHojlS1x8vsPPExDKiAzhpn9TkC+QQMBIczhqqa1pYrstP6bVxTU0PMTziVQBRPD04aA8ixk4qzIQd4NsHoH9be127u/QAcBuJO7vETEc99NNlNsD1U02t+WSl9P9sMq824tqArFWB+BKuh+ZsyVaj3qePm5hIyuZXt3edtEbFeftl3lPLeoBsQagyy66iLUcsiER23KAYRup/VvHb3AUOZra3Faj5Ax8tPjUAoKARyDg2onCkLSYpJU//F2ZAt5IzX4qKyIvEo4vu5WSYB6E7IBvSEQbRrfRKeq9N8Xjvfb9Vp88z9KqChBjNSIggSG7IRsS0f6rq3Qy9CCaGzqrChB3AJl4imHuxtWJuVeL6L690iGobsioAhBvAKGkMXNID8Y/NdniEXF2mal1O/cgux+3CkBqYDNoPXTCcO5Ys6vR1N3T05Pzj3RtK1695RRpT8yh9ePg7R97bjpOHTqQhgBIkwpAYFiQtLTWU4bsY/UAQZTod4Y+uAjbqRcy77Lv+fBbY6ZPXceyDYda4bHRIQeii+PqDhxyZHtc9SasR1BymA09Uy4r9nazhSvnewfTM0+381xDN4HGk+2IG40POZBLirTSiXMff2rqkSxLJtPPa+I6ZepKcD+n/ip93IJ/aIfopXSbXMcMJw4oBWJFDkSej3Uo+9hHjViPbAlplMkHR8gPzgXVVcVVeiKw//RGGVfU3ejKdhySDcSCHIjkYLU122XqUS4hveXfJLNNyCY4FfluZoigf+cr8q8Kazwmb7W0IAcixes9YDhWj/XEK4ze8vsr038lvXQu1Et9Y28p8ztfvqH4wro/zj4k1bZ4UQMRT/Meajb1wJGFn4XaH3dvkJjhcvLv/dh2X5EfAg56qXya3Yo2MpQE5NJZkfc/kv37HojySmgjg99fPFF84IP1FK+bu2RMuH9v6AwoV1cvnv8KII1DdCLtlAeyG3tgS8VG1oPsWiPcUXeYx7natqSfHzoOsE6aDyEr5IoD0YnE6AdMPUgkZyjraZ5+9sKHfKkSzq3O5TP27mb//M4cuFeXjapGJQrEKFKzTUPEg5Btn01mdwwXb8mcaozJJHL5uq/cvW8w+yf3bbwf+sUZhHbx6NAB8Ylpy/oehLLr7n3HI/u433lm4ADWJt6zUb7urhnbPxtzOuInti5ZiOTaHGgqVGJARHsabvagFf3GyvPR3fWTitevWr4gZeCQRTk5i2bMyMnZsfHu7UvuG7p7cPQr//EHixBdVz2/B9xgRAdEQo63uQe55Oyc5Jcvx4du34bskrAjiLrlUuJTWKAq3qOC3Pi66G05NIq+noHycrpPCGxyM6IDclRatsRjUoNJzyszrr5cJM5idtdnO/QorwNzOQQidrsXRwdEcu/3oWPdPSrJwh1Xh75WenpwLInzr+28mtNRjvj9G88JxuqX42y4hgSEEEezqUdN+fGujudzNm6/umT7jpxFNzp26cvRvyfWKBYSBuLdkwBDZYVjRIenvrvnOyom102zeG4RcbO1LyBfWh3HTN8xFli9R2r23YcWiIRdwTxr5US2x2XCbnkSja7sm6dk1HPR7w+Jr7chrdVwrrnx1tNimMnluXmiVf4No99BZYTU83PK4DnmakzuNUN1n5xoVVxJDxxVYUubDnIH6QGyEaj5o0ZTd7JQaPyo+ZzhBIweoP3qbPrcH0AkaQdaTzhuZje7Pvp9dzemJoJ6V3O24QTMRiyyKxvKXnUpFcOWhoAKQuA5dOqEw3DznOeY51jzx/WfNv7eZDJhCklh3SZTIyHEsz/mOZd97qbBceIUZATQl4fUEq6xLZBgSUsj/jlwgPz3QOuR1gOtrYcOmQ8R/xL/aW09YCb+Jb+bRvwLXqy6tMMaryGxycFbE9BEQFu14Li6QIigvUl77rzaKoirDwTHL9VWa8+eQ1lZoE6Cl7Xp03ixXQMQue8A+gx4ufvUfW5NdYVo7Pfi0EXBaA2f+6wGI1CNggaudMSfz1LT8LdMo8F2VIfjSQSETKlY3c6/TRg1Fm8QRyZxzX7XWff/bVkUe02LD0crcR9XQayUs5f/JoxG21Ejjl5SoPwWn8V2+LsM43AbMpuBBggIUryWtvbvnqmvrnFb1YIBFwitwbwXbd8Nu1Ld3ub2GnG1JQXJbzVesrQ1NdzCi6IlAShQArlFlViCFoVqQCgBwxb31ziTNzNZ3VTT5j7q9enwJJAUFd/L6LNe3G9rr06SRWNvt+23WL3GpOCQECBhy++71OJuO9vU1KA2m4aGJnI5XEyS5ZAsQNgKTWf0ei+1XHTXtp1tPwx98Vyurm5vt7W53S0tl7y/1iUrhSQCErt6dD6f9WiLez8ptbU2Ww0p7U5nEyHVhNiZZdVgJ/9YTfy10+lsBy+z2WpryZ9zW45avT6jLojfcpKCa6IB0UQDogHRRAOiAdFEA6IB0UQDogHRRAOiiQZEA6KJBuTWl/8XYADnNmjWHFGctAAAAABJRU5ErkJggg==" |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | isConnected: true, // 是否有网络连接 |
| | | networkType: "none", // 网络类型 |
| | | } |
| | | }, |
| | | computed: { |
| | | uZIndex() { |
| | | return this.zIndex ? this.zIndex : this.$u.zIndex.noNetwork; |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.isIOS = (uni.getSystemInfoSync().platform === 'ios'); |
| | | uni.onNetworkStatusChange((res) => { |
| | | this.isConnected = res.isConnected; |
| | | this.networkType = res.networkType; |
| | | }); |
| | | uni.getNetworkType({ |
| | | success: (res) => { |
| | | this.networkType = res.networkType; |
| | | if (res.networkType == 'none') { |
| | | this.isConnected = false; |
| | | } else { |
| | | this.isConnected = true; |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | methods: { |
| | | retry() { |
| | | // 重新检查网络 |
| | | uni.getNetworkType({ |
| | | success: (res) => { |
| | | this.networkType = res.networkType; |
| | | if (res.networkType == 'none') { |
| | | uni.showToast({ |
| | | title: '无网络连接', |
| | | icon: 'none', |
| | | position: 'top' |
| | | }) |
| | | this.isConnected = false; |
| | | } else { |
| | | uni.showToast({ |
| | | title: '网络已连接', |
| | | icon: 'none', |
| | | position: 'top' |
| | | }) |
| | | this.isConnected = true; |
| | | } |
| | | } |
| | | }); |
| | | this.$emit('retry'); |
| | | }, |
| | | async openSettings() { |
| | | if (this.networkType == "none") { |
| | | this.openSystemSettings(); |
| | | return; |
| | | } |
| | | }, |
| | | openAppSettings() { |
| | | this.gotoAppSetting(); |
| | | }, |
| | | openSystemSettings() { |
| | | // 以下方法来自5+范畴,如需深究,请自行查阅相关文档 |
| | | // https://ask.dcloud.net.cn/docs/ |
| | | if (this.isIOS) { |
| | | this.gotoiOSSetting(); |
| | | } else { |
| | | this.gotoAndroidSetting(); |
| | | } |
| | | }, |
| | | network() { |
| | | var result = null; |
| | | var cellularData = plus.ios.newObject("CTCellularData"); |
| | | var state = cellularData.plusGetAttribute("restrictedState"); |
| | | if (state == 0) { |
| | | result = null; |
| | | } else if (state == 2) { |
| | | result = 1; |
| | | } else if (state == 1) { |
| | | result = 2; |
| | | } |
| | | plus.ios.deleteObject(cellularData); |
| | | return result; |
| | | }, |
| | | gotoAppSetting() { |
| | | if (this.isIOS) { |
| | | var UIApplication = plus.ios.import("UIApplication"); |
| | | var application2 = UIApplication.sharedApplication(); |
| | | var NSURL2 = plus.ios.import("NSURL"); |
| | | var setting2 = NSURL2.URLWithString("app-settings:"); |
| | | application2.openURL(setting2); |
| | | plus.ios.deleteObject(setting2); |
| | | plus.ios.deleteObject(NSURL2); |
| | | plus.ios.deleteObject(application2); |
| | | } else { |
| | | var Intent = plus.android.importClass("android.content.Intent"); |
| | | var Settings = plus.android.importClass("android.provider.Settings"); |
| | | var Uri = plus.android.importClass("android.net.Uri"); |
| | | var mainActivity = plus.android.runtimeMainActivity(); |
| | | var intent = new Intent(); |
| | | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); |
| | | var uri = Uri.fromParts("package", mainActivity.getPackageName(), null); |
| | | intent.setData(uri); |
| | | mainActivity.startActivity(intent); |
| | | } |
| | | }, |
| | | gotoiOSSetting() { |
| | | var UIApplication = plus.ios.import("UIApplication"); |
| | | var application2 = UIApplication.sharedApplication(); |
| | | var NSURL2 = plus.ios.import("NSURL"); |
| | | var setting2 = NSURL2.URLWithString("App-prefs:root=General"); |
| | | application2.openURL(setting2); |
| | | plus.ios.deleteObject(setting2); |
| | | plus.ios.deleteObject(NSURL2); |
| | | plus.ios.deleteObject(application2); |
| | | }, |
| | | gotoAndroidSetting() { |
| | | var Intent = plus.android.importClass("android.content.Intent"); |
| | | var Settings = plus.android.importClass("android.provider.Settings"); |
| | | var mainActivity = plus.android.runtimeMainActivity(); |
| | | var intent = new Intent(Settings.ACTION_SETTINGS); |
| | | mainActivity.startActivity(intent); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-no-network { |
| | | background-color: #fff; |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | } |
| | | |
| | | .u-inner { |
| | | height: 100vh; |
| | | @include vue-flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-top: -15%; |
| | | } |
| | | |
| | | .u-tips { |
| | | color: $u-tips-color; |
| | | font-size: 28rpx; |
| | | padding: 30rpx 0; |
| | | } |
| | | |
| | | .u-error-icon { |
| | | width: 300rpx; |
| | | } |
| | | |
| | | .u-to-setting { |
| | | color: $u-light-color; |
| | | font-size: 26rpx; |
| | | } |
| | | |
| | | .u-setting-btn { |
| | | font-size: 26rpx; |
| | | color: $u-type-primary; |
| | | } |
| | | |
| | | .u-retry { |
| | | margin-top: 30rpx; |
| | | border: 1px solid $u-tips-color; |
| | | color: $u-tips-color; |
| | | font-size: 28rpx; |
| | | padding: 6rpx 30rpx; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .u-retry-hover { |
| | | color: #fff; |
| | | background-color: $u-tips-color; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-notice-bar-wrap" v-if="isShow" :style="{ |
| | | borderRadius: borderRadius + 'rpx', |
| | | }"> |
| | | <block v-if="mode == 'horizontal' && isCircular"> |
| | | <u-row-notice |
| | | :type="type" |
| | | :color="color" |
| | | :bgColor="bgColor" |
| | | :list="list" |
| | | :volumeIcon="volumeIcon" |
| | | :moreIcon="moreIcon" |
| | | :volumeSize="volumeSize" |
| | | :closeIcon="closeIcon" |
| | | :mode="mode" |
| | | :fontSize="fontSize" |
| | | :speed="speed" |
| | | :playState="playState" |
| | | :padding="padding" |
| | | @getMore="getMore" |
| | | @close="close" |
| | | @click="click" |
| | | ></u-row-notice> |
| | | </block> |
| | | <block v-if="mode == 'vertical' || (mode == 'horizontal' && !isCircular)"> |
| | | <u-column-notice |
| | | :type="type" |
| | | :color="color" |
| | | :bgColor="bgColor" |
| | | :list="list" |
| | | :volumeIcon="volumeIcon" |
| | | :moreIcon="moreIcon" |
| | | :closeIcon="closeIcon" |
| | | :mode="mode" |
| | | :volumeSize="volumeSize" |
| | | :disable-touch="disableTouch" |
| | | :fontSize="fontSize" |
| | | :duration="duration" |
| | | :playState="playState" |
| | | :padding="padding" |
| | | @getMore="getMore" |
| | | @close="close" |
| | | @click="click" |
| | | @end="end" |
| | | ></u-column-notice> |
| | | </block> |
| | | </view> |
| | | </template> |
| | | <script> |
| | | /** |
| | | * noticeBar 滚动通知 |
| | | * @description 该组件用于滚动通告场景,有多种模式可供选择 |
| | | * @tutorial https://www.uviewui.com/components/noticeBar.html |
| | | * @property {Array} list 滚动内容,数组形式,见上方说明 |
| | | * @property {String} type 显示的主题(默认warning) |
| | | * @property {Boolean} volume-icon 是否显示小喇叭图标(默认true) |
| | | * @property {Boolean} more-icon 是否显示右边的向右箭头(默认false) |
| | | * @property {Boolean} close-icon 是否显示关闭图标(默认false) |
| | | * @property {Boolean} autoplay 是否自动播放(默认true) |
| | | * @property {String} color 文字颜色 |
| | | * @property {String Number} bg-color 背景颜色 |
| | | * @property {String} mode 滚动模式(默认horizontal) |
| | | * @property {Boolean} show 是否显示(默认true) |
| | | * @property {String Number} font-size 字体大小,单位rpx(默认28) |
| | | * @property {String Number} volume-size 左边喇叭的大小(默认34) |
| | | * @property {String Number} duration 滚动周期时长,只对步进模式有效,横向衔接模式无效,单位ms(默认2000) |
| | | * @property {String Number} speed 水平滚动时的滚动速度,即每秒移动多少距离,只对水平衔接方式有效,单位rpx(默认160) |
| | | * @property {String Number} font-size 字体大小,单位rpx(默认28) |
| | | * @property {Boolean} is-circular mode为horizontal时,指明是否水平衔接滚动(默认true) |
| | | * @property {String} play-state 播放状态,play - 播放,paused - 暂停(默认play) |
| | | * @property {String Nubmer} border-radius 通知栏圆角(默认为0) |
| | | * @property {String Nubmer} padding 内边距,字符串,与普通的内边距css写法一直(默认"18rpx 24rpx") |
| | | * @property {Boolean} no-list-hidden 列表为空时,是否显示组件(默认false) |
| | | * @property {Boolean} disable-touch 是否禁止通过手动滑动切换通知,只有mode = vertical,或者mode = horizontal且is-circular = false时有效(默认true) |
| | | * @event {Function} click 点击通告文字触发,只有mode = vertical,或者mode = horizontal且is-circular = false时有效 |
| | | * @event {Function} close 点击右侧关闭图标触发 |
| | | * @event {Function} getMore 点击右侧向右图标触发 |
| | | * @event {Function} end 列表的消息每次被播放一个周期时触发,只有mode = vertical,或者mode = horizontal且is-circular = false时有效 |
| | | * @example <u-notice-bar :more-icon="true" :list="list"></u-notice-bar> |
| | | */ |
| | | export default { |
| | | name: "u-notice-bar", |
| | | props: { |
| | | // 显示的内容,数组 |
| | | list: { |
| | | type: Array, |
| | | default() { |
| | | return []; |
| | | } |
| | | }, |
| | | // 显示的主题,success|error|primary|info|warning |
| | | type: { |
| | | type: String, |
| | | default: 'warning' |
| | | }, |
| | | // 是否显示左侧的音量图标 |
| | | volumeIcon: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 音量喇叭的大小 |
| | | volumeSize: { |
| | | type: [Number, String], |
| | | default: 34 |
| | | }, |
| | | // 是否显示右侧的右箭头图标 |
| | | moreIcon: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否显示右侧的关闭图标 |
| | | closeIcon: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否自动播放 |
| | | autoplay: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 文字颜色,各图标也会使用文字颜色 |
| | | color: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | // 滚动方向,horizontal-水平滚动,vertical-垂直滚动 |
| | | mode: { |
| | | type: String, |
| | | default: 'horizontal' |
| | | }, |
| | | // 是否显示 |
| | | show: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 字体大小,单位rpx |
| | | fontSize: { |
| | | type: [Number, String], |
| | | default: 28 |
| | | }, |
| | | // 滚动一个周期的时间长,单位ms |
| | | duration: { |
| | | type: [Number, String], |
| | | default: 2000 |
| | | }, |
| | | // 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度 |
| | | speed: { |
| | | type: [Number, String], |
| | | default: 160 |
| | | }, |
| | | // 水平滚动时,是否采用衔接形式滚动 |
| | | // 水平衔接模式,采用的是swiper组件,水平滚动 |
| | | isCircular: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 播放状态,play-播放,paused-暂停 |
| | | playState: { |
| | | type: String, |
| | | default: 'play' |
| | | }, |
| | | // 是否禁止用手滑动切换 |
| | | // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 |
| | | disableTouch: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 滚动通知设置圆角 |
| | | borderRadius: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | }, |
| | | // 通知的边距 |
| | | padding: { |
| | | type: [Number, String], |
| | | default: '18rpx 24rpx' |
| | | }, |
| | | // list列表为空时,是否显示组件 |
| | | noListHidden: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | computed: { |
| | | // 如果设置show为false,或者设置了noListHidden为true,且list长度又为零的话,隐藏组件 |
| | | isShow() { |
| | | if(this.show == false || (this.noListHidden == true && this.list.length == 0)) return false; |
| | | else return true; |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击通告栏 |
| | | click(index) { |
| | | this.$emit('click', index); |
| | | }, |
| | | // 点击关闭按钮 |
| | | close() { |
| | | this.$emit('close'); |
| | | }, |
| | | // 点击更多箭头按钮 |
| | | getMore() { |
| | | this.$emit('getMore'); |
| | | }, |
| | | // 滚动一个周期结束,只对垂直,或者水平步进形式有效 |
| | | end() { |
| | | this.$emit('end'); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-notice-bar-wrap { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-notice-bar { |
| | | padding: 18rpx 24rpx; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-direction-row { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .u-left-icon { |
| | | @include vue-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-notice-box { |
| | | flex: 1; |
| | | @include vue-flex; |
| | | overflow: hidden; |
| | | margin-left: 12rpx; |
| | | } |
| | | |
| | | .u-right-icon { |
| | | margin-left: 12rpx; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-notice-content { |
| | | line-height: 1; |
| | | white-space: nowrap; |
| | | font-size: 26rpx; |
| | | animation: u-loop-animation 10s linear infinite both; |
| | | text-align: right; |
| | | // 这一句很重要,为了能让滚动左右连接起来 |
| | | padding-left: 100%; |
| | | } |
| | | |
| | | @keyframes u-loop-animation { |
| | | 0% { |
| | | transform: translate3d(0, 0, 0); |
| | | } |
| | | |
| | | 100% { |
| | | transform: translate3d(-100%, 0, 0); |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-numberbox"> |
| | | <view class="u-icon-minus" @touchstart.stop.prevent="btnTouchStart('minus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal <= min }" |
| | | :style="{ |
| | | background: bgColor, |
| | | height: inputHeight + 'rpx', |
| | | color: color |
| | | }"> |
| | | <u-icon name="minus" :size="size"></u-icon> |
| | | </view> |
| | | <input :disabled="disabledInput || disabled" :cursor-spacing="getCursorSpacing" :class="{ 'u-input-disabled': disabled }" |
| | | v-model="inputVal" class="u-number-input" @blur="onBlur" @focus="onFocus" |
| | | type="number" :style="{ |
| | | color: color, |
| | | fontSize: size + 'rpx', |
| | | background: bgColor, |
| | | height: inputHeight + 'rpx', |
| | | width: inputWidth + 'rpx' |
| | | }" /> |
| | | <view class="u-icon-plus" @touchstart.stop.prevent="btnTouchStart('plus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal >= max }" |
| | | :style="{ |
| | | background: bgColor, |
| | | height: inputHeight + 'rpx', |
| | | color: color |
| | | }"> |
| | | <u-icon name="plus" :size="size"></u-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | /** |
| | | * numberBox 步进器 |
| | | * @description 该组件一般用于商城购物选择物品数量的场景。注意:该输入框只能输入大于或等于0的整数,不支持小数输入 |
| | | * @tutorial https://www.uviewui.com/components/numberBox.html |
| | | * @property {Number} value 输入框初始值(默认1) |
| | | * @property {String} bg-color 输入框和按钮的背景颜色(默认#F2F3F5) |
| | | * @property {Number} min 用户可输入的最小值(默认0) |
| | | * @property {Number} max 用户可输入的最大值(默认99999) |
| | | * @property {Number} step 步长,每次加或减的值(默认1) |
| | | * @property {Boolean} disabled 是否禁用操作,禁用后无法加减或手动修改输入框的值(默认false) |
| | | * @property {Boolean} disabled-input 是否禁止输入框手动输入值(默认false) |
| | | * @property {Boolean} positive-integer 是否只能输入正整数(默认true) |
| | | * @property {String | Number} size 输入框文字和按钮字体大小,单位rpx(默认26) |
| | | * @property {String} color 输入框文字和加减按钮图标的颜色(默认#323233) |
| | | * @property {String | Number} input-width 输入框宽度,单位rpx(默认80) |
| | | * @property {String | Number} input-height 输入框和按钮的高度,单位rpx(默认50) |
| | | * @property {String | Number} index 事件回调时用以区分当前发生变化的是哪个输入框 |
| | | * @property {Boolean} long-press 是否开启长按连续递增或递减(默认true) |
| | | * @property {String | Number} press-time 开启长按触发后,每触发一次需要多久,单位ms(默认250) |
| | | * @property {String | Number} cursor-spacing 指定光标于键盘的距离,避免键盘遮挡输入框,单位rpx(默认200) |
| | | * @event {Function} change 输入框内容发生变化时触发,对象形式 |
| | | * @event {Function} blur 输入框失去焦点时触发,对象形式 |
| | | * @event {Function} minus 点击减少按钮时触发(按钮可点击情况下),对象形式 |
| | | * @event {Function} plus 点击增加按钮时触发(按钮可点击情况下),对象形式 |
| | | * @example <u-number-box :min="1" :max="100"></u-number-box> |
| | | */ |
| | | export default { |
| | | name: "u-number-box", |
| | | props: { |
| | | // 预显示的数字 |
| | | value: { |
| | | type: Number, |
| | | default: 1 |
| | | }, |
| | | // 背景颜色 |
| | | bgColor: { |
| | | type: String, |
| | | default: '#F2F3F5' |
| | | }, |
| | | // 最小值 |
| | | min: { |
| | | type: Number, |
| | | default: 0 |
| | | }, |
| | | // 最大值 |
| | | max: { |
| | | type: Number, |
| | | default: 99999 |
| | | }, |
| | | // 步进值,每次加或减的值 |
| | | step: { |
| | | type: Number, |
| | | default: 1 |
| | | }, |
| | | // 是否禁用加减操作 |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // input的字体大小,单位rpx |
| | | size: { |
| | | type: [Number, String], |
| | | default: 26 |
| | | }, |
| | | // 加减图标的颜色 |
| | | color: { |
| | | type: String, |
| | | default: '#323233' |
| | | }, |
| | | // input宽度,单位rpx |
| | | inputWidth: { |
| | | type: [Number, String], |
| | | default: 80 |
| | | }, |
| | | // input高度,单位rpx |
| | | inputHeight: { |
| | | type: [Number, String], |
| | | default: 50 |
| | | }, |
| | | // index索引,用于列表中使用,让用户知道是哪个numberbox发生了变化,一般使用for循环出来的index值即可 |
| | | index: { |
| | | type: [Number, String], |
| | | default: '' |
| | | }, |
| | | // 是否禁用输入框,与disabled作用于输入框时,为OR的关系,即想要禁用输入框,又可以加减的话 |
| | | // 设置disabled为false,disabledInput为true即可 |
| | | disabledInput: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 输入框于键盘之间的距离 |
| | | cursorSpacing: { |
| | | type: [Number, String], |
| | | default: 100 |
| | | }, |
| | | // 是否开启长按连续递增或递减 |
| | | longPress: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 开启长按触发后,每触发一次需要多久 |
| | | pressTime: { |
| | | type: [Number, String], |
| | | default: 250 |
| | | }, |
| | | // 是否只能输入大于或等于0的整数(正整数) |
| | | positiveInteger: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | watch: { |
| | | value(v1, v2) { |
| | | // 只有value的改变是来自外部的时候,才去同步inputVal的值,否则会造成循环错误 |
| | | if(!this.changeFromInner) { |
| | | this.inputVal = v1; |
| | | // 因为inputVal变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true, |
| | | // 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处 |
| | | // 将changeFromInner设置为false |
| | | this.$nextTick(function(){ |
| | | this.changeFromInner = false; |
| | | }) |
| | | } |
| | | }, |
| | | inputVal(v1, v2) { |
| | | // 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串 |
| | | if (v1 == '') return; |
| | | let value = 0; |
| | | // 首先判断是否数值,并且在min和max之间,如果不是,使用原来值 |
| | | let tmp = this.$u.test.number(v1); |
| | | if (tmp && v1 >= this.min && v1 <= this.max) value = v1; |
| | | else value = v2; |
| | | // 判断是否只能输入大于等于0的整数 |
| | | if(this.positiveInteger) { |
| | | // 小于0,或者带有小数点, |
| | | if(v1 < 0 || String(v1).indexOf('.') !== -1) { |
| | | value = v2; |
| | | // 双向绑定input的值,必须要使用$nextTick修改显示的值 |
| | | this.$nextTick(() => { |
| | | this.inputVal = v2; |
| | | }) |
| | | } |
| | | } |
| | | // 发出change事件 |
| | | this.handleChange(value, 'change'); |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | inputVal: 1, // 输入框中的值,不能直接使用props中的value,因为应该改变props的状态 |
| | | timer: null, // 用作长按的定时器 |
| | | changeFromInner: false, // 值发生变化,是来自内部还是外部 |
| | | innerChangeTimer: null, // 内部定时器 |
| | | }; |
| | | }, |
| | | created() { |
| | | this.inputVal = Number(this.value); |
| | | }, |
| | | computed: { |
| | | getCursorSpacing() { |
| | | // 先将值转为px单位,再转为数值 |
| | | return Number(uni.upx2px(this.cursorSpacing)); |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击退格键 |
| | | btnTouchStart(callback) { |
| | | // 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能 |
| | | this[callback](); |
| | | // 如果没开启长按功能,直接返回 |
| | | if (!this.longPress) return; |
| | | clearInterval(this.timer); //再次清空定时器,防止重复注册定时器 |
| | | this.timer = null; |
| | | this.timer = setInterval(() => { |
| | | // 执行加或减函数 |
| | | this[callback](); |
| | | }, this.pressTime); |
| | | }, |
| | | clearTimer() { |
| | | this.$nextTick(() => { |
| | | clearInterval(this.timer); |
| | | this.timer = null; |
| | | }) |
| | | }, |
| | | minus() { |
| | | this.computeVal('minus'); |
| | | }, |
| | | plus() { |
| | | this.computeVal('plus'); |
| | | }, |
| | | // 为了保证小数相加减出现精度溢出的问题 |
| | | calcPlus(num1, num2) { |
| | | let baseNum, baseNum1, baseNum2; |
| | | try { |
| | | baseNum1 = num1.toString().split('.')[1].length; |
| | | } catch (e) { |
| | | baseNum1 = 0; |
| | | } |
| | | try { |
| | | baseNum2 = num2.toString().split('.')[1].length; |
| | | } catch (e) { |
| | | baseNum2 = 0; |
| | | } |
| | | baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)); |
| | | let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; //精度 |
| | | return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision); |
| | | }, |
| | | // 为了保证小数相加减出现精度溢出的问题 |
| | | calcMinus(num1, num2) { |
| | | let baseNum, baseNum1, baseNum2; |
| | | try { |
| | | baseNum1 = num1.toString().split('.')[1].length; |
| | | } catch (e) { |
| | | baseNum1 = 0; |
| | | } |
| | | try { |
| | | baseNum2 = num2.toString().split('.')[1].length; |
| | | } catch (e) { |
| | | baseNum2 = 0; |
| | | } |
| | | baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)); |
| | | let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; |
| | | return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision); |
| | | }, |
| | | computeVal(type) { |
| | | uni.hideKeyboard(); |
| | | if (this.disabled) return; |
| | | let value = 0; |
| | | // 减 |
| | | if (type === 'minus') { |
| | | value = this.calcMinus(this.inputVal, this.step); |
| | | } else if (type === 'plus') { |
| | | value = this.calcPlus(this.inputVal, this.step); |
| | | } |
| | | // 判断是否小于最小值和大于最大值 |
| | | if (value < this.min || value > this.max) { |
| | | return; |
| | | } |
| | | this.inputVal = value; |
| | | this.handleChange(value, type); |
| | | }, |
| | | // 处理用户手动输入的情况 |
| | | onBlur(event) { |
| | | let val = 0; |
| | | let value = event.detail.value; |
| | | // 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值 |
| | | // 这里不直接判断是否正整数,是因为用户传递的props min值可能为0 |
| | | if (!/(^\d+$)/.test(value) || value[0] == 0) val = this.min; |
| | | val = +value; |
| | | if (val > this.max) { |
| | | val = this.max; |
| | | } else if (val < this.min) { |
| | | val = this.min; |
| | | } |
| | | this.$nextTick(() => { |
| | | this.inputVal = val; |
| | | }) |
| | | this.handleChange(val, 'blur'); |
| | | }, |
| | | // 输入框获得焦点事件 |
| | | onFocus() { |
| | | this.$emit('focus'); |
| | | }, |
| | | handleChange(value, type) { |
| | | if (this.disabled) return; |
| | | // 清除定时器,避免造成混乱 |
| | | if(this.innerChangeTimer) { |
| | | clearTimeout(this.innerChangeTimer); |
| | | this.innerChangeTimer = null; |
| | | } |
| | | // 发出input事件,修改通过v-model绑定的值,达到双向绑定的效果 |
| | | this.changeFromInner = true; |
| | | // 一定时间内,清除changeFromInner标记,否则内部值改变后 |
| | | // 外部通过程序修改value值,将会无效 |
| | | this.innerChangeTimer = setTimeout(() => { |
| | | this.changeFromInner = false; |
| | | }, 150); |
| | | this.$emit('input', Number(value)); |
| | | this.$emit(type, { |
| | | // 转为Number类型 |
| | | value: Number(value), |
| | | index: this.index |
| | | }) |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-numberbox { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-number-input { |
| | | position: relative; |
| | | text-align: center; |
| | | padding: 0; |
| | | margin: 0 6rpx; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .u-icon-plus, |
| | | .u-icon-minus { |
| | | width: 60rpx; |
| | | @include vue-flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | .u-icon-plus { |
| | | border-radius: 0 8rpx 8rpx 0; |
| | | } |
| | | |
| | | .u-icon-minus { |
| | | border-radius: 8rpx 0 0 8rpx; |
| | | } |
| | | |
| | | .u-icon-disabled { |
| | | color: #c8c9cc !important; |
| | | background: #f7f8fa !important; |
| | | } |
| | | |
| | | .u-input-disabled { |
| | | color: #c8c9cc !important; |
| | | background-color: #f2f3f5 !important; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="u-keyboard" @touchmove.stop.prevent="() => {}"> |
| | | <view class="u-keyboard-grids"> |
| | | <view |
| | | class="u-keyboard-grids-item" |
| | | :class="[btnBgGray(index) ? 'u-bg-gray' : '', index <= 2 ? 'u-border-top' : '', index < 9 ? 'u-border-bottom' : '', (index + 1) % 3 != 0 ? 'u-border-right' : '']" |
| | | :style="[itemStyle(index)]" |
| | | v-for="(item, index) in numList" |
| | | :key="index" |
| | | :hover-class="hoverClass(index)" |
| | | :hover-stay-time="100" |
| | | @tap="keyboardClick(item)"> |
| | | <view class="u-keyboard-grids-btn">{{ item }}</view> |
| | | </view> |
| | | <view class="u-keyboard-grids-item u-bg-gray" hover-class="u-hover-class" :hover-stay-time="100" @touchstart.stop="backspaceClick" |
| | | @touchend="clearTimer"> |
| | | <view class="u-keyboard-back u-keyboard-grids-btn"> |
| | | <u-icon name="backspace" :size="38" :bold="true"></u-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | props: { |
| | | // 键盘的类型,number-数字键盘,card-身份证键盘 |
| | | mode: { |
| | | type: String, |
| | | default: 'number' |
| | | }, |
| | | // 是否显示键盘的"."符号 |
| | | dotEnabled: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否打乱键盘按键的顺序 |
| | | random: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | backspace: 'backspace', // 退格键内容 |
| | | dot: '.', // 点 |
| | | timer: null, // 长按多次删除的事件监听 |
| | | cardX: 'X' // 身份证的X符号 |
| | | }; |
| | | }, |
| | | computed: { |
| | | // 键盘需要显示的内容 |
| | | numList() { |
| | | let tmp = []; |
| | | if (!this.dotEnabled && this.mode == 'number') { |
| | | if (!this.random) { |
| | | return [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; |
| | | } else { |
| | | return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); |
| | | } |
| | | } else if (this.dotEnabled && this.mode == 'number') { |
| | | if (!this.random) { |
| | | return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]; |
| | | } else { |
| | | return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]); |
| | | } |
| | | } else if (this.mode == 'card') { |
| | | if (!this.random) { |
| | | return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]; |
| | | } else { |
| | | return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]); |
| | | } |
| | | } |
| | | }, |
| | | // 按键的样式,在非乱序&&数字键盘&&不显示点按钮时,index为9时,按键占位两个空间 |
| | | itemStyle() { |
| | | return index => { |
| | | let style = {}; |
| | | if (this.mode == 'number' && !this.dotEnabled && index == 9) style.flex = '0 0 66.6666666666%'; |
| | | return style; |
| | | }; |
| | | }, |
| | | // 是否让按键显示灰色,只在非乱序&&数字键盘&&且允许点按键的时候 |
| | | btnBgGray() { |
| | | return index => { |
| | | if (!this.random && index == 9 && (this.mode != 'number' || (this.mode == 'number' && this.dotEnabled))) return true; |
| | | else return false; |
| | | }; |
| | | }, |
| | | hoverClass() { |
| | | return index => { |
| | | if (!this.random && index == 9 && (this.mode == 'number' && this.dotEnabled || this.mode == 'card')) return 'u-hover-class'; |
| | | else return 'u-keyboard-hover'; |
| | | } |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击退格键 |
| | | backspaceClick() { |
| | | this.$emit('backspace'); |
| | | clearInterval(this.timer); //再次清空定时器,防止重复注册定时器 |
| | | this.timer = null; |
| | | this.timer = setInterval(() => { |
| | | this.$emit('backspace'); |
| | | }, 250); |
| | | }, |
| | | clearTimer() { |
| | | clearInterval(this.timer); |
| | | this.timer = null; |
| | | }, |
| | | // 获取键盘显示的内容 |
| | | keyboardClick(val) { |
| | | // 允许键盘显示点模式和触发非点按键时,将内容转为数字类型 |
| | | if (this.dotEnabled && val != this.dot && val != this.cardX) val = Number(val); |
| | | this.$emit('change', val); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/style.components.scss"; |
| | | |
| | | .u-keyboard { |
| | | position: relative; |
| | | z-index: 1003; |
| | | } |
| | | |
| | | .u-keyboard-grids { |
| | | @include vue-flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .u-keyboard-grids-item { |
| | | flex: 0 0 33.3333333333%; |
| | | text-align: center; |
| | | font-size: 50rpx; |
| | | color: #333; |
| | | @include vue-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 110rpx; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .u-bg-gray { |
| | | background-color: $u-border-color; |
| | | } |
| | | |
| | | .u-keyboard-back { |
| | | font-size: 36rpx; |
| | | } |
| | | |
| | | .u-keyboard-hover { |
| | | background-color: #e7e6eb; |
| | | } |
| | | </style> |
| New file |
| | |
| | | const cfg = require('./config.js'), |
| | | isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); |
| | | |
| | | function CssHandler(tagStyle) { |
| | | var styles = Object.assign(Object.create(null), cfg.userAgentStyles); |
| | | for (var item in tagStyle) |
| | | styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item]; |
| | | this.styles = styles; |
| | | } |
| | | CssHandler.prototype.getStyle = function(data) { |
| | | this.styles = new parser(data, this.styles).parse(); |
| | | } |
| | | CssHandler.prototype.match = function(name, attrs) { |
| | | var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : ''; |
| | | if (attrs.class) { |
| | | var items = attrs.class.split(' '); |
| | | for (var i = 0, item; item = items[i]; i++) |
| | | if (tmp = this.styles['.' + item]) |
| | | matched += tmp + ';'; |
| | | } |
| | | if (tmp = this.styles['#' + attrs.id]) |
| | | matched += tmp + ';'; |
| | | return matched; |
| | | } |
| | | module.exports = CssHandler; |
| | | |
| | | function parser(data, init) { |
| | | this.data = data; |
| | | this.floor = 0; |
| | | this.i = 0; |
| | | this.list = []; |
| | | this.res = init; |
| | | this.state = this.Space; |
| | | } |
| | | parser.prototype.parse = function() { |
| | | for (var c; c = this.data[this.i]; this.i++) |
| | | this.state(c); |
| | | return this.res; |
| | | } |
| | | parser.prototype.section = function() { |
| | | return this.data.substring(this.start, this.i); |
| | | } |
| | | // 状态机 |
| | | parser.prototype.Space = function(c) { |
| | | if (c == '.' || c == '#' || isLetter(c)) { |
| | | this.start = this.i; |
| | | this.state = this.Name; |
| | | } else if (c == '/' && this.data[this.i + 1] == '*') |
| | | this.Comment(); |
| | | else if (!cfg.blankChar[c] && c != ';') |
| | | this.state = this.Ignore; |
| | | } |
| | | parser.prototype.Comment = function() { |
| | | this.i = this.data.indexOf('*/', this.i) + 1; |
| | | if (!this.i) this.i = this.data.length; |
| | | this.state = this.Space; |
| | | } |
| | | parser.prototype.Ignore = function(c) { |
| | | if (c == '{') this.floor++; |
| | | else if (c == '}' && !--this.floor) { |
| | | this.list = []; |
| | | this.state = this.Space; |
| | | } |
| | | } |
| | | parser.prototype.Name = function(c) { |
| | | if (cfg.blankChar[c]) { |
| | | this.list.push(this.section()); |
| | | this.state = this.NameSpace; |
| | | } else if (c == '{') { |
| | | this.list.push(this.section()); |
| | | this.Content(); |
| | | } else if (c == ',') { |
| | | this.list.push(this.section()); |
| | | this.Comma(); |
| | | } else if (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_') |
| | | this.state = this.Ignore; |
| | | } |
| | | parser.prototype.NameSpace = function(c) { |
| | | if (c == '{') this.Content(); |
| | | else if (c == ',') this.Comma(); |
| | | else if (!cfg.blankChar[c]) this.state = this.Ignore; |
| | | } |
| | | parser.prototype.Comma = function() { |
| | | while (cfg.blankChar[this.data[++this.i]]); |
| | | if (this.data[this.i] == '{') this.Content(); |
| | | else { |
| | | this.start = this.i--; |
| | | this.state = this.Name; |
| | | } |
| | | } |
| | | parser.prototype.Content = function() { |
| | | this.start = ++this.i; |
| | | if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length; |
| | | var content = this.section(); |
| | | for (var i = 0, item; item = this.list[i++];) |
| | | if (this.res[item]) this.res[item] += ';' + content; |
| | | else this.res[item] = content; |
| | | this.list = []; |
| | | this.state = this.Space; |
| | | } |
| New file |
| | |
| | | /** |
| | | * html 解析器 |
| | | * @tutorial https://github.com/jin-yufeng/Parser |
| | | * @version 20201029 |
| | | * @author JinYufeng |
| | | * @listens MIT |
| | | */ |
| | | const cfg = require('./config.js'), |
| | | blankChar = cfg.blankChar, |
| | | CssHandler = require('./CssHandler.js'), |
| | | windowWidth = uni.getSystemInfoSync().windowWidth; |
| | | var emoji; |
| | | |
| | | function MpHtmlParser(data, options = {}) { |
| | | this.attrs = {}; |
| | | this.CssHandler = new CssHandler(options.tagStyle, windowWidth); |
| | | this.data = data; |
| | | this.domain = options.domain; |
| | | this.DOM = []; |
| | | this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0; |
| | | options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http'; |
| | | this.options = options; |
| | | this.state = this.Text; |
| | | this.STACK = []; |
| | | // 工具函数 |
| | | this.bubble = () => { |
| | | for (var i = this.STACK.length, item; item = this.STACK[--i];) { |
| | | if (cfg.richOnlyTags[item.name]) return false; |
| | | item.c = 1; |
| | | } |
| | | return true; |
| | | } |
| | | this.decode = (val, amp) => { |
| | | var i = -1, |
| | | j, en; |
| | | while (1) { |
| | | if ((i = val.indexOf('&', i + 1)) == -1) break; |
| | | if ((j = val.indexOf(';', i + 2)) == -1) break; |
| | | if (val[i + 1] == '#') { |
| | | en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j)); |
| | | if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1); |
| | | } else { |
| | | en = val.substring(i + 1, j); |
| | | if (cfg.entities[en] || en == amp) |
| | | val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1); |
| | | } |
| | | } |
| | | return val; |
| | | } |
| | | this.getUrl = url => { |
| | | if (url[0] == '/') { |
| | | if (url[1] == '/') url = this.options.prot + ':' + url; |
| | | else if (this.domain) url = this.domain + url; |
| | | } else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://')) |
| | | url = this.domain + '/' + url; |
| | | return url; |
| | | } |
| | | this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>'); |
| | | this.section = () => this.data.substring(this.start, this.i); |
| | | this.parent = () => this.STACK[this.STACK.length - 1]; |
| | | this.siblings = () => this.STACK.length ? this.parent().children : this.DOM; |
| | | } |
| | | MpHtmlParser.prototype.parse = function() { |
| | | if (emoji) this.data = emoji.parseEmoji(this.data); |
| | | for (var c; c = this.data[this.i]; this.i++) |
| | | this.state(c); |
| | | if (this.state == this.Text) this.setText(); |
| | | while (this.STACK.length) this.popNode(this.STACK.pop()); |
| | | return this.DOM; |
| | | } |
| | | // 设置属性 |
| | | MpHtmlParser.prototype.setAttr = function() { |
| | | var name = this.attrName.toLowerCase(), |
| | | val = this.attrVal; |
| | | if (cfg.boolAttrs[name]) this.attrs[name] = 'T'; |
| | | else if (val) { |
| | | if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp')); |
| | | else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp'); |
| | | else if (name.substr(0, 5) != 'data-') this.attrs[name] = val; |
| | | } |
| | | this.attrVal = ''; |
| | | while (blankChar[this.data[this.i]]) this.i++; |
| | | if (this.isClose()) this.setNode(); |
| | | else { |
| | | this.start = this.i; |
| | | this.state = this.AttrName; |
| | | } |
| | | } |
| | | // 设置文本节点 |
| | | MpHtmlParser.prototype.setText = function() { |
| | | var back, text = this.section(); |
| | | if (!text) return; |
| | | text = (cfg.onText && cfg.onText(text, () => back = true)) || text; |
| | | if (back) { |
| | | this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i); |
| | | let j = this.start + text.length; |
| | | for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]); |
| | | return; |
| | | } |
| | | if (!this.pre) { |
| | | // 合并空白符 |
| | | var flag, tmp = []; |
| | | for (let i = text.length, c; c = text[--i];) |
| | | if (!blankChar[c]) { |
| | | tmp.unshift(c); |
| | | if (!flag) flag = 1; |
| | | } else { |
| | | if (tmp[0] != ' ') tmp.unshift(' '); |
| | | if (c == '\n' && flag == void 0) flag = 0; |
| | | } |
| | | if (flag == 0) return; |
| | | text = tmp.join(''); |
| | | } |
| | | this.siblings().push({ |
| | | type: 'text', |
| | | text: this.decode(text) |
| | | }); |
| | | } |
| | | // 设置元素节点 |
| | | MpHtmlParser.prototype.setNode = function() { |
| | | var node = { |
| | | name: this.tagName.toLowerCase(), |
| | | attrs: this.attrs |
| | | }, |
| | | close = cfg.selfClosingTags[node.name]; |
| | | if (this.options.nodes.length) node.type = 'node'; |
| | | this.attrs = {}; |
| | | if (!cfg.ignoreTags[node.name]) { |
| | | // 处理属性 |
| | | var attrs = node.attrs, |
| | | style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''), |
| | | styleObj = {}; |
| | | if (attrs.id) { |
| | | if (this.options.compress & 1) attrs.id = void 0; |
| | | else if (this.options.useAnchor) this.bubble(); |
| | | } |
| | | if ((this.options.compress & 2) && attrs.class) attrs.class = void 0; |
| | | switch (node.name) { |
| | | case 'a': |
| | | case 'ad': // #ifdef APP-PLUS |
| | | case 'iframe': |
| | | // #endif |
| | | this.bubble(); |
| | | break; |
| | | case 'font': |
| | | if (attrs.color) { |
| | | styleObj['color'] = attrs.color; |
| | | attrs.color = void 0; |
| | | } |
| | | if (attrs.face) { |
| | | styleObj['font-family'] = attrs.face; |
| | | attrs.face = void 0; |
| | | } |
| | | if (attrs.size) { |
| | | var size = parseInt(attrs.size); |
| | | if (size < 1) size = 1; |
| | | else if (size > 7) size = 7; |
| | | var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large']; |
| | | styleObj['font-size'] = map[size - 1]; |
| | | attrs.size = void 0; |
| | | } |
| | | break; |
| | | case 'embed': |
| | | // #ifndef APP-PLUS |
| | | var src = node.attrs.src || '', |
| | | type = node.attrs.type || ''; |
| | | if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8')) |
| | | node.name = 'video'; |
| | | else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes( |
| | | '.aac')) |
| | | node.name = 'audio'; |
| | | else break; |
| | | if (node.attrs.autostart) |
| | | node.attrs.autoplay = 'T'; |
| | | node.attrs.controls = 'T'; |
| | | // #endif |
| | | // #ifdef APP-PLUS |
| | | this.bubble(); |
| | | break; |
| | | // #endif |
| | | case 'video': |
| | | case 'audio': |
| | | if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]); |
| | | else this[`${node.name}Num`]++; |
| | | if (node.name == 'video') { |
| | | if (this.videoNum > 3) |
| | | node.lazyLoad = 1; |
| | | if (attrs.width) { |
| | | styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px'); |
| | | attrs.width = void 0; |
| | | } |
| | | if (attrs.height) { |
| | | styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px'); |
| | | attrs.height = void 0; |
| | | } |
| | | } |
| | | if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T'; |
| | | attrs.source = []; |
| | | if (attrs.src) { |
| | | attrs.source.push(attrs.src); |
| | | attrs.src = void 0; |
| | | } |
| | | this.bubble(); |
| | | break; |
| | | case 'td': |
| | | case 'th': |
| | | if (attrs.colspan || attrs.rowspan) |
| | | for (var k = this.STACK.length, item; item = this.STACK[--k];) |
| | | if (item.name == 'table') { |
| | | item.flag = 1; |
| | | break; |
| | | } |
| | | } |
| | | if (attrs.align) { |
| | | if (node.name == 'table') { |
| | | if (attrs.align == 'center') styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'; |
| | | else styleObj['float'] = attrs.align; |
| | | } else styleObj['text-align'] = attrs.align; |
| | | attrs.align = void 0; |
| | | } |
| | | // 压缩 style |
| | | var styles = style.split(';'); |
| | | style = ''; |
| | | for (var i = 0, len = styles.length; i < len; i++) { |
| | | var info = styles[i].split(':'); |
| | | if (info.length < 2) continue; |
| | | let key = info[0].trim().toLowerCase(), |
| | | value = info.slice(1).join(':').trim(); |
| | | if (value[0] == '-' || value.includes('safe')) |
| | | style += `;${key}:${value}`; |
| | | else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) |
| | | styleObj[key] = value; |
| | | } |
| | | if (node.name == 'img') { |
| | | if (attrs.src && !attrs.ignore) { |
| | | if (this.bubble()) |
| | | attrs.i = (this.imgNum++).toString(); |
| | | else attrs.ignore = 'T'; |
| | | } |
| | | if (attrs.ignore) { |
| | | style += ';-webkit-touch-callout:none'; |
| | | styleObj['max-width'] = '100%'; |
| | | } |
| | | var width; |
| | | if (styleObj.width) width = styleObj.width; |
| | | else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : parseFloat(attrs.width) + 'px'; |
| | | if (width) { |
| | | styleObj.width = width; |
| | | attrs.width = '100%'; |
| | | if (parseInt(width) > windowWidth) { |
| | | styleObj.height = ''; |
| | | if (attrs.height) attrs.height = void 0; |
| | | } |
| | | } |
| | | if (styleObj.height) { |
| | | attrs.height = styleObj.height; |
| | | styleObj.height = ''; |
| | | } else if (attrs.height && !attrs.height.includes('%')) |
| | | attrs.height = parseFloat(attrs.height) + 'px'; |
| | | } |
| | | for (var key in styleObj) { |
| | | var value = styleObj[key]; |
| | | if (!value) continue; |
| | | if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1; |
| | | // 填充链接 |
| | | if (value.includes('url')) { |
| | | var j = value.indexOf('('); |
| | | if (j++ != -1) { |
| | | while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++; |
| | | value = value.substr(0, j) + this.getUrl(value.substr(j)); |
| | | } |
| | | } |
| | | // 转换 rpx |
| | | else if (value.includes('rpx')) |
| | | value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px'); |
| | | else if (key == 'white-space' && value.includes('pre') && !close) |
| | | this.pre = node.pre = true; |
| | | style += `;${key}:${value}`; |
| | | } |
| | | style = style.substr(1); |
| | | if (style) attrs.style = style; |
| | | if (!close) { |
| | | node.children = []; |
| | | if (node.name == 'pre' && cfg.highlight) { |
| | | this.remove(node); |
| | | this.pre = node.pre = true; |
| | | } |
| | | this.siblings().push(node); |
| | | this.STACK.push(node); |
| | | } else if (!cfg.filter || cfg.filter(node, this) != false) |
| | | this.siblings().push(node); |
| | | } else { |
| | | if (!close) this.remove(node); |
| | | else if (node.name == 'source') { |
| | | var parent = this.parent(); |
| | | if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src) |
| | | parent.attrs.source.push(node.attrs.src); |
| | | } else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href; |
| | | } |
| | | if (this.data[this.i] == '/') this.i++; |
| | | this.start = this.i + 1; |
| | | this.state = this.Text; |
| | | } |
| | | // 移除标签 |
| | | MpHtmlParser.prototype.remove = function(node) { |
| | | var name = node.name, |
| | | j = this.i; |
| | | // 处理 svg |
| | | var handleSvg = () => { |
| | | var src = this.data.substring(j, this.i + 1); |
| | | node.attrs.xmlns = 'http://www.w3.org/2000/svg'; |
| | | for (var key in node.attrs) { |
| | | if (key == 'viewbox') src = ` viewBox="${node.attrs.viewbox}"` + src; |
| | | else if (key != 'style') src = ` ${key}="${node.attrs[key]}"` + src; |
| | | } |
| | | src = '<svg' + src; |
| | | var parent = this.parent(); |
| | | if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline')) |
| | | parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style; |
| | | this.siblings().push({ |
| | | name: 'img', |
| | | attrs: { |
| | | src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'), |
| | | style: node.attrs.style, |
| | | ignore: 'T' |
| | | } |
| | | }) |
| | | } |
| | | if (node.name == 'svg' && this.data[j] == '/') return handleSvg(this.i++); |
| | | while (1) { |
| | | if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) { |
| | | if (name == 'pre' || name == 'svg') this.i = j; |
| | | else this.i = this.data.length; |
| | | return; |
| | | } |
| | | this.start = (this.i += 2); |
| | | while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++; |
| | | if (this.section().toLowerCase() == name) { |
| | | // 代码块高亮 |
| | | if (name == 'pre') { |
| | | this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data |
| | | .substr(this.i - 5); |
| | | return this.i = j; |
| | | } else if (name == 'style') |
| | | this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7)); |
| | | else if (name == 'title') |
| | | this.DOM.title = this.data.substring(j + 1, this.i - 7); |
| | | if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length; |
| | | if (name == 'svg') handleSvg(); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | // 节点出栈处理 |
| | | MpHtmlParser.prototype.popNode = function(node) { |
| | | // 空白符处理 |
| | | if (node.pre) { |
| | | node.pre = this.pre = void 0; |
| | | for (let i = this.STACK.length; i--;) |
| | | if (this.STACK[i].pre) |
| | | this.pre = true; |
| | | } |
| | | var siblings = this.siblings(), |
| | | len = siblings.length, |
| | | childs = node.children; |
| | | if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false)) |
| | | return siblings.pop(); |
| | | var attrs = node.attrs; |
| | | // 替换一些标签名 |
| | | if (cfg.blockTags[node.name]) node.name = 'div'; |
| | | else if (!cfg.trustTags[node.name]) node.name = 'span'; |
| | | // 处理列表 |
| | | if (node.c && (node.name == 'ul' || node.name == 'ol')) { |
| | | if ((node.attrs.style || '').includes('list-style:none')) { |
| | | for (let i = 0, child; child = childs[i++];) |
| | | if (child.name == 'li') |
| | | child.name = 'div'; |
| | | } else if (node.name == 'ul') { |
| | | var floor = 1; |
| | | for (let i = this.STACK.length; i--;) |
| | | if (this.STACK[i].name == 'ul') floor++; |
| | | if (floor != 1) |
| | | for (let i = childs.length; i--;) |
| | | childs[i].floor = floor; |
| | | } else { |
| | | for (let i = 0, num = 1, child; child = childs[i++];) |
| | | if (child.name == 'li') { |
| | | child.type = 'ol'; |
| | | child.num = ((num, type) => { |
| | | if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26); |
| | | if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26); |
| | | if (type == 'i' || type == 'I') { |
| | | num = (num - 1) % 99 + 1; |
| | | var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'], |
| | | ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'], |
| | | res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || ''); |
| | | if (type == 'i') return res.toLowerCase(); |
| | | return res; |
| | | } |
| | | return num; |
| | | })(num++, attrs.type) + '.'; |
| | | } |
| | | } |
| | | } |
| | | // 处理表格 |
| | | if (node.name == 'table') { |
| | | var padding = parseFloat(attrs.cellpadding), |
| | | spacing = parseFloat(attrs.cellspacing), |
| | | border = parseFloat(attrs.border); |
| | | if (node.c) { |
| | | if (isNaN(padding)) padding = 2; |
| | | if (isNaN(spacing)) spacing = 2; |
| | | } |
| | | if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`; |
| | | if (node.flag && node.c) { |
| | | // 有 colspan 或 rowspan 且含有链接的表格转为 grid 布局实现 |
| | | attrs.style = `${attrs.style || ''};${spacing ? `;grid-gap:${spacing}px` : ';border-left:0;border-top:0'}`; |
| | | var row = 1, |
| | | col = 1, |
| | | colNum, |
| | | trs = [], |
| | | children = [], |
| | | map = {}; |
| | | (function f(ns) { |
| | | for (var i = 0; i < ns.length; i++) { |
| | | if (ns[i].name == 'tr') trs.push(ns[i]); |
| | | else f(ns[i].children || []); |
| | | } |
| | | })(node.children) |
| | | for (let i = 0; i < trs.length; i++) { |
| | | for (let j = 0, td; td = trs[i].children[j]; j++) { |
| | | if (td.name == 'td' || td.name == 'th') { |
| | | while (map[row + '.' + col]) col++; |
| | | var cell = { |
| | | name: 'div', |
| | | c: 1, |
| | | attrs: { |
| | | style: (td.attrs.style || '') + (border ? `;border:${border}px solid gray` + (spacing ? '' : |
| | | ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') |
| | | }, |
| | | children: td.children |
| | | } |
| | | if (td.attrs.colspan) { |
| | | cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + parseInt(td.attrs.colspan)); |
| | | if (!td.attrs.rowspan) cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + 1); |
| | | col += parseInt(td.attrs.colspan) - 1; |
| | | } |
| | | if (td.attrs.rowspan) { |
| | | cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + parseInt(td.attrs.rowspan)); |
| | | if (!td.attrs.colspan) cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + 1); |
| | | for (var k = 1; k < td.attrs.rowspan; k++) map[(row + k) + '.' + col] = 1; |
| | | } |
| | | children.push(cell); |
| | | col++; |
| | | } |
| | | } |
| | | if (!colNum) { |
| | | colNum = col - 1; |
| | | attrs.style += `;grid-template-columns:repeat(${colNum},auto)` |
| | | } |
| | | col = 1; |
| | | row++; |
| | | } |
| | | node.children = children; |
| | | } else { |
| | | attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`; |
| | | if (border || padding) |
| | | (function f(ns) { |
| | | for (var i = 0, n; n = ns[i]; i++) { |
| | | if (n.name == 'th' || n.name == 'td') { |
| | | if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style || ''}`; |
| | | if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style || ''}`; |
| | | } else f(n.children || []); |
| | | } |
| | | })(childs) |
| | | } |
| | | if (this.options.autoscroll) { |
| | | var table = Object.assign({}, node); |
| | | node.name = 'div'; |
| | | node.attrs = { |
| | | style: 'overflow:scroll' |
| | | } |
| | | node.children = [table]; |
| | | } |
| | | } |
| | | this.CssHandler.pop && this.CssHandler.pop(node); |
| | | // 自动压缩 |
| | | if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div') |
| | | siblings[len - 1] = childs[0]; |
| | | } |
| | | // 状态机 |
| | | MpHtmlParser.prototype.Text = function(c) { |
| | | if (c == '<') { |
| | | var next = this.data[this.i + 1], |
| | | isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); |
| | | if (isLetter(next)) { |
| | | this.setText(); |
| | | this.start = this.i + 1; |
| | | this.state = this.TagName; |
| | | } else if (next == '/') { |
| | | this.setText(); |
| | | if (isLetter(this.data[++this.i + 1])) { |
| | | this.start = this.i + 1; |
| | | this.state = this.EndTag; |
| | | } else this.Comment(); |
| | | } else if (next == '!' || next == '?') { |
| | | this.setText(); |
| | | this.Comment(); |
| | | } |
| | | } |
| | | } |
| | | MpHtmlParser.prototype.Comment = function() { |
| | | var key; |
| | | if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->'; |
| | | else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>'; |
| | | else key = '>'; |
| | | if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length; |
| | | else this.i += key.length - 1; |
| | | this.start = this.i + 1; |
| | | this.state = this.Text; |
| | | } |
| | | MpHtmlParser.prototype.TagName = function(c) { |
| | | if (blankChar[c]) { |
| | | this.tagName = this.section(); |
| | | while (blankChar[this.data[this.i]]) this.i++; |
| | | if (this.isClose()) this.setNode(); |
| | | else { |
| | | this.start = this.i; |
| | | this.state = this.AttrName; |
| | | } |
| | | } else if (this.isClose()) { |
| | | this.tagName = this.section(); |
| | | this.setNode(); |
| | | } |
| | | } |
| | | MpHtmlParser.prototype.AttrName = function(c) { |
| | | if (c == '=' || blankChar[c] || this.isClose()) { |
| | | this.attrName = this.section(); |
| | | if (blankChar[c]) |
| | | while (blankChar[this.data[++this.i]]); |
| | | if (this.data[this.i] == '=') { |
| | | while (blankChar[this.data[++this.i]]); |
| | | this.start = this.i--; |
| | | this.state = this.AttrValue; |
| | | } else this.setAttr(); |
| | | } |
| | | } |
| | | MpHtmlParser.prototype.AttrValue = function(c) { |
| | | if (c == '"' || c == "'") { |
| | | this.start++; |
| | | if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length; |
| | | this.attrVal = this.section(); |
| | | this.i++; |
| | | } else { |
| | | for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++); |
| | | this.attrVal = this.section(); |
| | | } |
| | | this.setAttr(); |
| | | } |
| | | MpHtmlParser.prototype.EndTag = function(c) { |
| | | if (blankChar[c] || c == '>' || c == '/') { |
| | | var name = this.section().toLowerCase(); |
| | | for (var i = this.STACK.length; i--;) |
| | | if (this.STACK[i].name == name) break; |
| | | if (i != -1) { |
| | | var node; |
| | | while ((node = this.STACK.pop()).name != name) this.popNode(node); |
| | | this.popNode(node); |
| | | } else if (name == 'p' || name == 'br') |
| | | this.siblings().push({ |
| | | name, |
| | | attrs: {} |
| | | }); |
| | | this.i = this.data.indexOf('>', this.i); |
| | | this.start = this.i + 1; |
| | | if (this.i == -1) this.i = this.data.length; |
| | | else this.state = this.Text; |
| | | } |
| | | } |
| | | module.exports = MpHtmlParser; |
| New file |
| | |
| | | /* 配置文件 */ |
| | | var cfg = { |
| | | // 出错占位图 |
| | | errorImg: null, |
| | | // 过滤器函数 |
| | | filter: null, |
| | | // 代码高亮函数 |
| | | highlight: null, |
| | | // 文本处理函数 |
| | | onText: null, |
| | | // 实体编码列表 |
| | | entities: { |
| | | quot: '"', |
| | | apos: "'", |
| | | semi: ';', |
| | | nbsp: '\xA0', |
| | | ensp: '\u2002', |
| | | emsp: '\u2003', |
| | | ndash: '–', |
| | | mdash: '—', |
| | | middot: '·', |
| | | lsquo: '‘', |
| | | rsquo: '’', |
| | | ldquo: '“', |
| | | rdquo: '”', |
| | | bull: '•', |
| | | hellip: '…' |
| | | }, |
| | | blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'), |
| | | boolAttrs: makeMap('allowfullscreen,autoplay,autostart,controls,ignore,loop,muted'), |
| | | // 块级标签,将被转为 div |
| | | blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'), |
| | | // 将被移除的标签 |
| | | ignoreTags: makeMap('area,base,canvas,frame,iframe,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'), |
| | | // 只能被 rich-text 显示的标签 |
| | | richOnlyTags: makeMap('a,colgroup,fieldset,legend'), |
| | | // 自闭合的标签 |
| | | selfClosingTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'), |
| | | // 信任的标签 |
| | | trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'), |
| | | // 默认的标签样式 |
| | | userAgentStyles: { |
| | | address: 'font-style:italic', |
| | | big: 'display:inline;font-size:1.2em', |
| | | blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px', |
| | | caption: 'display:table-caption;text-align:center', |
| | | center: 'text-align:center', |
| | | cite: 'font-style:italic', |
| | | dd: 'margin-left:40px', |
| | | mark: 'background-color:yellow', |
| | | pre: 'font-family:monospace;white-space:pre;overflow:scroll', |
| | | s: 'text-decoration:line-through', |
| | | small: 'display:inline;font-size:0.8em', |
| | | u: 'text-decoration:underline' |
| | | } |
| | | } |
| | | |
| | | function makeMap(str) { |
| | | var map = Object.create(null), |
| | | list = str.split(','); |
| | | for (var i = list.length; i--;) |
| | | map[list[i]] = true; |
| | | return map; |
| | | } |
| | | |
| | | // #ifdef MP-WEIXIN |
| | | if (wx.canIUse('editor')) { |
| | | cfg.blockTags.pre = void 0; |
| | | cfg.ignoreTags.rp = true; |
| | | Object.assign(cfg.richOnlyTags, makeMap('bdi,bdo,caption,rt,ruby')); |
| | | Object.assign(cfg.trustTags, makeMap('bdi,bdo,caption,pre,rt,ruby')); |
| | | } |
| | | // #endif |
| | | |
| | | // #ifdef APP-PLUS |
| | | cfg.ignoreTags.iframe = void 0; |
| | | Object.assign(cfg.trustTags, makeMap('embed,iframe')); |
| | | // #endif |
| | | |
| | | module.exports = cfg; |
| New file |
| | |
| | | var inline = { |
| | | abbr: 1, |
| | | b: 1, |
| | | big: 1, |
| | | code: 1, |
| | | del: 1, |
| | | em: 1, |
| | | i: 1, |
| | | ins: 1, |
| | | label: 1, |
| | | q: 1, |
| | | small: 1, |
| | | span: 1, |
| | | strong: 1, |
| | | sub: 1, |
| | | sup: 1 |
| | | } |
| | | module.exports = { |
| | | use: function(item) { |
| | | return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1 |
| | | } |
| | | } |
| New file |
| | |
| | | <template> |
| | | <view :class="'interlayer '+(c||'')" :style="s"> |
| | | <block v-for="(n, i) in nodes" v-bind:key="i"> |
| | | <!--图片--> |
| | | <view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap.stop="imgtap"> |
| | | <rich-text v-if="ctrl[i]!=0" :nodes="[{attrs:{src:loading&&(ctrl[i]||0)<2?loading:(lazyLoad&&!ctrl[i]?placeholder:(ctrl[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" /> |
| | | <image class="_image" :src="lazyLoad&&!ctrl[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad" |
| | | :show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg" |
| | | @error="error" /> |
| | | </view> |
| | | <!--文本--> |
| | | <text v-else-if="n.type=='text'" decode>{{n.text}}</text> |
| | | <!--#ifndef MP-BAIDU--> |
| | | <text v-else-if="n.name=='br'">\n</text> |
| | | <!--#endif--> |
| | | <!--视频--> |
| | | <view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&ctrl[i]==undefined" :id="n.attrs.id" |
| | | :class="'_video '+(n.attrs.class||'')" :style="n.attrs.style" :data-i="i" @tap.stop="_loadVideo" /> |
| | | <video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||ctrl[i]==0" |
| | | :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[ctrl[i]||0]" |
| | | :unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" /> |
| | | <!--音频--> |
| | | <audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" |
| | | :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" |
| | | :src="n.attrs.source[ctrl[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio" @error.native="error" |
| | | @play.native="play" /> |
| | | <!--链接--> |
| | | <view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style" |
| | | :data-attrs="n.attrs" @tap.stop="linkpress"> |
| | | <trees class="_span" c="_span" :nodes="n.children" /> |
| | | </view> |
| | | <!--广告--> |
| | | <!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />--> |
| | | <!--列表--> |
| | | <view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex;flex-direction:row'"> |
| | | <view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view> |
| | | <view v-else class="_ul-bef"> |
| | | <view v-if="n.floor%3==0" class="_ul-p1">█</view> |
| | | <view v-else-if="n.floor%3==2" class="_ul-p2" /> |
| | | <view v-else class="_ul-p1" style="border-radius:50%">█</view> |
| | | </view> |
| | | <trees class="_li" c="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" /> |
| | | </view> |
| | | <!--表格--> |
| | | <view v-else-if="n.name=='table'&&n.c&&n.flag" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:grid'"> |
| | | <trees v-for="(cell,n) in n.children" v-bind:key="n" :class="cell.attrs.class" :c="cell.attrs.class" :style="cell.attrs.style" |
| | | :s="cell.attrs.style" :nodes="cell.children" /> |
| | | </view> |
| | | <view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'"> |
| | | <view v-for="(tbody, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')"> |
| | | <view v-for="(tr, p) in tbody.children" v-bind:key="p" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')"> |
| | | <trees v-if="tr.name=='td'" :nodes="tr.children" /> |
| | | <trees v-else v-for="(td, q) in tr.children" v-bind:key="q" :class="td.attrs.class" :c="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" |
| | | :s="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" :nodes="td.children" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!--#ifdef APP-PLUS--> |
| | | <iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" |
| | | :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" /> |
| | | <embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" /> |
| | | <!--#endif--> |
| | | <!--富文本--> |
| | | <!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS--> |
| | | <rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" /> |
| | | <!--#endif--> |
| | | <!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS--> |
| | | <rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" /> |
| | | <!--#endif--> |
| | | <trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :c="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" |
| | | :style="n.attrs.style" :s="n.attrs.style" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" /> |
| | | </block> |
| | | </view> |
| | | </template> |
| | | <script module="handler" lang="wxs" src="./handler.wxs"></script> |
| | | <script> |
| | | global.Parser = {}; |
| | | import trees from './trees' |
| | | const errorImg = require('../libs/config.js').errorImg; |
| | | export default { |
| | | components: { |
| | | trees |
| | | }, |
| | | name: 'trees', |
| | | data() { |
| | | return { |
| | | ctrl: [], |
| | | placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>', |
| | | errorImg, |
| | | loadVideo: typeof plus == 'undefined', |
| | | // #ifndef MP-ALIPAY |
| | | c: '', |
| | | s: '' |
| | | // #endif |
| | | } |
| | | }, |
| | | props: { |
| | | nodes: Array, |
| | | lazyLoad: Boolean, |
| | | loading: String, |
| | | // #ifdef MP-ALIPAY |
| | | c: String, |
| | | s: String |
| | | // #endif |
| | | }, |
| | | mounted() { |
| | | for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent); |
| | | this.init(); |
| | | }, |
| | | // #ifdef APP-PLUS |
| | | beforeDestroy() { |
| | | this.observer && this.observer.disconnect(); |
| | | }, |
| | | // #endif |
| | | methods: { |
| | | init() { |
| | | for (var i = this.nodes.length, n; n = this.nodes[--i];) { |
| | | if (n.name == 'img') { |
| | | this.top.imgList.setItem(n.attrs.i, n.attrs['original-src'] || n.attrs.src); |
| | | // #ifdef APP-PLUS |
| | | if (this.lazyLoad && !this.observer) { |
| | | this.observer = uni.createIntersectionObserver(this).relativeToViewport({ |
| | | top: 500, |
| | | bottom: 500 |
| | | }); |
| | | setTimeout(() => { |
| | | this.observer.observe('._img', res => { |
| | | if (res.intersectionRatio) { |
| | | for (var j = this.nodes.length; j--;) |
| | | if (this.nodes[j].name == 'img') |
| | | this.$set(this.ctrl, j, 1); |
| | | this.observer.disconnect(); |
| | | } |
| | | }) |
| | | }, 0) |
| | | } |
| | | // #endif |
| | | } else if (n.name == 'video' || n.name == 'audio') { |
| | | var ctx; |
| | | if (n.name == 'video') { |
| | | ctx = uni.createVideoContext(n.attrs.id |
| | | // #ifndef MP-BAIDU |
| | | , this |
| | | // #endif |
| | | ); |
| | | } else if (this.$refs[n.attrs.id]) |
| | | ctx = this.$refs[n.attrs.id][0]; |
| | | if (ctx) { |
| | | ctx.id = n.attrs.id; |
| | | this.top.videoContexts.push(ctx); |
| | | } |
| | | } |
| | | } |
| | | // #ifdef APP-PLUS |
| | | // APP 上避免 video 错位需要延时渲染 |
| | | setTimeout(() => { |
| | | this.loadVideo = true; |
| | | }, 1000) |
| | | // #endif |
| | | }, |
| | | play(e) { |
| | | var contexts = this.top.videoContexts; |
| | | if (contexts.length > 1 && this.top.autopause) |
| | | for (var i = contexts.length; i--;) |
| | | if (contexts[i].id != e.currentTarget.dataset.id) |
| | | contexts[i].pause(); |
| | | }, |
| | | imgtap(e) { |
| | | var attrs = e.currentTarget.dataset.attrs; |
| | | if (!attrs.ignore) { |
| | | var preview = true, |
| | | data = { |
| | | id: e.target.id, |
| | | src: attrs.src, |
| | | ignore: () => preview = false |
| | | }; |
| | | global.Parser.onImgtap && global.Parser.onImgtap(data); |
| | | this.top.$emit('imgtap', data); |
| | | if (preview) { |
| | | var urls = this.top.imgList, |
| | | current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0); |
| | | uni.previewImage({ |
| | | current, |
| | | urls |
| | | }) |
| | | } |
| | | } |
| | | }, |
| | | loadImg(e) { |
| | | var i = e.currentTarget.dataset.i; |
| | | if (this.lazyLoad && !this.ctrl[i]) { |
| | | // #ifdef QUICKAPP-WEBVIEW |
| | | this.$set(this.ctrl, i, 0); |
| | | this.$nextTick(function() { |
| | | // #endif |
| | | // #ifndef APP-PLUS |
| | | this.$set(this.ctrl, i, 1); |
| | | // #endif |
| | | // #ifdef QUICKAPP-WEBVIEW |
| | | }) |
| | | // #endif |
| | | } else if (this.loading && this.ctrl[i] != 2) { |
| | | // #ifdef QUICKAPP-WEBVIEW |
| | | this.$set(this.ctrl, i, 0); |
| | | this.$nextTick(function() { |
| | | // #endif |
| | | this.$set(this.ctrl, i, 2); |
| | | // #ifdef QUICKAPP-WEBVIEW |
| | | }) |
| | | // #endif |
| | | } |
| | | }, |
| | | linkpress(e) { |
| | | var jump = true, |
| | | attrs = e.currentTarget.dataset.attrs; |
| | | attrs.ignore = () => jump = false; |
| | | global.Parser.onLinkpress && global.Parser.onLinkpress(attrs); |
| | | this.top.$emit('linkpress', attrs); |
| | | if (jump) { |
| | | // #ifdef MP |
| | | if (attrs['app-id']) { |
| | | return uni.navigateToMiniProgram({ |
| | | appId: attrs['app-id'], |
| | | path: attrs.path |
| | | }) |
| | | } |
| | | // #endif |
| | | if (attrs.href) { |
| | | if (attrs.href[0] == '#') { |
| | | if (this.top.useAnchor) |
| | | this.top.navigateTo({ |
| | | id: attrs.href.substring(1) |
| | | }) |
| | | } else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) { |
| | | // #ifdef APP-PLUS |
| | | plus.runtime.openWeb(attrs.href); |
| | | // #endif |
| | | // #ifndef APP-PLUS |
| | | uni.setClipboardData({ |
| | | data: attrs.href, |
| | | success: () => |
| | | uni.showToast({ |
| | | title: '链接已复制' |
| | | }) |
| | | }) |
| | | // #endif |
| | | } else |
| | | uni.navigateTo({ |
| | | url: attrs.href, |
| | | fail() { |
| | | uni.switchTab({ |
| | | url: attrs.href, |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | }, |
| | | error(e) { |
| | | var target = e.currentTarget, |
| | | source = target.dataset.source, |
| | | i = target.dataset.i; |
| | | if (source == 'video' || source == 'audio') { |
| | | // 加载其他 source |
| | | var index = this.ctrl[i] ? this.ctrl[i].i + 1 : 1; |
| | | if (index < this.nodes[i].attrs.source.length) |
| | | this.$set(this.ctrl, i, index); |
| | | if (e.detail.__args__) |
| | | e.detail = e.detail.__args__[0]; |
| | | } else if (errorImg && source == 'img') { |
| | | this.top.imgList.setItem(target.dataset.index, errorImg); |
| | | this.$set(this.ctrl, i, 3); |
| | | } |
| | | this.top && this.top.$emit('error', { |
| | | source, |
| | | target, |
| | | errMsg: e.detail.errMsg |
| | | }); |
| | | }, |
| | | _loadVideo(e) { |
| | | this.$set(this.ctrl, e.target.dataset.i, 0); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | /* 在这里引入自定义样式 */ |
| | | |
| | | /* 链接和图片效果 */ |
| | | ._a { |
| | | display: inline; |
| | | padding: 1.5px 0 1.5px 0; |
| | | color: #366092; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | ._hover { |
| | | text-decoration: underline; |
| | | opacity: 0.7; |
| | | } |
| | | |
| | | ._img { |
| | | display: inline-block; |
| | | max-width: 100%; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* #ifdef MP-WEIXIN */ |
| | | :host { |
| | | display: inline; |
| | | } |
| | | |
| | | /* #endif */ |
| | | |
| | | /* #ifndef MP-ALIPAY || APP-PLUS */ |
| | | .interlayer { |
| | | display: inherit; |
| | | flex-direction: inherit; |
| | | flex-wrap: inherit; |
| | | align-content: inherit; |
| | | align-items: inherit; |
| | | justify-content: inherit; |
| | | width: 100%; |
| | | white-space: inherit; |
| | | } |
| | | |
| | | /* #endif */ |
| | | |
| | | ._b, |
| | | ._strong { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | /* #ifndef MP-ALIPAY */ |
| | | ._blockquote, |
| | | ._div, |
| | | ._p, |
| | | ._ol, |
| | | ._ul, |
| | | ._li { |
| | | display: block; |
| | | } |
| | | |
| | | /* #endif */ |
| | | |
| | | ._code { |
| | | font-family: monospace; |
| | | } |
| | | |
| | | ._del { |
| | | text-decoration: line-through; |
| | | } |
| | | |
| | | ._em, |
| | | ._i { |
| | | font-style: italic; |
| | | } |
| | | |
| | | ._h1 { |
| | | font-size: 2em; |
| | | } |
| | | |
| | | ._h2 { |
| | | font-size: 1.5em; |
| | | } |
| | | |
| | | ._h3 { |
| | | font-size: 1.17em; |
| | | } |
| | | |
| | | ._h5 { |
| | | font-size: 0.83em; |
| | | } |
| | | |
| | | ._h6 { |
| | | font-size: 0.67em; |
| | | } |
| | | |
| | | ._h1, |
| | | ._h2, |
| | | ._h3, |
| | | ._h4, |
| | | ._h5, |
| | | ._h6 { |
| | | display: block; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | ._image { |
| | | display: block; |
| | | width: 100%; |
| | | height: 360px; |
| | | margin-top: -360px; |
| | | opacity: 0; |
| | | } |
| | | |
| | | ._ins { |
| | | text-decoration: underline; |
| | | } |
| | | |
| | | ._li { |
| | | flex: 1; |
| | | width: 0; |
| | | } |
| | | |
| | | ._ol-bef { |
| | | width: 36px; |
| | | margin-right: 5px; |
| | | text-align: right; |
| | | } |
| | | |
| | | ._ul-bef { |
| | | display: block; |
| | | margin: 0 12px 0 23px; |
| | | line-height: normal; |
| | | } |
| | | |
| | | ._ol-bef, |
| | | ._ul-bef { |
| | | flex: none; |
| | | user-select: none; |
| | | } |
| | | |
| | | ._ul-p1 { |
| | | display: inline-block; |
| | | width: 0.3em; |
| | | height: 0.3em; |
| | | overflow: hidden; |
| | | line-height: 0.3em; |
| | | } |
| | | |
| | | ._ul-p2 { |
| | | display: inline-block; |
| | | width: 0.23em; |
| | | height: 0.23em; |
| | | border: 0.05em solid black; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | ._q::before { |
| | | content: '"'; |
| | | } |
| | | |
| | | ._q::after { |
| | | content: '"'; |
| | | } |
| | | |
| | | ._sub { |
| | | font-size: smaller; |
| | | vertical-align: sub; |
| | | } |
| | | |
| | | ._sup { |
| | | font-size: smaller; |
| | | vertical-align: super; |
| | | } |
| | | |
| | | /* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */ |
| | | ._abbr, |
| | | ._b, |
| | | ._code, |
| | | ._del, |
| | | ._em, |
| | | ._i, |
| | | ._ins, |
| | | ._label, |
| | | ._q, |
| | | ._span, |
| | | ._strong, |
| | | ._sub, |
| | | ._sup { |
| | | display: inline; |
| | | } |
| | | |
| | | /* #endif */ |
| | | |
| | | /* #ifdef MP-WEIXIN || MP-QQ */ |
| | | .__bdo, |
| | | .__bdi, |
| | | .__ruby, |
| | | .__rt { |
| | | display: inline-block; |
| | | } |
| | | |
| | | /* #endif */ |
| | | ._video { |
| | | position: relative; |
| | | display: inline-block; |
| | | width: 300px; |
| | | height: 225px; |
| | | background-color: black; |
| | | } |
| | | |
| | | ._video::after { |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | margin: -15px 0 0 -15px; |
| | | content: ''; |
| | | border-color: transparent transparent transparent white; |
| | | border-style: solid; |
| | | border-width: 15px 0 15px 30px; |
| | | } |
| | | </style> |
| uview-ui/components/u-parse/u-parse.vue
uview-ui/components/u-picker/u-picker.vue
uview-ui/components/u-popup/u-popup.vue
uview-ui/components/u-radio-group/u-radio-group.vue
uview-ui/components/u-radio/u-radio.vue
uview-ui/components/u-rate/u-rate.vue
uview-ui/components/u-read-more/u-read-more.vue
uview-ui/components/u-row-notice/u-row-notice.vue
uview-ui/components/u-row/u-row.vue
uview-ui/components/u-search/u-search.vue
uview-ui/components/u-section/u-section.vue
uview-ui/components/u-select/u-select.vue
uview-ui/components/u-skeleton/u-skeleton.vue
uview-ui/components/u-slider/u-slider.vue
uview-ui/components/u-steps/u-steps.vue
uview-ui/components/u-sticky/u-sticky.vue
uview-ui/components/u-subsection/u-subsection.vue
uview-ui/components/u-swipe-action/u-swipe-action.vue
uview-ui/components/u-swiper/u-swiper.vue
uview-ui/components/u-switch/u-switch.vue
uview-ui/components/u-tabbar/u-tabbar.vue
uview-ui/components/u-table/u-table.vue
uview-ui/components/u-tabs-swiper/u-tabs-swiper.vue
uview-ui/components/u-tabs/u-tabs.vue
uview-ui/components/u-tag/u-tag.vue
uview-ui/components/u-td/u-td.vue
uview-ui/components/u-th/u-th.vue
uview-ui/components/u-time-line-item/u-time-line-item.vue
uview-ui/components/u-time-line/u-time-line.vue
uview-ui/components/u-toast/u-toast.vue
uview-ui/components/u-top-tips/u-top-tips.vue
uview-ui/components/u-tr/u-tr.vue
uview-ui/components/u-upload/u-upload.vue
uview-ui/components/u-verification-code/u-verification-code.vue
uview-ui/components/u-waterfall/u-waterfall.vue
uview-ui/iconfont.css
uview-ui/index.js
uview-ui/index.scss
uview-ui/libs/config/config.js
uview-ui/libs/config/zIndex.js
uview-ui/libs/css/color.scss
uview-ui/libs/css/common.scss
uview-ui/libs/css/style.components.scss
uview-ui/libs/css/style.h5.scss
uview-ui/libs/css/style.mp.scss
uview-ui/libs/css/style.nvue.scss
uview-ui/libs/css/style.vue.scss
uview-ui/libs/function/$parent.js
uview-ui/libs/function/addUnit.js
uview-ui/libs/function/bem.js
uview-ui/libs/function/color.js
uview-ui/libs/function/colorGradient.js
uview-ui/libs/function/debounce.js
uview-ui/libs/function/deepClone.js
uview-ui/libs/function/deepMerge.js
uview-ui/libs/function/getParent.js
uview-ui/libs/function/guid.js
uview-ui/libs/function/md5.js
uview-ui/libs/function/queryParams.js
uview-ui/libs/function/random.js
uview-ui/libs/function/randomArray.js
uview-ui/libs/function/route.js
uview-ui/libs/function/sys.js
uview-ui/libs/function/test.js
uview-ui/libs/function/throttle.js
uview-ui/libs/function/timeFormat.js
uview-ui/libs/function/timeFrom.js
uview-ui/libs/function/toast.js
uview-ui/libs/function/trim.js
uview-ui/libs/function/type2icon.js
uview-ui/libs/mixin/mixin.js
uview-ui/libs/mixin/mpShare.js
uview-ui/libs/request/index.js
uview-ui/libs/store/index.js
uview-ui/libs/util/area.js
uview-ui/libs/util/async-validator.js
uview-ui/libs/util/city.js
uview-ui/libs/util/emitter.js
uview-ui/libs/util/province.js
uview-ui/package.json
uview-ui/theme.scss
vue.config.js |