This commit is contained in:
zwt13703 2026-03-16 16:32:50 +08:00
parent 2cd78dab25
commit a6c052eda7
808 changed files with 95672 additions and 3 deletions

136
.gitignore vendored
View File

@ -6,6 +6,142 @@
# TODO: where does this rule come from? # TODO: where does this rule come from?
docs/_book docs/_book
task_detail_**.md
# TODO: where does this rule come from? # TODO: where does this rule come from?
test/ test/
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
unpackage
unpackage/**
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

153
App.vue Normal file
View File

@ -0,0 +1,153 @@
<script>
import {
mapMutations
} from 'vuex'
import {
version
} from './package.json'
import request, { get, post, put, del } from '@/common/request';
import { ENV } from '@/common/env';
import { API } from '@/common/api';
import { initAppConfig } from '@/common/app-config';
// #ifdef APP
import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
// #endif
export default {
onLaunch: function() {
const Tool = {
Request: { request, get, post, put, del },
Api: API,
Env: ENV
}
this.Tool = Tool
this.globalData.Tool = Tool
this.globalData.ENV = ENV
this.globalData.appConfigReady = initAppConfig();
// #ifdef H5
console.log(
`%c hello uniapp %c v${version} `,
'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
'background:#007aff ;padding: 1px; border-radius: 0 3px 3px 0; color: #fff; font-weight: bold;'
)
// #endif
// 线使
// console.log('%c uni-app uni-app & uniCloud hr2013@dcloud.io', 'color: red');
console.log('App Launch');
// #ifdef APP-PLUS
// AppuniCloudhttps://ext.dcloud.net.cn/plugin?id=4542
if (plus.runtime.appid !== 'HBuilder') { // appid'HBuilder'appid
checkUpdate()
}
//
uni.preLogin({
provider: 'univerify',
success: (res) => {
//
this.setUniverifyErrorMsg();
console.log("preLogin success: ", res);
},
fail: (res) => {
this.setUniverifyLogin(false);
this.setUniverifyErrorMsg(res.errMsg);
//
console.log("preLogin fail res: ", res);
}
})
// #endif
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
globalData: {
test: '',
Tool: {},
ENV: {},
},
methods: {
...mapMutations(['setUniverifyErrorMsg', 'setUniverifyLogin'])
}
}
</script>
<style lang="scss">
@import '@/uni_modules/uni-scss/index.scss';
/* #ifndef APP-PLUS-NVUE */
/* uni.css - 通用组件、模板样式库可以当作一套ui库应用 */
@import './common/uni.css';
@import '@/static/customicons.css';
/* H5 兼容 pc 所需 */
/* #ifdef H5 */
@media screen and (min-width: 768px) {
body {
overflow-y: scroll;
}
}
/* 顶栏通栏样式 */
/* .uni-top-window {
left: 0;
right: 0;
} */
uni-page-body {
background-color: #F5F5F5 !important;
min-height: 100% !important;
height: auto !important;
}
.uni-top-window uni-tabbar .uni-tabbar {
background-color: #fff !important;
}
.uni-app--showleftwindow .hideOnPc {
display: none !important;
}
/* #endif */
/* 以下样式用于 hello uni-app 演示所需 */
page {
background-color: #efeff4;
height: 100%;
font-size: 28rpx;
/* line-height: 1.8; */
}
.fix-pc-padding {
padding: 0 50px;
}
.uni-header-logo {
padding: 30rpx;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 10rpx;
}
.uni-header-image {
width: 100px;
height: 100px;
}
.uni-hello-text {
color: #7A7E83;
}
.uni-hello-addfile {
text-align: center;
line-height: 300rpx;
background: #FFF;
padding: 50rpx;
margin-top: 10px;
font-size: 38rpx;
color: #808080;
}
/* #endif*/
</style>

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 DCloud
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.

130
README.md
View File

@ -1,3 +1,129 @@
# wz-uniapp # hello-uniapp
`uni-app`框架示例一套代码同时发行到iOS、Android、H5、小程序等多个平台请使用手机在下方扫码快速体验`uni-app`的强大功能。[官方文档](https://uniapp.dcloud.net.cn/)
## 快速上手
hello-uniapp 示例工程可以通过两种方式创建, 一种是 HBuilderX, 配套 IDE集成开发另一种是 CLI 创建;推荐前者。
### 通过 HBuilderX 可视化界面创建(推荐)
可视化的方式比较简单HBuilderX内置相关环境开箱即用无需配置nodejs。
开始之前,开发者需先下载安装如下工具:
- HBuilderX[官方IDE下载地址](https://www.dcloud.io/hbuilderx.html)
HBuilderX是通用的前端开发工具但为`uni-app`做了特别强化请下载App开发版。
由于截图在 github 不便浏览,参见官方文档 [HBuilderX 可视化界面创建](https://uniapp.dcloud.net.cn/quickstart?id=_1-%e9%80%9a%e8%bf%87-hbuilderx-%e5%8f%af%e8%a7%86%e5%8c%96%e7%95%8c%e9%9d%a2)
### 通过 vue-cli 创建
```
npm install -g @vue/cli
```
#### 创建uni-app
**使用正式版**对应HBuilderX最新正式版
```
vue create -p dcloudio/uni-preset-vue my-project
```
**使用alpha版**对应HBuilderX最新alpha版
```
vue create -p dcloudio/uni-preset-vue#alpha my-alpha-project
```
此时,会提示选择项目模板,选择 `hello uni-app` 项目模板,如下所示:
<div>
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/h5-cli-01.png" width="300">
</div>
创建好后,进入项目目录
```
cd my-project
```
执行该命令运行到 h5 端
```
npm run dev:h5
```
欢迎提 issues推荐到[官方社区](https://ask.dcloud.net.cn/explore/)提问。
## 扫码体验
<div class="quick">
<p>一套代码编到10个平台这不是梦想。眼见为实扫描10个二维码亲自体验最全面的跨平台效果</p>
<div style="display: flex;">
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://web-assets.dcloud.net.cn/unidoc/zh/uni-android.png" width="160" />
</div>
<b>Android版</b>
</a>
<a href="https://itunes.apple.com/cn/app/hello-uni-app/id1417078253?mt=8" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://web-assets.dcloud.net.cn/unidoc/zh/uni-h5.png" width="160" />
</div>
<b>iOS版</b>
</a>
<a href="https://hellouniapp.dcloud.net.cn/" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/uni-h5-hosting-qr.png" width="160" />
</div>
<b>H5版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box"><img src="//img.cdn.aliyun.dcloud.net.cn/guide/uniapp/gh_33446d7f7a26_430.jpg" width="160" /></div>
<b>微信小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box"><img src="https://web-assets.dcloud.net.cn/unidoc/zh/alipay1.png" width="160" /></div>
<b>支付宝小程序版</b>
</a>
</div>
<div class="flex-img-group-view" style="margin-top: 20px;">
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box"><img src="https://web-assets.dcloud.net.cn/unidoc/zh/baidu-uniapp.png" width="160" /></div>
<b>百度小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/mp-toutiao.png" width="160" />
</div>
<b>字节跳动小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-qq.png" width="160" />
</div>
<b>QQ小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-qa-union.png" width="160" />
</div>
<b>快应用</b>
</a>
<a href="https://so.mp.360.cn/mp.html?appid=qh4j181qqtru354st6" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-mp-360-qr.png" width="160" />
</div>
<b>360小程序</b>
</a>
</div>
<p>
<em>某些平台不能提交简单demo故补充了一些其他功能hello uni-app示例代码可从[github](https://github.com/dcloudio/hello-uniapp)获取</em></br>
<em>快应用仅支持 vivo 、oppo、华为</em></br>
<em>360小程序仅 windows平台支持需要在360浏览器中打开</em></br>
</p>
</div>
`uni-app`官网文档详见[https://uniapp.dcloud.io](https://uniapp.dcloud.io)
更多uni-app的模板、示例详见[插件市场](https://ext.dcloud.net.cn/)
uniapp 版本 # 3.4.92025-06-25的套webview demo 传参数版本后端使用Golang自用做模板开发

186
changelog.md Normal file
View File

@ -0,0 +1,186 @@
## 3.4.92025-06-25
- 适配 鸿蒙元服务
- 更新 uni-swipe-action
- 新增 crypto 示例和测试例
- 新增 css 变量示例和测试例
- 新增 web-view点击位置偏移测试例
## 3.4.82025-04-10
- 调整 renderjs示例的平台兼容性
## 3.4.72025-01-08
- 新增 uni-calendar显示
- 更新 uniui 组件
- 更新uni-id至3.3.33版本
- 替换示例中失效图片资源地址
## 3.4.62023-06-30
- 更新 video组件引用的视频链接
## 3.4.42022-07-25
- 新增 同步 uni-ui@1.4.20
- uni-forms 【重要】组件逻辑重构,部分用法旧版本不兼容,请注意兼容问题
- uni-section 新增组件
## 3.4.32022-07-14
- 修复 HBuilderX 拉取 hello uni-app 项目直接运行提示无服务空间关联的bug
## 3.4.22022-07-06
- 新增 同步 uni-ui@1.4.18
- uni-forms 【重要】组件逻辑重构,部分用法旧版本不兼容,请注意兼容问题
- uni-forms 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力
- uni-forms 新增 更多表单示例
- uni-forms 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃
- uni-forms 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效
- uni-forms 新增 onFieldChange 方法可以对子表单进行校验可替代binddata方法
- uni-forms 新增 子表单的 setRules 方法,配合自定义校验函数使用
- uni-forms 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则
- uni-forms 修复 由 1.4.0 引发的 label 插槽不生效的bug
- uni-forms 修复 子组件找不到 setValue 报错的bug
- uni-forms 修复 uni-data-picker 在 uni-forms-item 中报错的bug
- uni-forms 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
- uni-forms 修复 表单校验顺序无序问题
- uni-forms 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式
- uni-forms 优化 动态表单校验方式废弃拼接name的方式
- uni-breadcrumb 修复 微信小程序 separator 不显示问题
- uni-data-checkbox 优化 在 uni-forms 中的依赖注入方式
- uni-data-picker 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
- uni-data-picker 优化 显示样式
- uni-data-select 优化 显示样式
- uni-datetime-picker 修复 日历顶部年月及底部确认未国际化 bug
- uni-datetime-picker 优化 组件样式调整了组件图标大小、高度、颜色等与uni-ui风格保持一致
- uni-easyinput 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
- uni-easyinput 新增 clear 事件,点击右侧叉号图标触发
- uni-easyinput 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
- uni-easyinput 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
- uni-easyinput 优化 clearable 显示策略
- uni-file-picker 修复 在uni-forms下样式不生效的bug
- uni-nav-bar 修复 组件示例中插槽用法无法显示内容的bug
- uni-swipe-action 修复 vue3 下使用组件不能正常运行的Bug
- uni-swipe-action 修复 h5端点击click触发两次的Bug
- uni-table 修复 微信小程序存在无使用组件的问题
## 3.4.12022-06-30
- 新增 支持 ios 安全区
## 3.3.82022-05-08
- 新增 同步 uni-ui@1.4.15
- uni-data-picker 修复 字节小程序 本地数据无法选择下一级的Bug
- uni-data-select 新增 记住上次的选项(仅 collection 存在时有效)
- uni-search-bar 修复 vue3 input 事件不生效的bug
- uni-search-bar 修复 多余代码导致的bug
- uni-tooltip 更新 text 属性变更为 content
- uni-tooltip 更新 移除 width 属性
- uni-tooltip 修复 组件根 text 嵌套组件 warning
## 3.3.72022-04-06
- 新增 更新扩展组件 uni-nav-bar、uni-list 的展示页面
## 3.3.62022-03-31
- 更新 uni-ui 组件及示例
## 3.3.52022-03-30
- 修复 插槽兼容 vue3 slot -> v-slot
- 新增 更新 uni-ui
## 3.3.42022-02-25
- 修复 编译到 App 平台的控制台报错
## 3.3.32022-02-23
- 修复 模板城市选择 vue3 报错的bug
- 修复 删除 map.nvue 中多余的 enableOverlooking 变量
- 修复 swipe-dot.nvue 中条件编译媒体查询
## 3.3.22022-01-26
- 修复 默认运行到 vue2
## 3.3.12022-01-26
- 新增 同步 uni-ui@1.4.11
- uni-collapse 修复 微信小程序resize后组件收起的bug
- uni-countdown 修复 在微信小程序中样式不生效的bug
- uni-countdown 新增 update 方法 ,在动态更新时间后,刷新组件
- uni-load-more 新增 showText属性 ,是否显示文本
- uni-load-more 修复 nvue 平台下不显示文本的bug
- uni-load-more 修复 微信小程序平台样式选择器报警告的问题
- uni-nav-bar 修复 在vue下标题不垂直居中的bug
- uni-nav-bar 修复 height 属性类型错误
- uni-nav-bar 新增 height 属性,可修改组件高度
- uni-nav-bar 新增 dark 属性可可开启暗黑模式
- uni-nav-bar 优化 标题字数过多显示省略号
- uni-nav-bar 优化 插槽,插入内容可完全覆盖
- uni-popup 修复 isMaskClick 失效的bug
- uni-popup 新增 cancelText \ confirmText 属性 ,可自定义文本
- uni-popup 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
- uni-popup 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
## 3.3.02022-01-04
- 修复 开发时在 vue3 下由 pc 端切换到手机端,不能返回上一级页面的 bug
- 优化 去掉 pc 端 topwindow 右上角用于演示的 url 导航
## 3.2.122021-12-20
- 新增 适配京东小程序
## 3.2.112021-12-07
- 修复 uni-ui 在 hello-uniapp 中丢失图标、ui 受到公共样式影响等问题
- 修复 微信登录取值报错的问题
## 3.2.102021-11-30
- 修复 map 组件示例不显示的 bug
## 3.2.92021-11-19
- 新增 uni-ui 以 uni_modules 方式引入,方便开发者从 hello-uniapp 中 copy 组件及示例
- 优化 uni-ui 示例页面,完善组件示例
## 3.2.82021-11-10
- 新增 适配飞书平台lark
## 3.2.72021-10-26
- 修复 uni-popup 示例的 button 文字在 iPhone 5 上换行的 bug
- 修复 uni-table 示例,页面顶部距离不对的 bug
- 修复 GlobalData示例在vue3是无效的 bug
- 优化 删除无用的 project.swan.json 文件
## 3.2.62021-10-08
- 由于体验问题,暂时撤销 uni-ui 以 uni_modules 方式引入的修改
## 3.2.42021-09-07
- 修复 vue3 在 H5 编译报错的 bug
- 新增 同步 uni-ui
- 新增 uni-ui 组件支持国际化 i18n
- uni-data-checkbox 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
- uni-datetime-picker 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次
- uni-datetime-picker 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法
- uni-datetime-picker 优化 调整字号大小,美化日历界面
- uni-datetime-picker 修复 因国际化导致的 placeholder 失效的 bug
- uni-file-picker 修复 return-type="object" 时且存在v-model时无法删除文件的Bug
- uni-file-picker 新增 参数中返回 fileID 字段
- uni-file-picker 修复 腾讯云传入fileID 不能回显的bug
- uni-file-picker 修复 选择图片后,不能放大的问题
- uni-link 修复 在 nvue 下不显示的 bug
- uni-list 修复 在vue3中to属性在发行应用的时候报错的bug
- uni-search-bar 修复 value 属性与 modelValue 属性不兼容的Bug
- uni-swipe-action 优化 close-all 方法
- uni-collapse 优化 show-arrow 属性默认为true
- uni-collapse 新增 show-arrow 属性,控制是否显示右侧箭头
- uni-data-checkbox 修复 单选 list 模式下 icon 为 left 时,选中图标不显示的问题
- uni-easyinput 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
- uni-file-picker 修复 由于 0.2.11 版本引起的不能回显图片的Bug
- uni-file-picker 新增 clearFiles(index) 方法,可以手动删除指定文件
- uni-file-picker 修复 v-model 值设为 null 报错的Bug
- uni-swipe-action 新增 close-all 方法,关闭所有已打开的组件
- uni-swipe-action 新增 resize() 方法在非微信小程序、h5、app-vue端出现不能滑动的问题的时候重置组件
- uni-swipe-action 修复 app 端偶尔出现类似 Page[x][-x,xx;-x,xx,x,x-x] 的问题
- uni-swipe-action 优化 微信小程序、h5、app-vue 滑动逻辑,避免出现动态新增组件后不能滑动的问题
## 3.2.32021-08-27
- 优化 tabbar 页面移除 vuex 相关代码
- 新增 适配 vue3 (app)
## 3.2.22021-08-10
- 新增 适配快手小程序
- 新增 同步 uni-ui
- uni-datetime-picker 新增 return-type 属性支持返回 date 日期对象
- uni-file-picker 修复 fileExtname属性不指定值报错的Bug
- uni-file-picker 修复 在某种场景下图片不回显的Bug
- uni-link 支持自定义插槽
- uni-calendar 修复 弹出层被 tabbar 遮盖 bug
- uni-dateformat 调整 默认时间不再是当前时间,而是显示'-'字符
- uni-datetime-picker 新增 适配 vue3
- uni-datetime-picker 新增 支持作为 uni-forms 子组件相关功能
- uni-datetime-picker 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug
- uni-datetime-picker 修复 type 属性动态赋值无效的 bug
- uni-datetime-picker 修复 ‘确认’按钮被 tabbar 遮盖 bug
- uni-datetime-picker 修复 组件未赋值时范围选左、右日历相同的 bug
- uni-datetime-picker 修复 范围选未正确显示当前值的 bug
- uni-datetime-picker 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug
- uni-file-picker 修复 auto-upload 属性失效的Bug
## 3.2.12021-07-31
- 新增 同步 uni-ui@1.3.8
## 3.2.02021-07-30
- 新增 同时兼容 vue2 & vue3
## 3.1.202021-07-30
- 新增 同时兼容 vue2 & vue3
## 3.1.172021-05-26
- 修复 3.1.16 依赖 sass 的问题
- 条件编译 nuve 不支持 css 属性
## 3.1.162021-05-26
- 修复 uni-data-checkbox 不关联服务空间的情况下组件报错的 Bug
## 3.1.122021-05-07
- hello-uniapp 发布插件市场

262
common/airport.js Normal file
View File

@ -0,0 +1,262 @@
export default {
"list": [{
"letter": "A",
"data": [
"阿克苏机场",
"阿拉山口机场",
"阿勒泰机场",
"阿里昆莎机场",
"安庆天柱山机场",
"澳门国际机场"
]
}, {
"letter": "B",
"data": [
"保山机场",
"包头机场",
"北海福成机场",
"北京南苑机场",
"北京首都国际机场"
]
}, {
"letter": "C",
"data": [
"长白山机场",
"长春龙嘉国际机场",
"常德桃花源机场",
"昌都邦达机场",
"长沙黄花国际机场",
"长治王村机场",
"常州奔牛机场",
"成都双流国际机场",
"赤峰机场"
]
}, {
"letter": "D",
"data": [
"大理机场",
"大连周水子国际机场",
"大庆萨尔图机场",
"大同东王庄机场",
"达州河市机场",
"丹东浪头机场",
"德宏芒市机场",
"迪庆香格里拉机场",
"东营机场",
"敦煌机场"
]
}, {
"letter": "E",
"data": [
"鄂尔多斯机场",
"恩施许家坪机场",
"二连浩特赛乌苏国际机场"
]
}, {
"letter": "F",
"data": [
"阜阳西关机场",
"福州长乐国际机场"
]
}, {
"letter": "G",
"data": [
"赣州黄金机场",
"格尔木机场",
"固原六盘山机场",
"广元盘龙机场",
"广州白云国际机场",
"桂林两江国际机场",
"贵阳龙洞堡国际机场"
]
}, {
"letter": "H",
"data": [
"哈尔滨太平国际机场",
"哈密机场",
"海口美兰国际机场",
"海拉尔东山国际机场",
"邯郸机场",
"汉中机场",
"杭州萧山国际机场",
"合肥骆岗国际机场",
"和田机场",
"黑河机场",
"呼和浩特白塔国际机场",
"淮安涟水机场",
"黄山屯溪国际机场"
]
}, {
"letter": "I",
"data": []
}, {
"letter": "J",
"data": [
"济南遥墙国际机场",
"济宁曲阜机场",
"鸡西兴凯湖机场",
"佳木斯东郊机场",
"嘉峪关机场",
"锦州小岭子机场",
"景德镇机场",
"井冈山机场",
"九江庐山机场",
"九寨黄龙机场"
]
}, {
"letter": "K",
"data": [
"喀什机场",
"克拉玛依机场",
"库车龟兹机场",
"库尔勒机场",
"昆明巫家坝国际机场"
]
}, {
"letter": "L",
"data": [
"拉萨贡嘎机场",
"兰州中川机场",
"丽江三义机场",
"黎平机场",
"连云港白塔埠机场",
"临沧机场",
"临沂机场",
"林芝米林机场",
"柳州白莲机场",
"龙岩冠豸山机场",
"泸州蓝田机场",
"洛阳北郊机场"
]
}, {
"letter": "M",
"data": [
"满洲里西郊机场",
"绵阳南郊机场",
"漠河古莲机场",
"牡丹江海浪机场"
]
}, {
"letter": "N",
"data": [
"南昌昌北国际机场",
"南充高坪机场",
"南京禄口国际机场",
"南宁吴圩机场",
"南通兴东机场",
"南阳姜营机场",
"宁波栎社国际机场"
]
}, {
"letter": "O",
"data": []
}, {
"letter": "P",
"data": [
"普洱思茅机场"
]
}, {
"letter": "Q",
"data": [
"齐齐哈尔三家子机场",
"秦皇岛山海关机场",
"青岛流亭国际机场",
"衢州机场",
"泉州晋江机场"
]
}, {
"letter": "R",
"data": [
"日喀则和平机场"
]
}, {
"letter": "S",
"data": [
"三亚凤凰国际机场",
"汕头外砂机场",
"上海虹桥国际机场",
"上海浦东国际机场",
"深圳宝安国际机场",
"沈阳桃仙国际机场",
"石家庄正定国际机场",
"苏南硕放国际机场"
]
}, {
"letter": "T",
"data": [
"塔城机场",
"太原武宿国际机场",
"台州路桥机场 (黄岩机场)",
"唐山三女河机场",
"腾冲驼峰机场",
"天津滨海国际机场",
"通辽机场",
"铜仁凤凰机场"
]
}, {
"letter": "U",
"data": []
}, {
"letter": "V",
"data": []
}, {
"letter": "W",
"data": [
"万州五桥机场",
"潍坊机场",
"威海大水泊机场",
"文山普者黑机场",
"温州永强国际机场",
"乌海机场",
"武汉天河国际机场",
"乌兰浩特机场",
"乌鲁木齐地窝堡国际机场",
"武夷山机场",
"梧州长洲岛机场"
]
}, {
"letter": "X",
"data": [
"西安咸阳国际机场",
"西昌青山机场",
"锡林浩特机场",
"西宁曹家堡机场",
"西双版纳嘎洒机场",
"厦门高崎国际机场",
"香港国际机场",
"襄阳刘集机场",
"兴义机场",
"徐州观音机场"
]
}, {
"letter": "Y",
"data": [
"延安二十里堡机场",
"盐城机场",
"延吉朝阳川机场",
"烟台莱山国际机场",
"宜宾菜坝机场",
"宜昌三峡机场",
"伊春林都机场",
"伊宁机场",
"义乌机场",
"银川河东机场",
"永州零陵机场",
"榆林榆阳机场",
"玉树巴塘机场",
"运城张孝机场"
]
}, {
"letter": "Z",
"data": [
"湛江机场",
"昭通机场",
"郑州新郑国际机场",
"芷江机场",
"重庆江北国际机场",
"中卫香山机场",
"舟山朱家尖机场",
"珠海三灶机场"
]
}]
}

5
common/api.js Normal file
View File

@ -0,0 +1,5 @@
export const API = {
WX_MINI_LOGIN: '/api/open/wechat/mini/login',
USER_PROFILE: '/api/user/profile',
USER_PASSWORD_LOGIN: '/api/open/user/login'
};

249
common/app-config.js Normal file
View File

@ -0,0 +1,249 @@
import { ENV, STORAGE_KEYS } from './env';
import { version as packageVersion } from '@/package.json';
const CACHE_KEY = STORAGE_KEYS.APP_CONFIG || 'app_config_cache';
const DEFAULT_TTL_SECONDS = 3600;
let inFlight = null;
let lastConfig = null;
function buildUrl(base, path) {
if (!base) return path;
const normalizedBase = String(base).replace(/\/$/, '');
const normalizedPath = String(path || '').replace(/^\//, '');
return `${normalizedBase}/${normalizedPath}`;
}
function parseVersion(value) {
if (!value) return [0];
return String(value)
.replace(/^v/i, '')
.split('.')
.map((item) => {
const num = Number.parseInt(item, 10);
return Number.isNaN(num) ? 0 : num;
});
}
function compareVersion(a, b) {
const left = parseVersion(a);
const right = parseVersion(b);
const maxLen = Math.max(left.length, right.length);
for (let i = 0; i < maxLen; i += 1) {
const l = left[i] || 0;
const r = right[i] || 0;
if (l > r) return 1;
if (l < r) return -1;
}
return 0;
}
function getAppVersion() {
let current = packageVersion || '0.0.0';
// #ifdef MP-WEIXIN
try {
const account = uni.getAccountInfoSync && uni.getAccountInfoSync();
const mpVersion = account && account.miniProgram && account.miniProgram.version;
if (mpVersion) current = mpVersion;
} catch (e) {
// ignore
}
// #endif
return current || '0.0.0';
}
function getCache() {
try {
return uni.getStorageSync(CACHE_KEY);
} catch (e) {
return null;
}
}
function setCache(config) {
const ttlSeconds = Number(config && config.ttlSeconds) || DEFAULT_TTL_SECONDS;
const now = Date.now();
const cache = {
data: config.data,
cachedAt: now,
expireAt: now + ttlSeconds * 1000
};
uni.setStorageSync(CACHE_KEY, cache);
return cache;
}
function isCacheValid(cache) {
if (!cache || !cache.expireAt) return false;
return Date.now() < cache.expireAt;
}
function applyConfig(config) {
if (!config) return;
if (config.api && config.api.baseUrl) {
ENV.API_BASE_URL = config.api.baseUrl;
}
if (config.webview && config.webview.baseUrl) {
ENV.WEBVIEW_BASE_URL = config.webview.baseUrl;
}
lastConfig = config;
}
function buildFailMessage(message) {
return message || '配置获取失败,请退出小程序重新打开。';
}
function showExitModal(message) {
uni.showModal({
title: '提示',
content: message,
showCancel: false,
confirmText: '退出',
success: () => {
// #ifdef MP-WEIXIN
if (uni.exitMiniProgram) {
uni.exitMiniProgram();
}
// #endif
// #ifdef APP-PLUS
if (typeof plus !== 'undefined' && plus.runtime && plus.runtime.quit) {
plus.runtime.quit();
}
// #endif
}
});
}
function validateConfig(config) {
if (!config) {
return { ok: false, message: buildFailMessage('配置无效,请退出小程序重新打开。') };
}
if (config.disabled) {
return {
ok: false,
message: config.disableReason || '服务暂不可用,请退出小程序重新打开。'
};
}
const currentVersion = getAppVersion();
const app = config.app || {};
const api = config.api || {};
const webview = config.webview || {};
if (app.minVersion && compareVersion(currentVersion, app.minVersion) < 0) {
return {
ok: false,
message: '当前版本过低,请退出小程序重新打开。'
};
}
if (api.version && webview.version && api.version !== webview.version) {
return {
ok: false,
message: '当前版本不一致,请退出小程序并重新打开以获取最新配置。'
};
}
if (api.minClientVersion && compareVersion(currentVersion, api.minClientVersion) < 0) {
return {
ok: false,
message: '当前版本过低,请退出小程序重新打开。'
};
}
if (webview.minClientVersion && compareVersion(currentVersion, webview.minClientVersion) < 0) {
return {
ok: false,
message: '当前版本过低,请退出小程序重新打开。'
};
}
return { ok: true, message: '' };
}
function requestConfig() {
const url = buildUrl(ENV.API_BASE_URL, '/api/open/app/config');
return new Promise((resolve, reject) => {
uni.request({
url,
method: 'GET',
timeout: 8000,
success: (res) => {
if (res.statusCode >= 200 && res.statusCode < 300 && res.data) {
resolve(res.data);
return;
}
reject({ message: '配置获取失败', response: res });
},
fail: (err) => reject(err)
});
});
}
export async function initAppConfig(options = {}) {
if (inFlight) return inFlight;
const { force = false } = options;
inFlight = (async () => {
const cache = getCache();
if (!force && isCacheValid(cache)) {
const validation = validateConfig(cache.data);
if (!validation.ok) {
showExitModal(validation.message);
throw new Error(validation.message);
}
applyConfig(cache.data);
return cache.data;
}
try {
const config = await requestConfig();
const validation = validateConfig(config);
if (!validation.ok) {
showExitModal(validation.message);
throw new Error(validation.message);
}
setCache(config);
applyConfig(config);
return config;
} catch (error) {
if (cache && isCacheValid(cache)) {
const validation = validateConfig(cache.data);
if (!validation.ok) {
showExitModal(validation.message);
throw new Error(validation.message);
}
applyConfig(cache.data);
return cache.data;
}
showExitModal(buildFailMessage());
throw error;
}
})();
try {
return await inFlight;
} finally {
inFlight = null;
}
}
export async function ensureAppConfig() {
const cache = getCache();
console.log('【业务代码定位】', cache, new Error().stack);
if (cache && isCacheValid(cache)) {
const validation = validateConfig(cache.data);
if (!validation.ok) {
showExitModal(validation.message);
throw new Error(validation.message);
}
applyConfig(cache.data);
return cache.data;
}
return initAppConfig({ force: true });
}
export function getAppConfig() {
return lastConfig || (getCache() ? getCache().data : null);
}
export function getWebviewBaseUrl() {
const config = getAppConfig();
let webviewBaseUrl = (config && config.webview && config.webview.baseUrl) || ENV.WEBVIEW_BASE_URL || '';
console.log(webviewBaseUrl);
return webviewBaseUrl;
}

16
common/env.js Normal file
View File

@ -0,0 +1,16 @@
export const ENV = {
API_BASE_URL: 'http://192.168.1.106:8081',
WEBVIEW_BASE_URL: '',
REQUEST_TIMEOUT: 15000,
APP_NAME: '领航专昇本志愿通',
APP_SUBTITLE: '这里填写副标题',
PROJECT_CODE: 'UNIAPP_ZSB',
SECURITY_ENABLE: false,
SECURITY_SECRET_KEY: ''
};
export const STORAGE_KEYS = {
TOKEN: 'wx_token',
USER_INFO: 'user_info',
APP_CONFIG: 'app_config_cache'
};

97
common/graceChecker.js Normal file
View File

@ -0,0 +1,97 @@
/**
数据验证表单验证
来自 grace.hcoder.net
作者 hcoder 深海
*/
export default {
error:'',
check : function (data, rule){
for(var i = 0; i < rule.length; i++){
if (!rule[i].checkType){return true;}
if (!rule[i].name) {return true;}
if (!rule[i].errorMsg) {return true;}
if (!data[rule[i].name]) {this.error = rule[i].errorMsg; return false;}
switch (rule[i].checkType){
case 'string':
var reg = new RegExp('^.{' + rule[i].checkRule + '}$');
if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
break;
case 'int':
var reg = new RegExp('^(-[1-9]|[1-9])[0-9]{' + rule[i].checkRule + '}$');
if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
break;
break;
case 'between':
if (!this.isNumber(data[rule[i].name])){
this.error = rule[i].errorMsg;
return false;
}
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'betweenD':
var reg = /^-?[1-9][0-9]?$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'betweenF':
var reg = /^-?[0-9][0-9]?.+[0-9]+$/;
if (!reg.test(data[rule[i].name])){this.error = rule[i].errorMsg; return false;}
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'same':
if (data[rule[i].name] != rule[i].checkRule) { this.error = rule[i].errorMsg; return false;}
break;
case 'notsame':
if (data[rule[i].name] == rule[i].checkRule) { this.error = rule[i].errorMsg; return false; }
break;
case 'email':
var reg = /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'phoneno':
var reg = /^1[0-9]{10,10}$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'zipcode':
var reg = /^[0-9]{6}$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'reg':
var reg = new RegExp(rule[i].checkRule);
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'in':
if(rule[i].checkRule.indexOf(data[rule[i].name]) == -1){
this.error = rule[i].errorMsg; return false;
}
break;
case 'notnull':
if(data[rule[i].name] == null || data[rule[i].name].length < 1){this.error = rule[i].errorMsg; return false;}
break;
}
}
return true;
},
isNumber : function (checkVal){
var reg = /^-?[1-9][0-9]?.?[0-9]*$/;
return reg.test(checkVal);
}
}

352
common/html-parser.js Normal file
View File

@ -0,0 +1,352 @@
/*
* HTML5 Parser By Sam Blowes
*
* Designed for HTML5 documents
*
* Original code by John Resig (ejohn.org)
* http://ejohn.org/blog/pure-javascript-html-parser/
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* ----------------------------------------------------------------------------
* License
* ----------------------------------------------------------------------------
*
* This code is triple licensed using Apache Software License 2.0,
* Mozilla Public License or GNU Public License
*
* ////////////////////////////////////////////////////////////////////////////
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* ////////////////////////////////////////////////////////////////////////////
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Simple HTML Parser.
*
* The Initial Developer of the Original Code is Erik Arvidsson.
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
* Reserved.
*
* ////////////////////////////////////////////////////////////////////////////
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ----------------------------------------------------------------------------
* Usage
* ----------------------------------------------------------------------------
*
* // Use like so:
* HTMLParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* // or to get an XML string:
* HTMLtoXML(htmlString);
*
* // or to get an XML DOM Document
* HTMLtoDOM(htmlString);
*
* // or to inject into an existing document/DOM node
* HTMLtoDOM(htmlString, document);
* HTMLtoDOM(htmlString, document.body);
*
*/
// Regular Expressions for parsing tags and attributes
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
// fixed by xxx 将 ins 标签从块级名单中移除
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
var special = makeMap('script,style');
function HTMLParser(html, handler) {
var index;
var chars;
var match;
var stack = [];
var last = html;
stack.last = function () {
return this[this.length - 1];
};
while (html) {
chars = true; // Make sure we're not in a script or style element
if (!stack.last() || !special[stack.last()]) {
// Comment
if (html.indexOf('<!--') == 0) {
index = html.indexOf('-->');
if (index >= 0) {
if (handler.comment) {
handler.comment(html.substring(4, index));
}
html = html.substring(index + 3);
chars = false;
} // end tag
} else if (html.indexOf('</') == 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
} // start tag
} else if (html.indexOf('<') == 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf('<');
var text = index < 0 ? html : html.substring(0, index);
html = index < 0 ? '' : html.substring(index);
if (handler.chars) {
handler.chars(text);
}
}
} else {
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
if (handler.chars) {
handler.chars(text);
}
return '';
});
parseEndTag('', stack.last());
}
if (html == last) {
throw 'Parse Error: ' + html;
}
last = html;
} // Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag('', stack.last());
}
}
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag('', tagName);
}
unary = empty[tagName] || !!unary;
if (!unary) {
stack.push(tagName);
}
if (handler.start) {
var attrs = [];
rest.replace(attr, function (match, name) {
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
if (!tagName) {
var pos = 0;
} // Find the closest opened tag of the same type
else {
for (var pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) {
break;
}
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--) {
if (handler.end) {
handler.end(stack[i]);
}
} // Remove the open elements from the stack
stack.length = pos;
}
}
}
function makeMap(str) {
var obj = {};
var items = str.split(',');
for (var i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
function removeDOCTYPE(html) {
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
}
function parseAttrs(attrs) {
return attrs.reduce(function (pre, attr) {
var value = attr.value;
var name = attr.name;
if (pre[name]) {
pre[name] = pre[name] + " " + value;
} else {
pre[name] = value;
}
return pre;
}, {});
}
function parseHtml(html) {
html = removeDOCTYPE(html);
var stacks = [];
var results = {
node: 'root',
children: []
};
HTMLParser(html, {
start: function start(tag, attrs, unary) {
var node = {
name: tag
};
if (attrs.length !== 0) {
node.attrs = parseAttrs(attrs);
}
if (unary) {
var parent = stacks[0] || results;
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
} else {
stacks.unshift(node);
}
},
end: function end(tag) {
var node = stacks.shift();
if (node.name !== tag) console.error('invalid state: mismatch end tag');
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
chars: function chars(text) {
var node = {
type: 'text',
text: text
};
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
comment: function comment(text) {
var node = {
node: 'comment',
text: text
};
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
});
return results.children;
}
export default parseHtml;

161
common/md5.js Normal file
View File

@ -0,0 +1,161 @@
function cmn(q, a, b, x, s, t) {
a = add32(add32(a, q), add32(x, t));
return add32((a << s) | (a >>> (32 - s)), b);
}
function ff(a, b, c, d, x, s, t) {
return cmn((b & c) | (~b & d), a, b, x, s, t);
}
function gg(a, b, c, d, x, s, t) {
return cmn((b & d) | (c & ~d), a, b, x, s, t);
}
function hh(a, b, c, d, x, s, t) {
return cmn(b ^ c ^ d, a, b, x, s, t);
}
function ii(a, b, c, d, x, s, t) {
return cmn(c ^ (b | ~d), a, b, x, s, t);
}
function md5cycle(state, x) {
let [a, b, c, d] = state;
a = ff(a, b, c, d, x[0], 7, -680876936);
d = ff(d, a, b, c, x[1], 12, -389564586);
c = ff(c, d, a, b, x[2], 17, 606105819);
b = ff(b, c, d, a, x[3], 22, -1044525330);
a = ff(a, b, c, d, x[4], 7, -176418897);
d = ff(d, a, b, c, x[5], 12, 1200080426);
c = ff(c, d, a, b, x[6], 17, -1473231341);
b = ff(b, c, d, a, x[7], 22, -45705983);
a = ff(a, b, c, d, x[8], 7, 1770035416);
d = ff(d, a, b, c, x[9], 12, -1958414417);
c = ff(c, d, a, b, x[10], 17, -42063);
b = ff(b, c, d, a, x[11], 22, -1990404162);
a = ff(a, b, c, d, x[12], 7, 1804603682);
d = ff(d, a, b, c, x[13], 12, -40341101);
c = ff(c, d, a, b, x[14], 17, -1502002290);
b = ff(b, c, d, a, x[15], 22, 1236535329);
a = gg(a, b, c, d, x[1], 5, -165796510);
d = gg(d, a, b, c, x[6], 9, -1069501632);
c = gg(c, d, a, b, x[11], 14, 643717713);
b = gg(b, c, d, a, x[0], 20, -373897302);
a = gg(a, b, c, d, x[5], 5, -701558691);
d = gg(d, a, b, c, x[10], 9, 38016083);
c = gg(c, d, a, b, x[15], 14, -660478335);
b = gg(b, c, d, a, x[4], 20, -405537848);
a = gg(a, b, c, d, x[9], 5, 568446438);
d = gg(d, a, b, c, x[14], 9, -1019803690);
c = gg(c, d, a, b, x[3], 14, -187363961);
b = gg(b, c, d, a, x[8], 20, 1163531501);
a = gg(a, b, c, d, x[13], 5, -1444681467);
d = gg(d, a, b, c, x[2], 9, -51403784);
c = gg(c, d, a, b, x[7], 14, 1735328473);
b = gg(b, c, d, a, x[12], 20, -1926607734);
a = hh(a, b, c, d, x[5], 4, -378558);
d = hh(d, a, b, c, x[8], 11, -2022574463);
c = hh(c, d, a, b, x[11], 16, 1839030562);
b = hh(b, c, d, a, x[14], 23, -35309556);
a = hh(a, b, c, d, x[1], 4, -1530992060);
d = hh(d, a, b, c, x[4], 11, 1272893353);
c = hh(c, d, a, b, x[7], 16, -155497632);
b = hh(b, c, d, a, x[10], 23, -1094730640);
a = hh(a, b, c, d, x[13], 4, 681279174);
d = hh(d, a, b, c, x[0], 11, -358537222);
c = hh(c, d, a, b, x[3], 16, -722521979);
b = hh(b, c, d, a, x[6], 23, 76029189);
a = hh(a, b, c, d, x[9], 4, -640364487);
d = hh(d, a, b, c, x[12], 11, -421815835);
c = hh(c, d, a, b, x[15], 16, 530742520);
b = hh(b, c, d, a, x[2], 23, -995338651);
a = ii(a, b, c, d, x[0], 6, -198630844);
d = ii(d, a, b, c, x[7], 10, 1126891415);
c = ii(c, d, a, b, x[14], 15, -1416354905);
b = ii(b, c, d, a, x[5], 21, -57434055);
a = ii(a, b, c, d, x[12], 6, 1700485571);
d = ii(d, a, b, c, x[3], 10, -1894986606);
c = ii(c, d, a, b, x[10], 15, -1051523);
b = ii(b, c, d, a, x[1], 21, -2054922799);
a = ii(a, b, c, d, x[8], 6, 1873313359);
d = ii(d, a, b, c, x[15], 10, -30611744);
c = ii(c, d, a, b, x[6], 15, -1560198380);
b = ii(b, c, d, a, x[13], 21, 1309151649);
a = ii(a, b, c, d, x[4], 6, -145523070);
d = ii(d, a, b, c, x[11], 10, -1120210379);
c = ii(c, d, a, b, x[2], 15, 718787259);
b = ii(b, c, d, a, x[9], 21, -343485551);
state[0] = add32(a, state[0]);
state[1] = add32(b, state[1]);
state[2] = add32(c, state[2]);
state[3] = add32(d, state[3]);
}
function md5blk(s) {
const blocks = [];
for (let i = 0; i < 64; i += 4) {
blocks[i >> 2] =
s.charCodeAt(i) +
(s.charCodeAt(i + 1) << 8) +
(s.charCodeAt(i + 2) << 16) +
(s.charCodeAt(i + 3) << 24);
}
return blocks;
}
function md5blkTail(s, length) {
const blocks = Array(16).fill(0);
let i = 0;
for (; i < length; i++) {
blocks[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
}
blocks[i >> 2] |= 0x80 << ((i % 4) << 3);
blocks[14] = length * 8;
return blocks;
}
function md51(str) {
let n = str.length;
let state = [1732584193, -271733879, -1732584194, 271733878];
let i;
for (i = 64; i <= n; i += 64) {
md5cycle(state, md5blk(str.substring(i - 64, i)));
}
const remaining = str.substring(i - 64);
md5cycle(state, md5blkTail(remaining, remaining.length));
return state;
}
function rhex(n) {
const hexChr = '0123456789abcdef';
let s = '';
for (let j = 0; j < 4; j++) {
s +=
hexChr.charAt((n >> (j * 8 + 4)) & 0x0f) +
hexChr.charAt((n >> (j * 8)) & 0x0f);
}
return s;
}
function hex(x) {
for (let i = 0; i < x.length; i++) {
x[i] = rhex(x[i]);
}
return x.join('');
}
function add32(a, b) {
return (a + b) & 0xffffffff;
}
export function md5(str) {
return hex(md51(str));
}

245
common/permission.js Normal file
View File

@ -0,0 +1,245 @@
/// null = 未请求1 = 已允许0 = 拒绝|受限, 2 = 系统未开启
var isIOS
function album() {
var result = 0;
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
var authStatus = PHPhotoLibrary.authorizationStatus();
if (authStatus === 0) {
result = null;
} else if (authStatus == 3) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(PHPhotoLibrary);
return result;
}
function camera() {
var result = 0;
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
if (authStatus === 0) {
result = null;
} else if (authStatus == 3) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(AVCaptureDevice);
return result;
}
function location() {
var result = 0;
var cllocationManger = plus.ios.import("CLLocationManager");
var enable = cllocationManger.locationServicesEnabled();
var status = cllocationManger.authorizationStatus();
if (!enable) {
result = 2;
} else if (status === 0) {
result = null;
} else if (status === 3 || status === 4) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(cllocationManger);
return result;
}
function push() {
var result = 0;
var UIApplication = plus.ios.import("UIApplication");
var app = UIApplication.sharedApplication();
var enabledTypes = 0;
if (app.currentUserNotificationSettings) {
var settings = app.currentUserNotificationSettings();
enabledTypes = settings.plusGetAttribute("types");
if (enabledTypes == 0) {
result = 0;
console.log("推送权限没有开启");
} else {
result = 1;
console.log("已经开启推送功能!")
}
plus.ios.deleteObject(settings);
} else {
enabledTypes = app.enabledRemoteNotificationTypes();
if (enabledTypes == 0) {
result = 3;
console.log("推送权限没有开启!");
} else {
result = 4;
console.log("已经开启推送功能!")
}
}
plus.ios.deleteObject(app);
plus.ios.deleteObject(UIApplication);
return result;
}
function contact() {
var result = 0;
var CNContactStore = plus.ios.import("CNContactStore");
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
if (cnAuthStatus === 0) {
result = null;
} else if (cnAuthStatus == 3) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(CNContactStore);
return result;
}
function record() {
var result = null;
var avaudiosession = plus.ios.import("AVAudioSession");
var avaudio = avaudiosession.sharedInstance();
var status = avaudio.recordPermission();
console.log("permissionStatus:" + status);
if (status === 1970168948) {
result = null;
} else if (status === 1735552628) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(avaudiosession);
return result;
}
function calendar() {
var result = null;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
if (ekAuthStatus == 3) {
result = 1;
console.log("日历权限已经开启");
} else {
console.log("日历权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
function memo() {
var result = null;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
if (ekAuthStatus == 3) {
result = 1;
console.log("备忘录权限已经开启");
} else {
console.log("备忘录权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
function requestIOS(permissionID) {
return new Promise((resolve, reject) => {
switch (permissionID) {
case "push":
resolve(push());
break;
case "location":
resolve(location());
break;
case "record":
resolve(record());
break;
case "camera":
resolve(camera());
break;
case "album":
resolve(album());
break;
case "contact":
resolve(contact());
break;
case "calendar":
resolve(calendar());
break;
case "memo":
resolve(memo());
break;
default:
resolve(0);
break;
}
});
}
function requestAndroid(permissionID) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[permissionID],
function(resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
var deniedPresentPermission = resultObj.deniedPresent[i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
},
function(error) {
console.log('result error: ' + error.message)
resolve({
code: error.code,
message: error.message
});
}
);
});
}
function gotoAppPermissionSetting() {
if (permission.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);
}
}
const permission = {
get isIOS(){
return typeof isIOS === 'boolean' ? isIOS : (isIOS = uni.getSystemInfoSync().platform === 'ios')
},
requestIOS: requestIOS,
requestAndroid: requestAndroid,
gotoAppSetting: gotoAppPermissionSetting
}
export default permission

102
common/request.js Normal file
View File

@ -0,0 +1,102 @@
import { ENV, STORAGE_KEYS } from './env';
import { md5 } from './md5';
const DEFAULT_TIMEOUT = ENV.REQUEST_TIMEOUT || 15000;
function buildQuery(params = {}) {
const entries = Object.entries(params).filter(([, value]) => value !== undefined && value !== null);
if (!entries.length) return '';
return entries
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
.join('&');
}
function combineUrl(base, path) {
if (/^https?:\/\//i.test(path)) return path;
const normalizedBase = (base || '').replace(/\/$/, '');
const normalizedPath = (path || '').replace(/^\//, '');
return `${normalizedBase}/${normalizedPath}`;
}
function request(method, url, data = {}, options = {}) {
const isQuery = method === 'GET' || method === 'DELETE';
const params = isQuery ? data : (options.params || {});
let finalUrl = combineUrl(ENV.API_BASE_URL, url);
const queryString = buildQuery(params);
if (queryString) {
finalUrl += (finalUrl.includes('?') ? '&' : '?') + queryString;
}
const header = Object.assign(
{ 'content-type': 'application/json' },
options.header || {}
);
if (ENV.SECURITY_ENABLE && ENV.SECURITY_SECRET_KEY) {
const timestamp = Date.now().toString();
header['X-App-Timestamp'] = timestamp;
header['X-App-Sign'] = md5(timestamp + ENV.SECURITY_SECRET_KEY);
}
const token = uni.getStorageSync(STORAGE_KEYS.TOKEN);
if (token) {
header.Authorization = `Bearer ${token}`;
} else if (!options.allowGuest) {
uni.showModal({
title: '提示',
content: '未登录,请先登录',
showCancel: false,
success: () => {
uni.navigateTo({ url: '/pages/login/login' });
}
});
return Promise.reject({ message: '未登录' });
}
return new Promise((resolve, reject) => {
uni.request({
url: finalUrl,
method,
data: isQuery ? {} : data,
header,
timeout: options.timeout || DEFAULT_TIMEOUT,
success: (res) => {
const { statusCode, data: resData } = res;
const businessCode = resData && typeof resData.code !== 'undefined' ? resData.code : null;
if (statusCode === 401 || businessCode === 401) {
uni.showModal({
title: '提示',
content: '登录已失效,请重新登录',
showCancel: false,
success: () => {
uni.removeStorageSync(STORAGE_KEYS.TOKEN);
uni.removeStorageSync(STORAGE_KEYS.USER_INFO);
uni.navigateTo({ url: '/pages/login/login' });
}
});
reject({ message: '登录失效', statusCode, data: resData });
return;
}
if (statusCode >= 200 && statusCode < 300) {
resolve(options.raw ? res : resData);
return;
}
reject({
message: resData && (resData.msg || resData.message) ? (resData.msg || resData.message) : '请求失败',
statusCode,
data: resData
});
},
fail: (err) => {
reject(err);
}
});
});
}
export const get = (url, params, options) => request('GET', url, params, options);
export const post = (url, data, options) => request('POST', url, data, options);
export const put = (url, data, options) => request('PUT', url, data, options);
export const del = (url, params, options) => request('DELETE', url, params, options);
export default request;

136
common/uni-nvue.css Normal file
View File

@ -0,0 +1,136 @@
/* #ifndef APP-PLUS-NVUE */
page {
min-height: 100%;
height: auto;
}
/* #endif */
/* 解决头条小程序字体图标不显示问题因为头条运行时自动插入了span标签且有全局字体 */
/* #ifdef MP-TOUTIAO */
/* text :not(view) {
font-family: uniicons;
} */
/* #endif */
.uni-icon {
font-family: uniicons;
font-weight: normal;
}
.uni-container {
padding: 15px;
background-color: #f8f8f8;
}
.uni-header-logo {
/* #ifdef H5 */
display: flex;
/* #endif */
padding: 15px 15px;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 10rpx;
}
.uni-header-image {
width: 80px;
height: 80px;
}
.uni-hello-text {
margin-bottom: 20px;
}
.hello-text {
color: #7A7E83;
font-size: 14px;
line-height: 20px;
}
.hello-link {
color: #7A7E83;
font-size: 14px;
line-height: 20px;
}
.uni-panel {
margin-bottom: 12px;
}
.uni-panel-h {
/* #ifdef H5 */
display: flex;
/* #endif */
background-color: #ffffff;
flex-direction: row !important;
/* justify-content: space-between !important; */
align-items: center !important;
padding: 12px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
/*
.uni-panel-h:active {
background-color: #f8f8f8;
}
*/
.uni-panel-h-on {
background-color: #f0f0f0;
}
.uni-panel-text {
flex: 1;
color: #000000;
font-size: 14px;
font-weight: normal;
}
.uni-panel-icon {
margin-left: 15px;
color: #999999;
font-size: 14px;
font-weight: normal;
transform: rotate(0deg);
transition-duration: 0s;
transition-property: transform;
}
.uni-panel-icon-on {
transform: rotate(180deg);
}
.uni-navigate-item {
/* #ifdef H5 */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
background-color: #FFFFFF;
border-top-style: solid;
border-top-color: #f0f0f0;
border-top-width: 1px;
padding: 12px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-navigate-item:active {
background-color: #f8f8f8;
}
.uni-navigate-text {
flex: 1;
color: #000000;
font-size: 14px;
font-weight: normal;
}
.uni-navigate-icon {
margin-left: 15px;
color: #999999;
font-size: 14px;
font-weight: normal;
}

1458
common/uni.css Normal file

File diff suppressed because it is too large Load Diff

73
common/util.js Normal file
View File

@ -0,0 +1,73 @@
function formatTime(time) {
if (typeof time !== 'number' || time < 0) {
return time
}
var hour = parseInt(time / 3600)
time = time % 3600
var minute = parseInt(time / 60)
time = time % 60
var second = time
return ([hour, minute, second]).map(function(n) {
n = n.toString()
return n[1] ? n : '0' + n
}).join(':')
}
function formatLocation(longitude, latitude) {
if (typeof longitude === 'string' && typeof latitude === 'string') {
longitude = parseFloat(longitude)
latitude = parseFloat(latitude)
}
longitude = longitude.toFixed(2)
latitude = latitude.toFixed(2)
return {
longitude: longitude.toString().split('.'),
latitude: latitude.toString().split('.')
}
}
var dateUtils = {
UNITS: {
'年': 31557600000,
'月': 2629800000,
'天': 86400000,
'小时': 3600000,
'分钟': 60000,
'秒': 1000
},
humanize: function(milliseconds) {
var humanize = '';
for (var key in this.UNITS) {
if (milliseconds >= this.UNITS[key]) {
humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
break;
}
}
return humanize || '刚刚';
},
format: function(dateStr) {
var date = this.parse(dateStr)
var diff = Date.now() - date.getTime();
if (diff < this.UNITS['天']) {
return this.humanize(diff);
}
var _format = function(number) {
return (number < 10 ? ('0' + number) : number);
};
return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +
_format(date.getHours()) + ':' + _format(date.getMinutes());
},
parse: function(str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串转化为一个Date对象
var a = str.split(/[^0-9]/);
return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
}
};
export {
formatTime,
formatLocation,
dateUtils
}

View File

@ -0,0 +1,181 @@
import amap from '@/components/amap-wx/lib/amap-wx.js';
// 地铁颜色图
const line = {
'1号线': '#C43B33',
'2号线': '#016299',
'4号线/大兴线': '#008E9C',
'5号线': '#A42380',
'6号线': '#D09900',
'7号线': '#F2C172',
'8号线': '#009D6A',
'9号线': '#8FC41E',
'10号线': '#009DBE',
'13号线': '#F9E701',
'14号线东段': '#D4A7A2',
'14号线西段': '#D4A7A2',
'15号线': '#5D2D69',
'八通线': '#C33A32',
'昌平线': '#DE82B1',
'亦庄线': '#E40177',
'房山线': '#E66021',
'机场线': '#A29BBC',
}
// 150500:地铁站 ,150700:公交站 , 190700:地名地址
const typecode = [{
id: '150500',
icon: 'icon-ditie'
}, {
id: '150700',
icon: 'icon-gongjiao'
}, {
id: '190700',
icon: 'icon-gonglu'
}];
const util = {
key:'b526b09b86cd2996e7732be8ab8c4430',
/**
* 初始化高德地图api
*/
mapInit() {
return new amap.AMapWX({
key: this.key
});
},
// 服务状态吗
typecode,
/**
* 获取地图颜色
*/
lineColor(name) {
if (line[name]) {
return line[name];
} else {
return '#ccc';
}
},
/**
* 关键字颜色变化
*/
serachNmme(val, name) {
let namestr = new RegExp(val);
let nameresult =
`<div style="font-size: 14px;color: #333;line-height: 1.5;">
${name.replace(namestr, "<span style='color:#66ccff;'>" + val + '</span>')}
</div>`
.trim();
return nameresult;
},
/**
* 地址转地铁线路
*/
addressToLine(address, type) {
let addr = address.split(';');
let dt = '';
addr.forEach(elm => {
let color = '#cccccc';
if (type === typecode[0].id) {
color = this.lineColor(elm)
} else if (type === typecode[1].id) {
color = '#4075cb'
}
let style = 'margin:5px 0;margin-right:5px;padding:0 5px;background:' + color +
';font-size:12px;color:#fff;border-radius:3px;';
dt += `<div style=\'${style}\'>${elm}</div>`;
});
return `<div style="display:flex;flex-wrap: wrap;">${dt}</div>`;
},
/**
* 数据处理
*/
dataHandle(item, val) {
// 改变字体颜色
if (val) {
item.nameNodes = util.serachNmme(val, item.name);
} else {
item.nameNodes = `<div style="font-size: 14px;color: #333;line-height: 1.5;">${item.name}</div>`;
}
// 地址解析 地铁
if (
item.typecode === util.typecode[0].id ||
item.typecode === util.typecode[1].id
) {
item.addressNodes = util.addressToLine(item.address, item.typecode);
if (item.typecode === util.typecode[0].id) {
item.icon = util.typecode[0].icon;
} else if (item.typecode === util.typecode[1].id) {
item.icon = util.typecode[1].icon;
}
} else {
item.addressNodes = `<span>${item.district}${
item.address.length > 0 ? '·' + item.address : ''
}</span>`.trim();
item.icon = 'icon-weizhi';
}
if (item.location && item.location.length === 0) {
item.icon = 'icon-sousuo';
}
return item;
},
/**
* 存储历史数据
* val [string | object]需要存储的内容
*/
setHistory(val) {
let searchHistory = uni.getStorageSync('search:history');
if (!searchHistory) searchHistory = [];
let serachData = {};
if (typeof(val) === 'string') {
serachData = {
adcode: [],
address: [],
city: [],
district: [],
id: [],
location: [],
name: val,
typecode: []
};
} else {
serachData = val
}
// 判断数组是否存在,如果存在,那么将放到最前面
for (var i = 0; i < searchHistory.length; i++) {
if (searchHistory[i].name === serachData.name) {
searchHistory.splice(i, 1);
break;
}
}
searchHistory.unshift(util.dataHandle(serachData));
uni.setStorage({
key: 'search:history',
data: searchHistory,
success: function() {
// console.log('success');
}
});
},
getHistory() {
},
removeHistory() {
uni.removeStorage({
key: 'search:history',
success: function(res) {
console.log('success');
}
});
return []
}
}
export default util;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,158 @@
<template>
<view class="uni-padding-wrap">
<page-head :title="title"></page-head>
<button class="button" @click="setTabBarBadge">{{ !hasSetTabBarBadge ? '设置tab徽标' : '移除tab徽标' }}</button>
<!-- #ifndef MP-HARMONY -->
<button class="button" @click="showTabBarRedDot">{{ !hasShownTabBarRedDot ? '显示红点' : '移除红点'}}</button>
<button class="button" @click="customStyle">{{ !hasCustomedStyle ? '自定义Tab样式' : '移除自定义样式'}}</button>
<button class="button" @click="customItem">{{ !hasCustomedItem ? '自定义Tab信息' : '移除自定义信息' }}</button>
<!-- #endif -->
<button class="button" @click="hideTabBar">{{ !hasHiddenTabBar ? '隐藏TabBar' : '显示TabBar' }}</button>
<view class="btn-area">
<button class="button" type="primary" @click="navigateBack">返回上一级</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'tababr',
hasSetTabBarBadge: false,
hasShownTabBarRedDot: false,
hasCustomedStyle: false,
hasCustomedItem: false,
hasHiddenTabBar: false
}
},
destroyed() {
if (this.hasSetTabBarBadge) {
uni.removeTabBarBadge({
index: 1
})
}
if (this.hasShownTabBarRedDot) {
uni.hideTabBarRedDot({
index: 1
})
}
if (this.hasHiddenTabBar) {
uni.showTabBar()
}
if (this.hasCustomedStyle) {
uni.setTabBarStyle({
color: '#7A7E83',
selectedColor: '#007AFF',
backgroundColor: '#F8F8F8',
borderStyle: 'black'
})
}
if (this.hasCustomedItem) {
let tabBarOptions = {
index: 1,
text: '接口',
iconPath: '/static/api.png',
selectedIconPath: '/static/apiHL.png'
}
uni.setTabBarItem(tabBarOptions)
}
},
methods: {
navigateBack() {
this.$emit('unmount')
},
setTabBarBadge() {
if(this.hasShownTabBarRedDot){
uni.hideTabBarRedDot({
index: 1
})
this.hasShownTabBarRedDot = !this.hasShownTabBarRedDot
}
if (!this.hasSetTabBarBadge) {
uni.setTabBarBadge({
index: 1,
text: '1'
})
} else {
uni.removeTabBarBadge({
index: 1
})
}
this.hasSetTabBarBadge = !this.hasSetTabBarBadge
},
showTabBarRedDot() {
if(this.hasSetTabBarBadge) {
uni.removeTabBarBadge({
index: 1
})
this.hasSetTabBarBadge = !this.hasSetTabBarBadge
}
if (!this.hasShownTabBarRedDot) {
uni.showTabBarRedDot({
index: 1
})
} else {
uni.hideTabBarRedDot({
index: 1
})
}
this.hasShownTabBarRedDot = !this.hasShownTabBarRedDot
},
hideTabBar() {
if (!this.hasHiddenTabBar) {
uni.hideTabBar()
} else {
uni.showTabBar()
}
this.hasHiddenTabBar = !this.hasHiddenTabBar
},
customStyle() {
if (this.hasCustomedStyle) {
uni.setTabBarStyle({
color: '#7A7E83',
selectedColor: '#007AFF',
backgroundColor: '#F8F8F8',
borderStyle: 'black'
})
} else {
uni.setTabBarStyle({
color: '#FFF',
selectedColor: '#007AFF',
backgroundColor: '#000000',
borderStyle: 'black'
})
}
this.hasCustomedStyle = !this.hasCustomedStyle
},
customItem() {
let tabBarOptions = {
index: 1,
text: '接口',
iconPath: '/static/api.png',
selectedIconPath: '/static/apiHL.png'
}
if (this.hasCustomedItem) {
uni.setTabBarItem(tabBarOptions)
} else {
tabBarOptions.text = 'API'
uni.setTabBarItem(tabBarOptions)
}
this.hasCustomedItem = !this.hasCustomedItem
}
}
}
</script>
<style>
.button {
margin-top: 30rpx;
margin-left: 0;
margin-right: 0;
}
.btn-area {
padding-top: 30rpx;
}
</style>

View File

@ -0,0 +1 @@
export default './lib/marked'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
<template name="page-foot">
<view class="page-share-title">
<text>感谢{{name}}提供本示例</text>
<text class="submit" @click="submit">我也提交</text>
</view>
</template>
<script>
export default {
name: "page-foot",
props: {
name: {
type: String,
default: ""
}
},
methods: {
submit() {
uni.showModal({
content:"hello uni-app开源地址为https://github.com/dcloudio/uni-app/tree/master/examples请在这个开源项目上贡献你的代码",
showCancel:false
})
}
}
}
</script>
<style>
.page-share-title {
text-align: center;
font-size: 30rpx;
color: #BEBEBE;
padding: 20rpx 0;
}
.submit {
border-bottom: 1rpx solid #BEBEBE;
}
</style>

View File

@ -0,0 +1,16 @@
<template name="page-head">
<view class="common-page-head">
<view class="common-page-head-title">{{title}}</view>
</view>
</template>
<script>
export default {
name: "page-head",
props: {
title: {
type: String,
default: ""
}
}
}
</script>

66
components/product.vue Normal file
View File

@ -0,0 +1,66 @@
<template>
<view class="product">
<image class="product-image" :src="image ? image : 'https://via.placeholder.com/150x200'"></image>
<view class="product-title">{{title}}</view>
<view class="product-price">
<text class="product-price-favour">{{originalPrice}}</text>
<text class="product-price-original">{{favourPrice}}</text>
<text class="product-tip">{{tip}}</text>
</view>
</view>
</template>
<script>
export default {
name: 'product',
props: ['image', 'title', 'originalPrice', 'favourPrice', 'tip']
}
</script>
<style>
.product {
padding: 10rpx 20rpx;
display: flex;
flex-direction: column;
}
.product-image {
height: 330rpx;
width: 330rpx;
}
.product-title {
width: 300rpx;
font-size: 32rpx;
word-break: break-all;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.product-price {
font-size: 28rpx;
position: relative;
}
.product-price-original {
color: #E80080;
}
.product-price-favour {
color: #888888;
text-decoration: line-through;
margin-left: 10rpx;
}
.product-tip {
position: absolute;
right: 10rpx;
background-color: #FF3333;
color: #FFFFFF;
padding: 0 10rpx;
border-radius: 5rpx;
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<view class="view">
<view class="list-cell view" hover-class="uni-list-cell-hover" @click="bindClick">
<view class="media-list view" v-if="options.title">
<view class="view" :class="{'media-image-right': options.article_type === 2, 'media-image-left': options.article_type === 1}">
<text class="media-title" :class="{'media-title2': options.article_type === 1 || options.article_type === 2}">{{options.title}}</text>
<view v-if="options.image_list || options.image_url" class="image-section view" :class="{'image-section-right': options.article_type === 2, 'image-section-left': options.article_type === 1}">
<image class="image-list1" :class="{'image-list2': options.article_type === 1 || options.article_type === 2}"
v-if="options.image_url" :src="options.image_url"></image>
<image class="image-list3" v-if="options.image_list" :src="source.url" v-for="(source, i) in options.image_list"
:key="i" />
</view>
</view>
<view class="media-foot view">
<view class="media-info view">
<text class="info-text">{{options.source}}</text>
<text class="info-text">{{options.comment_count}}条评论</text>
<text class="info-text">{{options.datetime}}</text>
</view>
<view class="max-close-view view" @click.stop="close">
<view class="close-view view"><text class="close">×</text></view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
options: {
type: Object,
default: function(e) {
return {}
}
}
},
methods: {
close(e) {
this.$emit('close');
},
bindClick() {
this.$emit('click');
}
}
}
</script>
<style>
.view {
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.list-cell {
width: 750rpx;
padding: 0 30rpx;
}
.uni-list-cell-hover {
background-color: #eeeeee;
}
.media-list {
flex: 1;
flex-direction: column;
border-bottom-width: 1rpx;
border-bottom-style: solid;
border-bottom-color: #c8c7cc;
padding: 20rpx 0;
}
.media-image-right {
flex-direction: row;
}
.media-image-left {
flex-direction: row-reverse;
}
.media-title {
flex: 1;
}
.media-title {
lines: 3;
text-overflow: ellipsis;
font-size: 32rpx;
color: #555555;
}
.media-title2 {
flex: 1;
margin-top: 6rpx;
line-height: 40rpx;
}
.image-section {
margin-top: 20rpx;
flex-direction: row;
justify-content: space-between;
}
.image-section-right {
margin-top: 0rpx;
margin-left: 10rpx;
width: 225rpx;
height: 146rpx;
}
.image-section-left {
margin-top: 0rpx;
margin-right: 10rpx;
width: 225rpx;
height: 146rpx;
}
.image-list1 {
width: 690rpx;
height: 481rpx;
}
.image-list2 {
width: 225rpx;
height: 146rpx;
}
.image-list3 {
width: 225rpx;
height: 146rpx;
}
.media-info {
flex-direction: row;
}
.info-text {
margin-right: 20rpx;
color: #999999;
font-size: 24rpx;
}
.media-foot {
margin-top: 20rpx;
flex-direction: row;
justify-content: space-between;
}
.max-close-view {
align-items: center;
justify-content: flex-end;
flex-direction: row;
height: 40rpx;
width: 80rpx;
}
.close-view {
border-style: solid;
border-width: 1px;
border-color: #999999;
border-radius: 10rpx;
justify-content: center;
height: 30rpx;
width: 40rpx;
line-height: 30rpx;
}
.close {
text-align: center;
color: #999999;
font-size: 28rpx;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
<template>
<text style="text-decoration:underline" :href="href" @click="openURL" :inWhiteList="inWhiteList">{{text}}</text>
</template>
<script>
/**
* @description u-link是一个外部网页超链接组件在小程序内打开内部web-view组件或复制url在app内打开外部浏览器在h5端打开新网页
* @property {String} href 点击后打开的外部网页url小程序中必须以https://
* @property {String} text 显示的文字
* @property {Boolean} inWhiteList 是否在小程序白名单中如果在的话在小程序端会直接打开内置web-view否则会只会复制url提示在外部打开
* @example * <u-link href="https://ext.dcloud.net.cn" text="https://ext.dcloud.net.cn" :inWhiteList="true"></u-link>
*/
export default {
name: 'u-link',
props: {
href: {
type: String,
default: ''
},
text: {
type: String,
default: ''
},
inWhiteList: {
type: Boolean,
default: false
}
},
methods: {
openURL() {
// #ifdef APP-PLUS
plus.runtime.openURL(this.href) //使web-view
// #endif
// #ifdef H5
window.open(this.href)
// #endif
// #ifdef MP
if (this.inWhiteList) { //webview
uni.navigateTo({
url: '/pages/component/web-view/web-view?url=' + this.href
});
} else {
uni.setClipboardData({
data: this.href
});
uni.showModal({
content: '本网址无法直接在小程序内打开。已自动复制网址,请在手机浏览器里粘贴该网址',
showCancel: false
});
}
// #endif
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,95 @@
# 小程序接口与 WebView 版本一致性方案
## 目标
- 小程序获取接口地址与 WebView 地址。
- 判断版本一致性与可用性。
- 不可用时提示用户退出小程序重新打开。
## 总体思路
采用“配置中心 + 启动校验 + 缓存兜底 + 失效提示”的方案:
1. 小程序启动时请求配置中心接口获取最新配置。
2. 校验小程序版本、API版本、WebView版本一致性。
3. 校验失败则提示退出重启;通过则写入缓存并使用配置。
4. 配置接口不可用时使用缓存,缓存失效则提示退出重启。
## 配置中心接口
- 方法: `GET`
- 路径: `/api/open/app/config`
### 返回示例
```json
{
"app": {
"minVersion": "1.2.0",
"latestVersion": "1.3.5",
"forceUpdate": true
},
"api": {
"baseUrl": "https://api.xxx.com",
"version": "2026-03-16",
"minClientVersion": "1.2.0"
},
"webview": {
"baseUrl": "https://m.xxx.com",
"version": "2026-03-16",
"minClientVersion": "1.2.0"
},
"ttlSeconds": 3600,
"disabled": false,
"disableReason": ""
}
```
### 字段说明
- `app.minVersion`: 最低可用小程序版本。
- `app.latestVersion`: 最新版本号。
- `app.forceUpdate`: 是否强制更新。
- `api.baseUrl`: API 入口地址。
- `api.version`: API 版本号(用于一致性校验)。
- `api.minClientVersion`: API 允许的最小客户端版本。
- `webview.baseUrl`: WebView 入口地址。
- `webview.version`: WebView 版本号(用于一致性校验)。
- `webview.minClientVersion`: WebView 允许的最小客户端版本。
- `ttlSeconds`: 配置缓存有效期(秒)。
- `disabled`: 服务下线开关。
- `disableReason`: 下线原因。
## 小程序启动校验流程
1. 小程序 `App.onLaunch` 请求 `/api/open/app/config`
2. 校验顺序:
- `disabled == true` → 提示“服务暂不可用,请退出小程序重新打开”。
- `当前版本 < app.minVersion` → 提示强更并阻断。
- `api.version != webview.version` → 提示“版本不一致,请退出小程序重新打开”。
- `当前版本 < api.minClientVersion``当前版本 < webview.minClientVersion` → 提示强更。
3. 校验通过:
- 缓存配置(本地 `storage`)。
- 全局设置 API baseUrl 与 WebView baseUrl。
## 缓存与兜底策略
- 配置接口失败:
- 如果本地缓存存在且未过期 → 使用缓存。
- 若缓存过期或不存在 → 提示“配置获取失败,请退出小程序重新打开”。
## 提示文案建议
- 版本不一致:
- “当前版本不一致,请退出小程序并重新打开以获取最新配置。”
- 服务不可用:
- “服务暂不可用,请退出小程序重新打开。”
- 配置获取失败:
- “配置获取失败,请退出小程序重新打开。”
## WebView 入口处理
- WebView URL 必须由配置中心下发,避免在客户端写死。
- 进入 WebView 前再次校验缓存是否过期;过期则重新拉取。
## 前端需要完成的部分
1. 在 `App.onLaunch` 拉取 `/api/open/app/config`,并写入本地缓存。
2. 实现版本校验逻辑(`app.minVersion`、`api.version`、`webview.version`、`minClientVersion`)。
3. 校验失败时弹窗提示,并引导退出小程序重新打开。
4. 缓存过期或接口失败时,优先使用缓存;无缓存则提示退出。
5. WebView 入口统一从配置下发的 `webview.baseUrl` 拼接跳转。
6. 请求 API 时统一使用配置下发的 `api.baseUrl`
## 可选扩展
- 灰度策略:按用户或设备下发不同配置。
- 公告字段:返回 `notice` 用于维护提示。

404
docs/Task1.md Normal file
View File

@ -0,0 +1,404 @@
# UniApp + H5混合开发微信小程序 技术实施方案UniApp端
你希望通过UniApp开发微信小程序以web-view嵌套H5承载核心业务展示同时由UniApp原生层负责登录、退出、支付等核心能力以下是UniApp端的详细技术实施方案明确需要开发的核心模块、功能和关键逻辑。
## 一、整体架构与职责边界
### 核心分工
| 模块/能力 | UniApp小程序端负责 | H5端负责 |
|----------------|---------------------------------------------|--------------------------------|
| 用户身份 | 微信登录、token管理、用户信息存储、退出登录逻辑 | 接收并使用用户信息,触发退出登录指令 |
| 支付 | 调起微信支付、支付结果处理、支付状态同步 | 发起支付请求、展示支付结果 |
| 页面容器 | 提供tabBar框架、web-view容器、页面跳转控制 | 承载table栏、业务内容展示、内部页面跳转 |
| 通信 | 接收H5消息、主动向H5推送数据、处理通信异常 | 向UniApp发送指令、接收UniApp数据、展示状态反馈 |
## 二、UniApp端核心开发内容
### 1. 项目基础配置
#### 1全局配置pages.json
配置tabBar、页面路由、web-view权限等核心配置
```json
{
"pages": [
// 登录页
"pages/login/login",
// tabBar主页面嵌套web-view
"pages/index/index",
// 其他tabBar页面如需
"pages/my/my"
],
"tabBar": {
"color": "#666666",
"selectedColor": "#007AFF",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/my/my",
"text": "我的",
"iconPath": "static/tabbar/my.png",
"selectedIconPath": "static/tabbar/my-active.png"
}
]
},
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序定位"
}
},
"webview": {
"webviewParameter": {
"allowUniversalAccessFromFileURLs": true,
"allowFileAccess": true
}
}
}
```
#### 2微信小程序配置manifest.json
开启微信小程序相关权限配置AppID等
```json
{
"mp-weixin": {
"appid": "你的微信小程序AppID",
"setting": {
"urlCheck": false, // 允许访问非域名白名单的H5开发阶段正式需配置白名单
"es6": true,
"postcss": true
},
"usingComponents": true,
"permission": {
"scope.userInfo": {
"desc": "获取你的用户信息用于登录"
}
}
}
}
```
### 2. 核心页面开发
#### 1登录页pages/login/login.vue
负责微信授权登录、获取用户信息、token存储
```vue
<template>
<view class="login-container">
<button open-type="getUserProfile" @click="wxLogin" type="primary">微信快捷登录</button>
</view>
</template>
<script>
export default {
methods: {
async wxLogin() {
try {
// 1. 获取微信登录code
const loginRes = await uni.login({ provider: 'weixin' });
if (loginRes.code) {
// 2. 调用后端接口换取用户token和信息
const userRes = await uni.request({
url: '你的后端登录接口', // 如https://api.xxx.com/wx/login
method: 'POST',
data: {
code: loginRes.code,
appid: '你的小程序AppID'
}
});
if (userRes.data.code === 200) {
// 3. 存储用户信息和token全局可用
const { token, userInfo } = userRes.data.data;
uni.setStorageSync('wx_token', token);
uni.setStorageSync('user_info', userInfo);
// 4. 登录成功跳转到首页
uni.switchTab({ url: '/pages/index/index' });
uni.showToast({ title: '登录成功', icon: 'success' });
} else {
uni.showToast({ title: userRes.data.msg, icon: 'none' });
}
}
} catch (err) {
uni.showToast({ title: '登录失败,请重试', icon: 'none' });
console.error('登录异常:', err);
}
}
}
};
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
button {
width: 80%;
height: 80rpx;
font-size: 32rpx;
}
</style>
```
#### 2tabBar首页pages/index/index.vue
核心容器页嵌套web-view、处理与H5的通信
```vue
<template>
<view class="webview-container">
<!-- web-view组件嵌套H5页面绑定通信事件 -->
<web-view
:src="h5Url"
@message="handleH5Message"
id="main-webview"
></web-view>
</view>
</template>
<script>
export default {
data() {
return {
h5Url: '你的H5网页地址', // 如https://h5.xxx.com/home
userToken: '',
userInfo: {}
};
},
onShow() {
// 页面显示时校验登录态,无登录态则跳转登录页
this.checkLoginStatus();
// 拼接用户信息到H5链接初始化传参
this.initH5Url();
},
methods: {
// 1. 校验登录状态
checkLoginStatus() {
this.userToken = uni.getStorageSync('wx_token');
this.userInfo = uni.getStorageSync('user_info');
if (!this.userToken) {
uni.redirectTo({ url: '/pages/login/login' });
}
},
// 2. 初始化H5链接携带用户信息
initH5Url() {
if (this.userToken) {
// 拼接token、用户ID等关键信息到H5链接
this.h5Url = `${this.h5Url}?token=${this.userToken}&userId=${this.userInfo.id}&nickName=${encodeURIComponent(this.userInfo.nickName)}`;
}
},
// 3. 接收H5发送的消息核心通信逻辑
handleH5Message(e) {
// H5发送的消息格式建议{ type: '指令类型', data: '指令参数' }
const [message] = e.detail.data;
if (!message) return;
switch (message.type) {
// H5触发支付
case 'triggerPay':
this.handlePay(message.data);
break;
// H5触发退出登录
case 'triggerLogout':
this.handleLogout();
break;
// H5请求重新获取用户信息
case 'getUserInfo':
this.sendUserInfoToH5();
break;
default:
console.log('未知指令类型:', message.type);
}
},
// 4. 处理支付逻辑UniApp原生调起微信支付
async handlePay(payParams) {
try {
// 4.1 调用后端获取微信支付参数如prepay_id
const payRes = await uni.request({
url: '你的后端支付接口', // 如https://api.xxx.com/pay/create
method: 'POST',
data: {
token: this.userToken,
...payParams // H5传递的订单ID、金额等参数
}
});
if (payRes.data.code !== 200) {
uni.showToast({ title: payRes.data.msg, icon: 'none' });
// 通知H5支付失败
this.sendMsgToH5({ type: 'payFail', data: payRes.data.msg });
return;
}
// 4.2 调起微信支付
const wxPayParams = payRes.data.data;
uni.requestPayment({
timeStamp: wxPayParams.timeStamp,
nonceStr: wxPayParams.nonceStr,
package: wxPayParams.package,
signType: 'MD5',
paySign: wxPayParams.paySign,
// 支付成功回调
success: () => {
uni.showToast({ title: '支付成功', icon: 'success' });
this.sendMsgToH5({ type: 'paySuccess', data: '支付成功' });
},
// 支付失败/取消回调
fail: (err) => {
uni.showToast({ title: '支付失败', icon: 'none' });
this.sendMsgToH5({ type: 'payFail', data: err.errMsg });
}
});
} catch (err) {
console.error('支付异常:', err);
this.sendMsgToH5({ type: 'payFail', data: '支付接口异常' });
}
},
// 5. 处理退出登录
handleLogout() {
// 清除本地存储的登录态
uni.removeStorageSync('wx_token');
uni.removeStorageSync('user_info');
// 通知H5退出成功
this.sendMsgToH5({ type: 'logoutSuccess' });
// 跳转登录页
uni.redirectTo({ url: '/pages/login/login' });
},
// 6. 主动向H5发送消息
sendMsgToH5(msg) {
// 创建web-view上下文指定ID与页面中web-view的id一致
const webViewCtx = uni.createWebViewContext('main-webview');
// 发送消息H5需监听message事件接收
webViewCtx.postMessage({ data: msg });
},
// 7. 主动向H5推送用户信息
sendUserInfoToH5() {
this.sendMsgToH5({
type: 'userInfo',
data: {
token: this.userToken,
userId: this.userInfo.id,
nickName: this.userInfo.nickName,
avatarUrl: this.userInfo.avatarUrl
}
});
}
}
};
</script>
<style scoped>
.webview-container {
width: 100%;
height: 100vh;
}
</style>
```
#### 3“我的”页面pages/my/my.vue
可选,如需原生展示用户信息、退出按钮等:
```vue
<template>
<view class="my-container">
<view class="user-info">
<image :src="userInfo.avatarUrl" class="avatar"></image>
<text class="nickname">{{ userInfo.nickName }}</text>
</view>
<button @click="handleLogout" type="warn">退出登录</button>
</view>
</template>
<script>
export default {
data() {
return {
userInfo: {}
};
},
onShow() {
this.userInfo = uni.getStorageSync('user_info');
},
methods: {
handleLogout() {
uni.removeStorageSync('wx_token');
uni.removeStorageSync('user_info');
uni.redirectTo({ url: '/pages/login/login' });
}
}
};
</script>
<style scoped>
.my-container {
padding: 40rpx;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 60rpx;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
margin-right: 30rpx;
}
.nickname {
font-size: 36rpx;
font-weight: bold;
}
button {
width: 100%;
height: 80rpx;
}
</style>
```
### 3. 全局工具类(可选)
封装通信、登录校验等通用方法,便于复用:
```js
// common/utils.js
/**
* 向H5发送消息
* @param {String} webViewId web-view的ID
* @param {Object} msg 消息内容
*/
export const sendMsgToH5 = (webViewId, msg) => {
const webViewCtx = uni.createWebViewContext(webViewId);
webViewCtx.postMessage({ data: msg });
};
/**
* 校验登录态
* @returns {Boolean} 是否已登录
*/
export const checkLogin = () => {
const token = uni.getStorageSync('wx_token');
if (!token) {
uni.redirectTo({ url: '/pages/login/login' });
return false;
}
return true;
};
```
### 4. 关键注意事项
1. **域名白名单配置**微信小程序后台需配置H5域名、后端接口域名的白名单否则无法访问
2. **通信格式统一**UniApp与H5的通信消息需约定固定格式如`{type: 'xxx', data: 'xxx'}`),避免解析异常;
3. **支付权限**:确保小程序已开通微信支付权限,后端支付接口需正确对接微信支付商户平台;
4. **登录态同步**H5页面刷新时需重新从UniApp获取登录态可通过URL参数或postMessage
5. **兼容性**web-view组件在微信小程序中部分JS API有限制需避免在H5中调用小程序专属API如支付、登录
## 总结
UniApp端核心开发内容可归纳为3个关键点
1. **基础层**配置tabBar、页面路由、微信小程序权限搭建整体框架
2. **核心能力层**开发登录页实现微信授权登录在web-view容器页处理与H5的通信封装支付、退出登录等核心逻辑
3. **保障层**:统一通信格式、配置域名白名单、处理登录态同步,确保混合开发模式稳定运行。

18
docs/Task2.md Normal file
View File

@ -0,0 +1,18 @@
@App.vue里链接一些工具 类似
```
import Request from "@/common/request";
const request = new Request();
export default {
Tool:{
Request: request,
},
}
```
这样之后在其他页面,只需要调用
```
const app = getApp()
app.Tool.Request.get(...)
```
是不是就可以用了?你看一下可行性,可以的话把目前的 Rquest这样试试一下再加一个 接口地址的静态变量工具,避免接口地址在代码里分散管理,需要做集中管理。

316
docs/Task3.md Normal file
View File

@ -0,0 +1,316 @@
<!-- 关于小程序 - 领航专升本志愿通 -->
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#ec5b13",
"background-light": "#f8f6f6",
"background-dark": "#221610",
},
fontFamily: {
"display": ["Public Sans"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
body {
font-family: 'Public Sans', sans-serif;
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-slate-100 font-display">
<div class="relative flex h-auto min-h-screen w-full flex-col overflow-x-hidden">
<!-- Top Navigation Bar -->
<div class="flex items-center bg-background-light dark:bg-background-dark p-4 pb-2 sticky top-0 z-10 border-b border-primary/10">
<div class="text-primary flex size-10 shrink-0 items-center justify-center cursor-pointer">
<span class="material-symbols-outlined" style="font-size: 24px;">arrow_back</span>
</div>
<h2 class="text-slate-900 dark:text-slate-100 text-lg font-bold leading-tight tracking-tight flex-1 text-center pr-10">关于我们</h2>
</div>
<!-- App Branding Section -->
<div class="flex p-8">
<div class="flex w-full flex-col gap-6 items-center">
<div class="flex gap-4 flex-col items-center">
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-2xl min-h-24 w-24 shadow-lg border-4 border-white dark:border-slate-800" data-alt="Modern app logo with minimalist orange icon" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCiv6QxOqmrKmNG9YgnMDyQTPn1Cfdax9jmClnKY4WVInBWPAHl4GU3AA-LYmiChx7SUDRlzAV36Iiqp2Chh9WIRHWuf2ZyPQVx9befY6vh6E85GCXSNwGr-QrSCCX5rKwhUNh7hEaBKA_zmuMhAKiIRI2JbTQqcCHYviXPTbRyOa8x1GKSpCzdl_zmFKDwnSER8v_WDh2-4Xlu-jz27I5jQqU5WAkWTGRMg55_a6f0-JOl0pugk4tpDJicoKI-No218Wo_zkI3y-hM");'>
</div>
<div class="flex flex-col items-center justify-center gap-1">
<p class="text-slate-900 dark:text-slate-100 text-2xl font-bold leading-tight tracking-tight text-center">悦享生活</p>
<p class="text-primary text-sm font-medium leading-normal text-center bg-primary/10 px-3 py-0.5 rounded-full">Version 2.4.5</p>
</div>
</div>
</div>
</div>
<!-- Developer Information -->
<div class="px-4">
<h3 class="text-slate-900 dark:text-slate-100 text-sm font-bold uppercase tracking-wider px-2 pb-3 pt-6 opacity-60">开发者信息</h3>
<div class="flex flex-col bg-white dark:bg-slate-800/50 rounded-xl overflow-hidden shadow-sm">
<div class="flex items-center gap-4 px-4 min-h-[64px] py-3 justify-between border-b border-slate-100 dark:border-slate-700/50">
<div class="flex flex-col justify-center">
<p class="text-slate-900 dark:text-slate-100 text-base font-medium">主体名称</p>
</div>
<div class="shrink-0">
<p class="text-slate-500 dark:text-slate-400 text-sm">某某网络科技有限公司</p>
</div>
</div>
<div class="flex items-center gap-4 px-4 min-h-[64px] py-3 justify-between">
<div class="flex flex-col justify-center">
<p class="text-slate-900 dark:text-slate-100 text-base font-medium">联系邮箱</p>
</div>
<div class="shrink-0">
<p class="text-primary text-sm font-medium">support@example.com</p>
</div>
</div>
</div>
</div>
<!-- Legal Links -->
<div class="px-4">
<h3 class="text-slate-900 dark:text-slate-100 text-sm font-bold uppercase tracking-wider px-2 pb-3 pt-8 opacity-60">法律条款</h3>
<div class="flex flex-col bg-white dark:bg-slate-800/50 rounded-xl overflow-hidden shadow-sm">
<a class="flex items-center gap-4 px-4 min-h-[56px] py-2 justify-between border-b border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors" href="#">
<p class="text-slate-900 dark:text-slate-100 text-base">服务协议</p>
<span class="material-symbols-outlined text-slate-400" style="font-size: 20px;">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 min-h-[56px] py-2 justify-between border-b border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors" href="#">
<p class="text-slate-900 dark:text-slate-100 text-base">隐私政策</p>
<span class="material-symbols-outlined text-slate-400" style="font-size: 20px;">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 min-h-[56px] py-2 justify-between hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors" href="#">
<p class="text-slate-900 dark:text-slate-100 text-base">开源声明</p>
<span class="material-symbols-outlined text-slate-400" style="font-size: 20px;">chevron_right</span>
</a>
</div>
</div>
<!-- Footer / Copyright -->
<div class="mt-auto p-10 flex flex-col items-center gap-2">
<p class="text-slate-400 dark:text-slate-500 text-xs font-medium text-center uppercase tracking-widest">
Copyright © 2024 Tech Co., Ltd.
</p>
<p class="text-slate-400/60 dark:text-slate-500/60 text-[10px] text-center">
All Rights Reserved.
</p>
</div>
</div>
</body></html>
<!-- 个人中心 - 领航专升本志愿通 -->
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>个人中心 - 领航专升本志愿通</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#4a7c59", // Moss green primary
"primary-light": "#f2f5f1",
"background-light": "#fdfaf6", // Warm sand-beige
"background-dark": "#0f1723",
},
fontFamily: {
"display": ["Work Sans", "system-ui", "sans-serif"]
},
borderRadius: {"DEFAULT": "1rem", "lg": "1.5rem", "xl": "2rem", "full": "9999px"},
boxShadow: {
'soft': '0 4px 20px -2px rgba(74, 124, 89, 0.08)',
}
},
},
}</script>
<style>
.water-ripple {
background-image: radial-gradient(circle at 20% 30%, rgba(74, 124, 89, 0.04) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(74, 124, 89, 0.02) 0%, transparent 40%);
}
</style>
<style>
.water-ripple {
background-image: radial-gradient(circle at 20% 30%, rgba(74, 124, 89, 0.04) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(74, 124, 89, 0.02) 0%, transparent 40%);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-slate-100 min-h-screen pb-24 water-ripple">
<!-- Header Section -->
<div class="relative overflow-hidden bg-white dark:bg-slate-900 px-6 pt-12 pb-8 rounded-b-xl shadow-soft">
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl font-bold tracking-tight">个人中心</h1>
<div class="flex gap-4">
<span class="material-symbols-outlined text-slate-500 cursor-pointer">settings</span>
<span class="material-symbols-outlined text-slate-500 cursor-pointer">notifications</span>
</div>
</div>
<div class="flex items-center gap-4">
<div class="relative">
<div class="size-20 rounded-full border-4 dark:border-primary/20 bg-cover bg-center overflow-hidden border-primary/10" data-alt="Professional male avatar with friendly expression" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuD7TIe9GDVO1UZZG9ZjCIbRtiqctAqgq2edMmYepRSbGlgdRUNWmCo1YcjkUT6aDmcTCkp6V987VgM3NmOQ_qeIbJQMVtF-LRpKDI8FUH6V4xOgL0yAkM4ZbXtr8JtwWii-g1beh8iFGen89NHYse5Ma9FENtP-FSj3SxczUvJJPDJmb8LUUINPh8YSBqoECgT5mMB5DOvUUn0sq_Yq_RoSJSIqVud4H0NHlm-SthkF8tS_lLEE2CBN8YKESS65RP_-p2bua_3H-HHL')">
</div>
<div class="absolute bottom-0 right-0 bg-primary text-white p-1 rounded-full border-2 border-white dark:border-slate-900">
<span class="material-symbols-outlined text-[14px] block">verified</span>
</div>
</div>
<div class="flex-1">
<h2 class="text-lg font-bold">领航学子</h2>
<div class="flex items-center gap-2 mt-1">
<span class="px-2 py-0.5 bg-slate-100 dark:bg-slate-800 text-slate-500 text-[10px] font-bold rounded-full uppercase tracking-wider">普通用户</span>
<span class="text-primary text-xs font-medium flex items-center gap-0.5">
<span class="material-symbols-outlined text-[14px]">stars</span>
未激活会员
</span>
</div>
</div>
<button class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-full text-xs font-bold transition-all shadow-md shadow-primary/10">
立即激活
</button>
</div>
</div>
<!-- Stats/Quick Actions -->
<div class="px-4 -mt-4">
<div class="dark:bg-slate-800 rounded-lg p-4 shadow-soft flex justify-around items-center border border-primary/5 bg-white/80">
<div class="text-center">
<p class="text-xl font-bold text-primary">12</p>
<p class="text-[10px] text-slate-500 mt-1">志愿数量</p>
</div>
<div class="w-px h-8 bg-slate-100 dark:bg-slate-700"></div>
<div class="text-center">
<p class="text-xl font-bold text-primary">342</p>
<p class="text-[10px] text-slate-500 mt-1">全省排名</p>
</div>
<div class="w-px h-8 bg-slate-100 dark:bg-slate-700"></div>
<div class="text-center">
<p class="text-xl font-bold text-primary">85%</p>
<p class="text-[10px] text-slate-500 mt-1">录取概率</p>
</div>
</div>
</div>
<!-- Main List Sections -->
<div class="mt-6 px-4 space-y-6">
<!-- Section 1: Core Services -->
<div class="bg-white dark:bg-slate-900 rounded-lg overflow-hidden shadow-soft">
<div class="p-4 border-b border-slate-50 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-400 uppercase tracking-widest">核心服务</h3>
</div>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">assignment</span>
</div>
<span class="flex-1 text-sm font-medium">我的志愿表</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">receipt_long</span>
</div>
<span class="flex-1 text-sm font-medium">我的订单/缴费记录</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">leaderboard</span>
</div>
<span class="flex-1 text-sm font-medium">成绩/排名记录</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">print</span>
</div>
<span class="flex-1 text-sm font-medium">打印设置</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
</div>
<!-- Section 2: Support & Others -->
<div class="bg-white dark:bg-slate-900 rounded-lg overflow-hidden shadow-soft">
<div class="p-4 border-b border-slate-50 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-400 uppercase tracking-widest">更多功能</h3>
</div>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-orange-50 dark:bg-orange-900/10 flex items-center justify-center text-orange-600">
<span class="material-symbols-outlined">card_membership</span>
</div>
<span class="flex-1 text-sm font-medium">激活会员卡</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">handshake</span>
</div>
<span class="flex-1 text-sm font-medium">商务合作</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">menu_book</span>
</div>
<span class="flex-1 text-sm font-medium">填报指南/教程</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">support_agent</span>
</div>
<span class="flex-1 text-sm font-medium">联系老师/客服</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">info</span>
</div>
<span class="flex-1 text-sm font-medium">关于小程序</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
</div>
<!-- Logout & Footer -->
<div class="pt-4 pb-8 text-center">
<button class="w-full bg-white/60 dark:bg-slate-900 text-red-400 py-4 rounded-lg font-bold text-sm shadow-soft hover:bg-red-50/50 transition-colors border border-red-100/30">
退出登录
</button>
<p class="text-[10px] text-slate-400 mt-6 tracking-widest uppercase">Version 2.4.1 (Build 20241021)</p>
</div>
</div>
<!-- Bottom Navigation Bar -->
<nav class="fixed bottom-0 left-0 right-0 bg-white/80 dark:bg-slate-900/80 backdrop-blur-lg border-t border-slate-100 dark:border-slate-800 flex justify-around items-center px-4 pb-6 pt-2 z-50">
<a class="flex flex-col items-center gap-1 text-slate-400 group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">home</span>
<span class="text-[10px] font-bold">首页</span>
</a>
<a class="flex flex-col items-center gap-1 text-slate-400 group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">assignment</span>
<span class="text-[10px] font-bold">志愿表</span>
</a>
<a class="flex flex-col items-center gap-1 text-primary" href="#">
<div class="relative">
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1">person</span>
<div class="absolute -top-1 -right-1 size-2 bg-primary rounded-full border border-white dark:border-slate-900"></div>
</div>
<span class="text-[10px] font-bold">个人中心</span>
</a>
</nav>
</body></html>

109
docs/WeChatMiniLogin.md Normal file
View File

@ -0,0 +1,109 @@
# 微信小程序登录接口文档
## 概述
- 接口用于微信小程序登录,前端通过 `code` 换取 `openid/session_key`,后端自动创建或更新 `t_user``t_platform_user`
- 成功后返回用户ID与平台用户ID供前端后续业务使用。
## 基础信息
- 方法: `POST`
- 路径: `/api/open/wechat/mini/login`
- Content-Type: `application/json`
## 请求头
- `Content-Type: application/json`
- `Authorization`: 不需要(已加入登录白名单)
- 安全校验(当 `security.enable: true` 时必须):
- `X-App-Timestamp`: 毫秒时间戳
- `X-App-Sign`: MD5(`timestamp` + `secret_key`)
## 请求参数
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| code | string | 是 | 微信登录 `wx.login` 返回的 `code` |
| phoneCode | string | 否 | 快速手机号登录返回的 `phoneCode`(优先使用) |
| encryptedData | string | 否 | `getPhoneNumber` 返回的加密数据 |
| iv | string | 否 | `getPhoneNumber` 返回的解密向量 |
| nickname | string | 否 | 用户昵称 |
| avatarUrl | string | 否 | 用户头像URL |
| phone | string | 否 | 手机号(不传或空字符串将写入为 NULL |
| gender | int | 否 | 性别0-未知1-男2-女 |
| platformExtra | object | 否 | 平台扩展字段(如城市、语言等) |
### 请求示例
```json
{
"code": "wx_login_code",
"phoneCode": "8064f569d035bf3a9b4c7fc223bbbc589bf442c0557e0ae51039031d883668f5",
"encryptedData": "EZyKBdrHgQoAjgMDPNGXRGOjsPrB8LHcupwFCztA3IBNvbdkrSsk6iU6FqQsrn5TfpMJeTzHJ2l7lg6e+EBqqDXVgVEgQxkTWlxBS6mwUN4NgRI2FanA0wPFAWMZGmn7jKgEJhu8xKGSihcI111Y/mq+3T6gDzjZvkz32MhngL5/wBEjDQbBdBXfvY6FveyV7CinM0j17wE7pbjNIFrExw==",
"iv": "DMzzMUAx5ke1S8nFItBHSg==",
"nickname": "张三",
"avatarUrl": "https://example.com/avatar.png",
"phone": "13800000000",
"gender": 1,
"platformExtra": {
"city": "Shenzhen",
"language": "zh_CN"
}
}
```
## 响应参数
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| userId | int64 | 用户IDt_user.id |
| platformUserId | int64 | 平台用户IDt_platform_user.id |
| openid | string | 微信 openid |
| unionid | string | 微信 unionid可能为空 |
| sessionKey | string | 微信 session_key |
| phone | string | 手机号(如成功获取) |
| token | string | 登录令牌(用于鉴权) |
| isNewPlatform | bool | 是否新创建平台用户 |
| isNewUser | bool | 是否新创建用户 |
### 成功响应示例
```json
{
"code": 200,
"message": "success",
"data": {
"userId": 10001,
"platformUserId": 20001,
"openid": "oL1oU5gS6Q...",
"unionid": "o6_bmasdasds...",
"sessionKey": "HKq6lZzG2u...",
"phone": "13800000000",
"token": "c6f7f1e4-5a3b-4f4e-9d0b-6b3f7b8c5e3a",
"isNewPlatform": false,
"isNewUser": false
}
}
```
## 错误响应示例
```json
{
"code": 400,
"message": "微信接口错误: 40163 code been used",
"data": null
}
```
## 业务说明
- 首次登录时会创建 `t_user``t_platform_user`
- 之后登录会更新 `platform_session_key`、`last_login_time`,并按需更新 `nickname/avatarUrl/gender/phone`
- `phoneCode` 优先于 `encryptedData+iv` 用于获取手机号;两者都不提供时不更新手机号。
- 返回 `token` 可直接用于鉴权(`Authorization: Bearer <token>`)。
- 若 `wechat.mini_program.app_id/app_secret` 未配置,将返回错误。
## 配置说明
配置文件中新增:
```yaml
wechat:
mini_program:
app_id: "wx_your_app_id"
app_secret: "wx_your_app_secret"
```
## 相关配置与限制
- 若启用安全校验(`security.enable: true`),该接口仍需携带签名头。
- 默认未做登录态发放(如需 JWT/Token可在接口层扩展

438
docs/login_page.md Normal file
View File

@ -0,0 +1,438 @@
<!-- 微信小程序登录 -->
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#07c160", // WeChat Green for the main button
"brand": "#ec5b13", // Theme Primary
"background-light": "#f8f6f6",
"background-dark": "#221610",
},
fontFamily: {
"display": ["Public Sans", "sans-serif"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
body {
font-family: "Public Sans", sans-serif;
}
.wechat-green {
background-color: #07c160;
}
.checkbox-custom:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark min-h-screen flex flex-col">
<div class="flex flex-col w-full max-w-md mx-auto min-h-screen relative px-6">
<div class="flex items-center justify-center pt-16 pb-12">
<div class="flex flex-col items-center gap-4">
<div class="w-24 h-24 rounded-full bg-brand/10 flex items-center justify-center overflow-hidden border-2 border-brand/20">
<div class="w-16 h-16 bg-brand rounded-xl flex items-center justify-center shadow-lg shadow-brand/30">
<span class="material-symbols-outlined text-white text-4xl">polymer</span>
</div>
</div>
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100 tracking-tight">品牌小程序</h1>
<p class="text-slate-500 dark:text-slate-400 text-sm">连接精彩生活,发现无限可能</p>
</div>
</div>
<div class="mt-8 space-y-4">
<button class="w-full h-14 bg-primary hover:bg-opacity-90 text-white rounded-xl flex items-center justify-center gap-3 transition-all active:scale-[0.98] shadow-sm">
<span class="material-symbols-outlined">chat</span>
<span class="text-lg font-semibold">微信手机号快速登录</span>
</button>
<button class="w-full h-14 bg-slate-200 dark:bg-slate-800 text-slate-700 dark:text-slate-200 rounded-xl flex items-center justify-center gap-3 transition-all">
<span class="text-base font-medium">其他方式登录</span>
</button>
</div>
<div class="mt-8 flex justify-center">
<a class="text-brand dark:text-brand font-medium text-sm hover:underline" href="#">账号密码登录</a>
</div>
<div class="mt-auto pb-10">
<div class="flex items-start gap-3">
<div class="pt-1">
<input class="checkbox-custom w-5 h-5 rounded border-slate-300 dark:border-slate-600 text-brand focus:ring-brand bg-white dark:bg-slate-700" id="agreement" type="checkbox"/>
</div>
<label class="text-xs text-slate-500 dark:text-slate-400 leading-relaxed" for="agreement">
我已阅读并同意
<a class="text-brand font-medium" href="#">《用户服务协议》</a>
<a class="text-brand font-medium" href="#">《隐私保护指引》</a>
以及
<a class="text-brand font-medium" href="#">《运营商服务条款》</a>
并授权小程序获取您的手机号用于登录。
</label>
</div>
</div>
<div class="absolute top-0 left-0 w-full h-64 bg-gradient-to-b from-brand/5 to-transparent -z-10"></div>
</div>
<div class="hidden">
<div class="w-full h-full" data-alt="Abstract orange and white flowing artistic pattern"></div>
</div>
</body></html>
<!-- 用户服务协议 -->
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#ec5b13",
"background-light": "#f8f6f6",
"background-dark": "#221610",
},
fontFamily: {
"display": ["Public Sans", "sans-serif"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<title>用户服务协议</title>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-slate-100 min-h-screen">
<!-- Top Navigation Bar -->
<header class="sticky top-0 z-50 bg-background-light/80 dark:bg-background-dark/80 backdrop-blur-md border-b border-primary/10">
<div class="max-w-4xl mx-auto px-4 py-4 flex items-center justify-between">
<button class="flex items-center text-slate-600 dark:text-slate-400 hover:text-primary transition-colors">
<span class="material-symbols-outlined">arrow_back</span>
</button>
<h1 class="text-lg font-bold tracking-tight text-center flex-1">用户服务协议</h1>
<div class="w-6"></div> <!-- Spacer for centering -->
</div>
<!-- Sticky Quick Navigation -->
<nav class="max-w-4xl mx-auto px-4 overflow-x-auto">
<div class="flex border-b border-primary/10">
<a class="flex flex-col items-center justify-center border-b-2 border-primary text-primary pb-3 pt-4 px-4 whitespace-nowrap" href="#preface">
<span class="text-sm font-bold">前言</span>
</a>
<a class="flex flex-col items-center justify-center border-b-2 border-transparent text-slate-500 dark:text-slate-400 pb-3 pt-4 px-4 whitespace-nowrap hover:text-primary transition-colors" href="#services">
<span class="text-sm font-bold">服务内容</span>
</a>
<a class="flex flex-col items-center justify-center border-b-2 border-transparent text-slate-500 dark:text-slate-400 pb-3 pt-4 px-4 whitespace-nowrap hover:text-primary transition-colors" href="#behavior">
<span class="text-sm font-bold">行为规范</span>
</a>
<a class="flex flex-col items-center justify-center border-b-2 border-transparent text-slate-500 dark:text-slate-400 pb-3 pt-4 px-4 whitespace-nowrap hover:text-primary transition-colors" href="#disclaimer">
<span class="text-sm font-bold">免责声明</span>
</a>
</div>
</nav>
</header>
<main class="max-w-4xl mx-auto px-4 py-8 md:py-12">
<div class="prose prose-slate dark:prose-invert max-w-none">
<!-- Preface Section -->
<section class="mb-12 scroll-mt-32" id="preface">
<div class="flex items-center gap-2 mb-4">
<div class="w-1.5 h-6 bg-primary rounded-full"></div>
<h2 class="text-2xl font-bold m-0">一、前言</h2>
</div>
<div class="bg-white dark:bg-white/5 p-6 rounded-xl border border-primary/5 shadow-sm">
<p class="leading-relaxed text-slate-700 dark:text-slate-300 mb-4">
欢迎您使用我们的服务。本协议是用户(以下简称“您”)与公司之间关于使用产品及服务(以下简称“本服务”)所订立的权利义务规范。
</p>
<p class="leading-relaxed text-slate-700 dark:text-slate-300">
在注册或使用本服务前,请您务必审慎阅读并充分理解本协议各条款内容,特别是免除或者限制责任的条款、法律适用和争议解决条款。当您按照注册页面提示填写信息、阅读并同意本协议且完成全部注册程序后,即表示您已充分阅读、理解并接受本协议的全部内容。
</p>
</div>
</section>
<!-- Services Section -->
<section class="mb-12 scroll-mt-32" id="services">
<div class="flex items-center gap-2 mb-4">
<div class="w-1.5 h-6 bg-primary rounded-full"></div>
<h2 class="text-2xl font-bold m-0">二、服务内容</h2>
</div>
<div class="space-y-4">
<p class="leading-relaxed text-slate-700 dark:text-slate-300">
1. 本公司向用户提供包括但不限于信息存储空间、搜索、社区交流、电子商务等技术服务。具体服务内容以届时实际提供的功能为准。
</p>
<p class="leading-relaxed text-slate-700 dark:text-slate-300">
2. 用户理解并同意,本公司有权根据业务发展需要,随时对服务内容进行变更、中止或终止,而无需提前通知用户,但法律法规另有规定的除外。
</p>
<div class="bg-primary/5 p-4 rounded-lg border-l-4 border-primary">
<p class="text-sm text-primary font-medium m-0">
注意:部分增值服务可能涉及收费,我们将会在相应页面进行明确提示,用户可根据需求自主选择。
</p>
</div>
</div>
</section>
<!-- User Behavior Section -->
<section class="mb-12 scroll-mt-32" id="behavior">
<div class="flex items-center gap-2 mb-4">
<div class="w-1.5 h-6 bg-primary rounded-full"></div>
<h2 class="text-2xl font-bold m-0">三、用户行为规范</h2>
</div>
<div class="grid gap-4">
<div class="flex gap-4 p-4 rounded-lg border border-primary/10 bg-white dark:bg-white/5">
<span class="material-symbols-outlined text-primary">security</span>
<div>
<h4 class="font-bold mb-1">账号安全</h4>
<p class="text-sm text-slate-600 dark:text-slate-400 m-0">用户应当妥善保管账号及密码,对其账号下发生的一切活动承担法律责任。不得将账号转让、出借、出租或售卖给他人。</p>
</div>
</div>
<div class="flex gap-4 p-4 rounded-lg border border-primary/10 bg-white dark:bg-white/5">
<span class="material-symbols-outlined text-primary">gavel</span>
<div>
<h4 class="font-bold mb-1">合法使用</h4>
<p class="text-sm text-slate-600 dark:text-slate-400 m-0">用户在使用本服务时,必须遵守法律法规,不得利用本服务制作、复制、发布、传播违法违规信息或从事非法活动。</p>
</div>
</div>
<div class="flex gap-4 p-4 rounded-lg border border-primary/10 bg-white dark:bg-white/5">
<span class="material-symbols-outlined text-primary">copyright</span>
<div>
<h4 class="font-bold mb-1">知识产权</h4>
<p class="text-sm text-slate-600 dark:text-slate-400 m-0">用户在平台上发布的原创内容,其著作权归用户本人所有,但用户授权本公司在全球范围内拥有免费的、永久的、不可撤销的非独占使用许可。</p>
</div>
</div>
</div>
</section>
<!-- Disclaimer Section -->
<section class="mb-12 scroll-mt-32" id="disclaimer">
<div class="flex items-center gap-2 mb-4">
<div class="w-1.5 h-6 bg-primary rounded-full"></div>
<h2 class="text-2xl font-bold m-0">四、免责声明</h2>
</div>
<div class="bg-slate-100 dark:bg-slate-800/50 p-6 rounded-xl border border-dashed border-slate-300 dark:border-slate-700">
<ul class="space-y-3 text-slate-700 dark:text-slate-300 list-none p-0 m-0">
<li class="flex items-start gap-2">
<span class="material-symbols-outlined text-xs mt-1 shrink-0">info</span>
<span>鉴于网络服务的特殊性,用户同意本公司有权随时变更、中断或终止部分或全部的网络服务。</span>
</li>
<li class="flex items-start gap-2">
<span class="material-symbols-outlined text-xs mt-1 shrink-0">info</span>
<span>本公司不保证网络服务一定能满足用户的要求,也不保证网络服务不会中断。</span>
</li>
<li class="flex items-start gap-2">
<span class="material-symbols-outlined text-xs mt-1 shrink-0">info</span>
<span>对于因不可抗力或本公司不能控制的原因造成的网络服务中断或其他缺陷,本公司不承担任何责任,但将尽力减少因此而给用户造成的损失和影响。</span>
</li>
</ul>
</div>
</section>
</div>
<!-- Footer Info -->
<footer class="mt-16 pt-8 border-t border-primary/10 text-center">
<p class="text-sm text-slate-500 dark:text-slate-400">更新日期2023年10月24日</p>
<p class="text-sm text-slate-500 dark:text-slate-400 mb-8">生效日期2023年10月24日</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<button class="px-8 py-3 bg-primary text-white font-bold rounded-lg shadow-lg shadow-primary/20 hover:bg-primary/90 transition-all">
同意并接受
</button>
<button class="px-8 py-3 bg-slate-200 dark:bg-slate-800 text-slate-700 dark:text-slate-300 font-bold rounded-lg hover:bg-slate-300 dark:hover:bg-slate-700 transition-all">
不同意
</button>
</div>
</footer>
</main>
<!-- Scroll to Top Button (Mobile) -->
<div class="fixed bottom-6 right-6 lg:hidden">
<button class="size-12 bg-primary text-white rounded-full shadow-xl flex items-center justify-center" onclick="window.scrollTo({top: 0, behavior: 'smooth'})">
<span class="material-symbols-outlined">expand_less</span>
</button>
</div>
</body></html>
<!-- 隐私保护指引 -->
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#ec5b13",
"background-light": "#f8f6f6",
"background-dark": "#221610",
},
fontFamily: {
"display": ["Public Sans", "sans-serif"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-slate-100 antialiased">
<!-- Top Navigation Bar -->
<nav class="sticky top-0 z-50 bg-background-light/80 dark:bg-background-dark/80 backdrop-blur-md border-b border-primary/10">
<div class="max-w-3xl mx-auto px-4 h-16 flex items-center justify-between">
<button class="p-2 hover:bg-primary/10 rounded-full transition-colors">
<span class="material-symbols-outlined block">arrow_back</span>
</button>
<h1 class="text-lg font-bold tracking-tight">隐私保护指引</h1>
<div class="w-10"></div> <!-- Spacer for centering -->
</div>
</nav>
<main class="max-w-3xl mx-auto px-4 py-8">
<!-- Header Section -->
<header class="mb-8">
<h2 class="text-3xl font-bold mb-4">个人信息保护政策</h2>
<p class="text-slate-600 dark:text-slate-400 leading-relaxed">
我们非常重视您的个人信息保护。本指引将详细说明我们如何收集、使用、共享及保护您的信息,请您务必仔细阅读并理解。
</p>
</header>
<!-- Content Sections -->
<div class="space-y-10">
<!-- Section 1: Collection -->
<section class="bg-white dark:bg-slate-900/50 p-6 rounded-xl border border-primary/5 shadow-sm">
<div class="flex items-center gap-3 mb-4">
<span class="material-symbols-outlined text-primary">data_usage</span>
<h3 class="text-xl font-bold">一、我们如何收集信息</h3>
</div>
<p class="text-slate-600 dark:text-slate-400 mb-4 text-sm">为了向您提供基本服务,我们需要收集以下必要信息:</p>
<div class="grid gap-3">
<div class="flex items-start gap-3 p-3 rounded-lg bg-background-light dark:bg-background-dark">
<span class="material-symbols-outlined text-primary text-sm mt-1">check_circle</span>
<div>
<p class="font-semibold text-sm">账号基础信息</p>
<p class="text-xs text-slate-500">手机号码、头像、昵称等用于身份验证的信息。</p>
</div>
</div>
<div class="flex items-start gap-3 p-3 rounded-lg bg-background-light dark:bg-background-dark">
<span class="material-symbols-outlined text-primary text-sm mt-1">check_circle</span>
<div>
<p class="font-semibold text-sm">设备及网络信息</p>
<p class="text-xs text-slate-500">IP地址、设备型号、操作系统版本、唯一设备标识符。</p>
</div>
</div>
<div class="flex items-start gap-3 p-3 rounded-lg bg-background-light dark:bg-background-dark">
<span class="material-symbols-outlined text-primary text-sm mt-1">check_circle</span>
<div>
<p class="font-semibold text-sm">位置及日志信息</p>
<p class="text-xs text-slate-500">在使用特定功能时,经您授权收集的地理位置信息。</p>
</div>
</div>
</div>
</section>
<!-- Section 2: Usage -->
<section class="p-2">
<div class="flex items-center gap-3 mb-4">
<span class="material-symbols-outlined text-primary">analytics</span>
<h3 class="text-xl font-bold">二、我们如何使用信息</h3>
</div>
<ul class="space-y-4 text-slate-600 dark:text-slate-400">
<li class="flex gap-3">
<span class="text-primary font-bold">01.</span>
<p><strong>服务提供:</strong>利用收集的信息实现软件的基本功能与核心服务。</p>
</li>
<li class="flex gap-3">
<span class="text-primary font-bold">02.</span>
<p><strong>安全保障:</strong>用于身份验证、客户服务、安全防范及诈骗监测。</p>
</li>
<li class="flex gap-3">
<span class="text-primary font-bold">03.</span>
<p><strong>产品改进:</strong>通过数据分析优化产品体验,开发新的功能模块。</p>
</li>
</ul>
</section>
<!-- Section 3: Sharing -->
<section class="bg-primary/5 p-6 rounded-xl border border-primary/10">
<div class="flex items-center gap-3 mb-4">
<span class="material-symbols-outlined text-primary">share</span>
<h3 class="text-xl font-bold">三、我们如何共享信息</h3>
</div>
<p class="text-slate-600 dark:text-slate-400 leading-relaxed mb-4">
我们不会向第三方转让您的个人信息,除非获得您的明确同意或法律法规另有规定。在以下情形中,我们可能会共享信息:
</p>
<div class="flex flex-wrap gap-2">
<span class="px-3 py-1 bg-white dark:bg-background-dark rounded-full text-xs border border-primary/20 text-primary font-medium">第三方SDK集成</span>
<span class="px-3 py-1 bg-white dark:bg-background-dark rounded-full text-xs border border-primary/20 text-primary font-medium">关联公司业务合规</span>
<span class="px-3 py-1 bg-white dark:bg-background-dark rounded-full text-xs border border-primary/20 text-primary font-medium">法律程序要求</span>
</div>
</section>
<!-- Section 4: Protection -->
<section class="p-2">
<div class="flex items-center gap-3 mb-4">
<span class="material-symbols-outlined text-primary">shield</span>
<h3 class="text-xl font-bold">四、我们如何保护您的信息</h3>
</div>
<div class="space-y-4 text-slate-600 dark:text-slate-400 leading-relaxed">
<p>
我们采用行业领先的安全技术如SSL加密传输、匿名化处理来保护您的信息不被泄露、毁损或丢失。
</p>
<p>
我们建立了专门的管理制度、流程和组织以保障信息的安全。严格限制访问信息的人员范围,并要求他们履行保密义务。
</p>
</div>
</section>
<!-- Bottom Consent/Agreement -->
<section class="mt-12 pt-8 border-t border-primary/10">
<div class="flex flex-col gap-4">
<label class="flex items-start gap-3 cursor-pointer group">
<div class="relative flex items-center mt-1">
<input class="peer h-5 w-5 rounded border-primary/30 text-primary focus:ring-primary/50 transition-all cursor-pointer" type="checkbox"/>
</div>
<span class="text-sm text-slate-500 dark:text-slate-400 group-hover:text-primary transition-colors">
我已阅读并同意上述《个人信息保护政策》的所有条款,了解我所享有的权利及其行使方式。
</span>
</label>
<button class="w-full bg-primary hover:bg-primary/90 text-white font-bold py-4 rounded-xl shadow-lg shadow-primary/20 transition-all">
同意并继续
</button>
<button class="w-full py-2 text-sm text-slate-400 hover:text-primary transition-colors">
不同意并退出
</button>
</div>
</section>
</div>
<!-- Footer -->
<footer class="mt-16 text-center text-xs text-slate-400 pb-10">
<p>最近更新日期2023年10月27日</p>
<p class="mt-1">© 2023 隐私保护中心 版权所有</p>
</footer>
</main>
</body></html>

View File

@ -0,0 +1,70 @@
# 手机号+密码登录接口文档
## 概述
用于移动端/前端通过手机号和密码登录,成功后返回 `token` 与用户信息,后续请求携带 `Authorization: Bearer <token>`
## 基础信息
- 方法: `POST`
- 路径: `/api/open/user/login`
- Content-Type: `application/json`
## 请求头
- `Content-Type: application/json`
- `Authorization`: 不需要(已加入登录白名单)
- 安全校验(当 `security.enable: true` 时必须):
- `X-App-Timestamp`: 毫秒时间戳
- `X-App-Sign`: MD5(`timestamp` + `secret_key`)
## 请求参数
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| phone | string | 是 | 手机号 |
| password | string | 是 | 密码 |
### 请求示例
```json
{
"phone": "13800000000",
"password": "your_password"
}
```
## 响应参数
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| token | string | 登录令牌 |
| user | object | 登录用户信息(基础字段) |
### 成功响应示例
```json
{
"code": 200,
"message": "success",
"data": {
"token": "c6f7f1e4-5a3b-4f4e-9d0b-6b3f7b8c5e3a",
"user": {
"id": "10001",
"username": "13800000000",
"realname": "张三",
"avatar": "https://oss-xxx/avatar.png",
"phone": "13800000000",
"email": "",
"token": "c6f7f1e4-5a3b-4f4e-9d0b-6b3f7b8c5e3a"
}
}
}
```
## 错误响应示例
```json
{
"code": 401,
"message": "手机号或密码错误",
"data": null
}
```
## 备注
- 需要在 `t_user` 中预先设置 `password``salt`
- 密码加密方式与系统一致:`common.Encrypt(phone, rawPassword, salt)`。
- token 默认 24 小时过期Redis

76
docs/user_profile.md Normal file
View File

@ -0,0 +1,76 @@
# 登录后获取用户信息接口文档
## 概述
登录成功后,前端携带 `token` 调用该接口获取用户基础信息与平台扩展信息(头像、昵称、性别、地区等)。
## 基础信息
- 方法: `GET`
- 路径: `/api/user/profile`
- Content-Type: `application/json`
## 请求头
- `Authorization: Bearer <token>`
## 请求参数Query
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| platformType | int | 否 | 平台类型,默认 `1`(微信小程序) |
### 请求示例
```
GET /api/user/profile?platformType=1
Authorization: Bearer <token>
```
## 响应参数
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| userId | int64 | 用户IDt_user.id |
| username | string | 用户名 |
| nickname | string | 昵称 |
| avatarUrl | string | 头像URL |
| phone | string | 手机号 |
| gender | int | 性别0-未知1-男2-女 |
| region | string | 地区(优先 `platform_extra.region`,否则拼接国家/省/市) |
| platformType | int | 平台类型 |
| platformOpenid | string | 平台 openid |
| platformUnionid | string | 平台 unionid |
| platformExtra | object | 平台扩展字段(原样返回) |
### 成功响应示例
```json
{
"code": 200,
"message": "success",
"data": {
"userId": 10001,
"username": "wx_20260315121500_ab12cd34",
"nickname": "张三",
"avatarUrl": "https://oss-xxx/avatar.png",
"phone": "13800000000",
"gender": 1,
"region": "中国 广东 深圳",
"platformType": 1,
"platformOpenid": "oL1oU5gS6Q...",
"platformUnionid": "o6_bmasdasds...",
"platformExtra": {
"country": "中国",
"province": "广东",
"city": "深圳"
}
}
}
```
## 错误响应示例
```json
{
"code": 401,
"message": "未登录",
"data": null
}
```
## 备注
- 需要先登录并获取 `token`
- `platformExtra` 为空时,`region` 可能为空。

10
h5/home.html Normal file
View File

@ -0,0 +1,10 @@
<html>
<head>
</head>
<body>
<div>
这里是h5的Home页面
</div>
</body>
</html>

10
h5/index.html Normal file
View File

@ -0,0 +1,10 @@
<html>
<head>
</head>
<body>
<div>
这里是h5的首页
</div>
</body>
</html>

180
h5/my.html Normal file
View File

@ -0,0 +1,180 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>我的</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "PingFang SC", "Microsoft Yahei", sans-serif;
background: #f4f6fb;
color: #1f2d3d;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 32px 20px 40px;
}
h1 {
font-size: 26px;
margin: 0 0 16px;
}
.card {
background: #fff;
border-radius: 12px;
padding: 16px 20px;
box-shadow: 0 10px 24px rgba(15, 33, 66, 0.08);
margin-bottom: 18px;
}
.label {
font-size: 12px;
color: #7a8699;
margin-bottom: 8px;
letter-spacing: 0.08em;
}
.user-info {
font-size: 16px;
line-height: 1.6;
word-break: break-all;
}
.actions {
display: grid;
gap: 12px;
}
button {
height: 44px;
border-radius: 10px;
border: none;
font-size: 15px;
cursor: pointer;
}
.btn-primary {
background: #2d7cf6;
color: #fff;
}
.btn-danger {
background: #ff5c5c;
color: #fff;
}
.hint {
font-size: 12px;
color: #9aa4b2;
margin-top: 8px;
}
</style>
</head>
<body>
<div class="container">
<h1>H5 我的</h1>
<div class="card">
<div class="label">收到的文本</div>
<div id="saved-text" class="user-info">未接收到文本</div>
</div>
<div class="card">
<div class="label">用户信息</div>
<div id="user" class="user-info">未获取用户信息</div>
</div>
<div class="actions">
<button class="btn-primary" id="btn-info">请求用户信息</button>
<button class="btn-danger" id="btn-logout">退出登录</button>
</div>
<div class="hint">该页面通过 postMessage 与 uni-app web-view 通信</div>
</div>
<script>
(function () {
var userEl = document.getElementById('user');
var savedTextEl = document.getElementById('saved-text');
var btnInfo = document.getElementById('btn-info');
var btnLogout = document.getElementById('btn-logout');
function normalizePayload(payload) {
if (payload && payload.data) {
if (Array.isArray(payload.data) && payload.data.length) {
return payload.data[0];
}
if (payload.data.type) {
return payload.data;
}
}
return payload;
}
function setUserInfo(data) {
if (!data) {
userEl.textContent = '未获取用户信息';
return;
}
userEl.textContent =
'昵称:' +
(data.nickName || '-') +
'用户ID' +
(data.userId || '-') +
'Token' +
(data.token || '-');
}
function setSavedText(text) {
savedTextEl.textContent = text ? text : '未接收到文本';
}
function getQueryParam(key) {
var params = new URLSearchParams(window.location.search);
return params.get(key) || '';
}
function postMessageToUniApp(payload) {
if (window.wx && window.wx.miniProgram && window.wx.miniProgram.postMessage) {
window.wx.miniProgram.postMessage({ data: payload });
return;
}
if (window.parent && window.parent.postMessage) {
window.parent.postMessage(payload, '*');
}
}
function handleMessage(payload) {
var data = normalizePayload(payload);
if (!data || !data.type) return;
if (data.type === 'userInfo') {
setUserInfo(data.data);
}
if (data.type === 'savedText') {
setSavedText(data.data && data.data.text ? data.data.text : '');
}
if (data.type === 'logoutSuccess') {
userEl.textContent = '已退出登录';
}
}
btnInfo.addEventListener('click', function () {
postMessageToUniApp({ type: 'getUserInfo' });
});
btnLogout.addEventListener('click', function () {
postMessageToUniApp({ type: 'triggerLogout' });
});
window.addEventListener('message', function (event) {
handleMessage(event.data);
});
if (window.wx && window.wx.miniProgram && window.wx.miniProgram.onMessage) {
window.wx.miniProgram.onMessage(function (message) {
handleMessage(message);
});
}
var initialText = getQueryParam('text');
if (initialText) {
setSavedText(initialText);
}
})();
</script>
</body>
</html>

View File

@ -0,0 +1,47 @@
from playwright.sync_api import sync_playwright
URL = "https://psy5mp.xinliyun.cn/"
ID_CARD = "410323200512060159"
PASSWORD = "000000"
LOGIN_ID_INPUT = "#van-field-1-input"
LOGIN_PWD_INPUT = "#van-field-2-input"
LOGIN_BTN = "#app > div > form > div.form-act > button"
PAPER_ENTRY = "#app > div > div.layout-content > div.van-cell.van-cell--center.van-cell--clickable > div"
PAPER_START = "#app > div > div.paper-desc > div.paper-act.van-safe-area-bottom > button"
FIRST_OPTION = "#app > div > div.paper-quiz-wrapper > div.paper-detail > div.van-radio-group > div > div:nth-child(1)"
def main() -> None:
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto(URL, wait_until="domcontentloaded")
# 登录
page.fill(LOGIN_ID_INPUT, ID_CARD)
page.fill(LOGIN_PWD_INPUT, PASSWORD)
page.click(LOGIN_BTN)
# 进入待测量表列表
page.wait_for_selector(PAPER_ENTRY)
page.click(PAPER_ENTRY)
# 进入测评介绍页并开始
page.wait_for_selector(PAPER_START)
page.click(PAPER_START)
# 连续选择第一个选项 90 次
for _ in range(90):
page.wait_for_selector(FIRST_OPTION)
page.click(FIRST_OPTION)
page.wait_for_timeout(1000)
browser.close()
if __name__ == "__main__":
main()

59
h5/python_test/test.md Normal file
View File

@ -0,0 +1,59 @@
# 使用 Python 实现浏览器自动化提交表单
下面示例使用 Playwright同步 API自动完成登录、进入测评、并连续点击选项 90 次。
## 环境准备
```bash
python -m pip install playwright
playwright install
```
## 示例脚本
```python
from playwright.sync_api import sync_playwright
URL = "https://psy5mp.xinliyun.cn/"
ID_CARD = "410323200512060159"
PASSWORD = "000000"
LOGIN_ID_INPUT = "#van-field-1-input"
LOGIN_PWD_INPUT = "#van-field-2-input"
LOGIN_BTN = "#app > div > form > div.form-act > button"
PAPER_ENTRY = "#app > div > div.layout-content > div.van-cell.van-cell--center.van-cell--clickable > div"
PAPER_START = "#app > div > div.paper-desc > div.paper-act.van-safe-area-bottom > button"
FIRST_OPTION = "#app > div > div.paper-quiz-wrapper > div.paper-detail > div.van-radio-group > div > div:nth-child(1)"
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto(URL, wait_until="domcontentloaded")
# 登录
page.fill(LOGIN_ID_INPUT, ID_CARD)
page.fill(LOGIN_PWD_INPUT, PASSWORD)
page.click(LOGIN_BTN)
# 进入待测量表列表
page.wait_for_selector(PAPER_ENTRY)
page.click(PAPER_ENTRY)
# 进入测评介绍页并开始
page.wait_for_selector(PAPER_START)
page.click(PAPER_START)
# 连续选择第一个选项 90 次
for _ in range(90):
page.wait_for_selector(FIRST_OPTION)
page.click(FIRST_OPTION)
page.wait_for_timeout(1000)
browser.close()
```
## 备注
- 如果页面切换很慢,可在关键步骤之间增加 `page.wait_for_timeout(500)`
- 如果元素选择器变动,优先在浏览器开发者工具中更新选择器。
- 如需无头模式,将 `headless=False` 改为 `True`

200
h5/test.html Normal file
View File

@ -0,0 +1,200 @@
<!-- 个人中心 - 领航专升本志愿通 -->
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>个人中心 - 领航专升本志愿通</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#4a7c59", // Moss green primary
"primary-light": "#f2f5f1",
"background-light": "#fdfaf6", // Warm sand-beige
"background-dark": "#0f1723",
},
fontFamily: {
"display": ["Work Sans", "system-ui", "sans-serif"]
},
borderRadius: {"DEFAULT": "1rem", "lg": "1.5rem", "xl": "2rem", "full": "9999px"},
boxShadow: {
'soft': '0 4px 20px -2px rgba(74, 124, 89, 0.08)',
}
},
},
}</script>
<style>
.water-ripple {
background-image: radial-gradient(circle at 20% 30%, rgba(74, 124, 89, 0.04) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(74, 124, 89, 0.02) 0%, transparent 40%);
}
</style>
<style>
.water-ripple {
background-image: radial-gradient(circle at 20% 30%, rgba(74, 124, 89, 0.04) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(74, 124, 89, 0.02) 0%, transparent 40%);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-slate-100 min-h-screen pb-24 water-ripple">
<!-- Header Section -->
<div class="relative overflow-hidden bg-white dark:bg-slate-900 px-6 pt-12 pb-8 rounded-b-xl shadow-soft">
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl font-bold tracking-tight">个人中心</h1>
<div class="flex gap-4">
<span class="material-symbols-outlined text-slate-500 cursor-pointer">settings</span>
<span class="material-symbols-outlined text-slate-500 cursor-pointer">notifications</span>
</div>
</div>
<div class="flex items-center gap-4">
<div class="relative">
<div class="size-20 rounded-full border-4 dark:border-primary/20 bg-cover bg-center overflow-hidden border-primary/10" data-alt="Professional male avatar with friendly expression" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuD7TIe9GDVO1UZZG9ZjCIbRtiqctAqgq2edMmYepRSbGlgdRUNWmCo1YcjkUT6aDmcTCkp6V987VgM3NmOQ_qeIbJQMVtF-LRpKDI8FUH6V4xOgL0yAkM4ZbXtr8JtwWii-g1beh8iFGen89NHYse5Ma9FENtP-FSj3SxczUvJJPDJmb8LUUINPh8YSBqoECgT5mMB5DOvUUn0sq_Yq_RoSJSIqVud4H0NHlm-SthkF8tS_lLEE2CBN8YKESS65RP_-p2bua_3H-HHL')">
</div>
<div class="absolute bottom-0 right-0 bg-primary text-white p-1 rounded-full border-2 border-white dark:border-slate-900">
<span class="material-symbols-outlined text-[14px] block">verified</span>
</div>
</div>
<div class="flex-1">
<h2 class="text-lg font-bold">领航学子</h2>
<div class="flex items-center gap-2 mt-1">
<span class="px-2 py-0.5 bg-slate-100 dark:bg-slate-800 text-slate-500 text-[10px] font-bold rounded-full uppercase tracking-wider">普通用户</span>
<span class="text-primary text-xs font-medium flex items-center gap-0.5">
<span class="material-symbols-outlined text-[14px]">stars</span>
未激活会员
</span>
</div>
</div>
<button class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-full text-xs font-bold transition-all shadow-md shadow-primary/10">
立即激活
</button>
</div>
</div>
<!-- Stats/Quick Actions -->
<div class="px-4 -mt-4">
<div class="dark:bg-slate-800 rounded-lg p-4 shadow-soft flex justify-around items-center border border-primary/5 bg-white/80">
<div class="text-center">
<p class="text-xl font-bold text-primary">12</p>
<p class="text-[10px] text-slate-500 mt-1">志愿数量</p>
</div>
<div class="w-px h-8 bg-slate-100 dark:bg-slate-700"></div>
<div class="text-center">
<p class="text-xl font-bold text-primary">342</p>
<p class="text-[10px] text-slate-500 mt-1">全省排名</p>
</div>
<div class="w-px h-8 bg-slate-100 dark:bg-slate-700"></div>
<div class="text-center">
<p class="text-xl font-bold text-primary">85%</p>
<p class="text-[10px] text-slate-500 mt-1">录取概率</p>
</div>
</div>
</div>
<!-- Main List Sections -->
<div class="mt-6 px-4 space-y-6">
<!-- Section 1: Core Services -->
<div class="bg-white dark:bg-slate-900 rounded-lg overflow-hidden shadow-soft">
<div class="p-4 border-b border-slate-50 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-400 uppercase tracking-widest">核心服务</h3>
</div>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">assignment</span>
</div>
<span class="flex-1 text-sm font-medium">我的志愿表</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">receipt_long</span>
</div>
<span class="flex-1 text-sm font-medium">我的订单/缴费记录</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">leaderboard</span>
</div>
<span class="flex-1 text-sm font-medium">成绩/排名记录</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-primary-light dark:bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined">print</span>
</div>
<span class="flex-1 text-sm font-medium">打印设置</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
</div>
<!-- Section 2: Support & Others -->
<div class="bg-white dark:bg-slate-900 rounded-lg overflow-hidden shadow-soft">
<div class="p-4 border-b border-slate-50 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-400 uppercase tracking-widest">更多功能</h3>
</div>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-orange-50 dark:bg-orange-900/10 flex items-center justify-center text-orange-600">
<span class="material-symbols-outlined">card_membership</span>
</div>
<span class="flex-1 text-sm font-medium">激活会员卡</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">handshake</span>
</div>
<span class="flex-1 text-sm font-medium">商务合作</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">menu_book</span>
</div>
<span class="flex-1 text-sm font-medium">填报指南/教程</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">support_agent</span>
</div>
<span class="flex-1 text-sm font-medium">联系老师/客服</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
<a class="flex items-center gap-4 px-4 py-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors" href="#">
<div class="size-10 rounded-lg bg-slate-50 dark:bg-slate-800 flex items-center justify-center text-slate-600">
<span class="material-symbols-outlined">info</span>
</div>
<span class="flex-1 text-sm font-medium">关于小程序</span>
<span class="material-symbols-outlined text-slate-300 text-lg">chevron_right</span>
</a>
</div>
<!-- Logout & Footer -->
<div class="pt-4 pb-8 text-center">
<button class="w-full bg-white/60 dark:bg-slate-900 text-red-400 py-4 rounded-lg font-bold text-sm shadow-soft hover:bg-red-50/50 transition-colors border border-red-100/30">
退出登录
</button>
<p class="text-[10px] text-slate-400 mt-6 tracking-widest uppercase">Version 2.4.1 (Build 20241021)</p>
</div>
</div>
<!-- Bottom Navigation Bar -->
<nav class="fixed bottom-0 left-0 right-0 bg-white/80 dark:bg-slate-900/80 backdrop-blur-lg border-t border-slate-100 dark:border-slate-800 flex justify-around items-center px-4 pb-6 pt-2 z-50">
<a class="flex flex-col items-center gap-1 text-slate-400 group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">home</span>
<span class="text-[10px] font-bold">首页</span>
</a>
<a class="flex flex-col items-center gap-1 text-slate-400 group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">assignment</span>
<span class="text-[10px] font-bold">志愿表</span>
</a>
<a class="flex flex-col items-center gap-1 text-primary" href="#">
<div class="relative">
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1">person</span>
<div class="absolute -top-1 -right-1 size-2 bg-primary rounded-full border border-white dark:border-slate-900"></div>
</div>
<span class="text-[10px] font-bold">个人中心</span>
</a>
</nav>
</body></html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title>点击显示小点</title>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
background-color: aqua;
cursor: pointer;
}
.dot {
position: absolute;
width: 8px;
height: 8px;
background-color: red;
border-radius: 50%;
pointer-events: none;
}
</style>
</head>
<body>
</body>
<script type="text/javascript" src="https://doc.dcloud.net.cn/uni.webview.1.5.6.js"></script>
<script>
document.addEventListener('click', function(e) {
const {
clientX,
clientY
} = e
uni.postMessage({
data: {
action: 'click',
clientX,
clientY
}
});
const dot = document.createElement('div');
dot.className = 'dot';
dot.style.left = `${clientX - 4}px`;
dot.style.top = `${clientY - 4}px`;
document.body.appendChild(dot);
setTimeout(() => {
dot.remove();
}, 500); // 显示 500 毫秒后消失
});
</script>
</html>

88
hybrid/html/local.html Normal file
View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>本地网页</title>
<style type="text/css">
.btn {
display: block;
margin: 20px auto;
padding: 5px;
background-color: #007aff;
border: 0;
color: #ffffff;
height: 40px;
width: 200px;
}
.btn-red {
background-color: #dd524d;
}
.btn-yellow {
background-color: #f0ad4e;
}
.desc {
padding: 10px;
color: #999999;
}
</style>
</head>
<body>
<p class="desc">web-view 组件加载本地 html 示例,仅在 App 环境下生效。点击下列按钮,跳转至其它页面。</p>
<div class="btn-list">
<button class="btn" type="button" data-action="navigateTo">navigateTo</button>
<button class="btn" type="button" data-action="redirectTo">redirectTo</button>
<button class="btn" type="button" data-action="navigateBack">navigateBack</button>
<button class="btn" type="button" data-action="reLaunch">reLaunch</button>
<button class="btn" type="button" data-action="switchTab">switchTab</button>
</div>
<p class="desc">网页向应用发送消息。注意:小程序端应用会在此页面后退时接收到消息。</p>
<div class="btn-list">
<button class="btn btn-red" type="button" id="postMessage">postMessage</button>
</div>
<!-- uni 的 SDK -->
<script type="text/javascript" src="https://unpkg.com/@dcloudio/uni-webview-js@0.0.1/index.js"></script>
<script type="text/javascript">
document.addEventListener('UniAppJSBridgeReady', function() {
document.querySelector('.btn-list').addEventListener('click', function(evt) {
var target = evt.target;
if (target.tagName === 'BUTTON') {
var action = target.getAttribute('data-action');
switch (action) {
case 'switchTab':
uni.switchTab({
url: '/pages/tabBar/API/API'
});
break;
case 'reLaunch':
uni.reLaunch({
url: '/pages/tabBar/API/API'
});
break;
case 'navigateBack':
uni.navigateBack({
delta: 1
});
break;
default:
uni[action]({
url: '/pages/component/button/button'
});
break;
}
}
});
document.querySelector("#postMessage").addEventListener('click', function() {
uni.postMessage({
data: {
action: 'message'
}
});
})
});
</script>
</body>
</html>

22
index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<script src="/static/web/image-resize-3.0.1.min.js"></script>
<script src="/static/web/quill-1.3.7.min.js"></script>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

11
jest.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
testTimeout: 20000,
reporters: [
'default'
],
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
moduleFileExtensions: ['js', 'json'],
rootDir: __dirname,
testMatch: ["<rootDir>/pages/**/*test.[jt]s?(x)"],
testPathIgnorePatterns: ['/node_modules/']
}

44
main.js Normal file
View File

@ -0,0 +1,44 @@
import App from './App'
import store from './store'
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
Vue.prototype.$store = store
Vue.prototype.$adpid = "1111111111"
Vue.prototype.$backgroundAudioData = {
playing: false,
playTime: 0,
formatedPlayTime: '00:00:00'
}
App.mpType = 'app'
const app = new Vue({
store,
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
createSSRApp
} from 'vue'
import * as Pinia from 'pinia';
import Vuex from "vuex";
export function createApp() {
const app = createSSRApp(App)
app.use(store)
app.use(Pinia.createPinia());
app.config.globalProperties.$adpid = "1111111111"
app.config.globalProperties.$backgroundAudioData = {
playing: false,
playTime: 0,
formatedPlayTime: '00:00:00'
}
return {
app,
Vuex, // 如果 nvue 使用 vuex 的各种map工具方法时必须 return Vuex
Pinia // 此处必须将 Pinia 返回
}
}
// #endif

191
manifest.json Normal file
View File

@ -0,0 +1,191 @@
{
"name" : "uniapp-demo-new",
"appid" : "",
"description" : "应用描述",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
"usingComponents" : true,
"nvueCompiler" : "uni-app",
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"nvueLaunchMode" : "fast",
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {
"OAuth" : {},
"Payment" : {},
"Push" : {},
"Share" : {},
"Speech" : {},
"VideoPlayer" : {}
},
"distribute" : {
"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.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_MOCK_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.GET_TASKS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<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_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.READ_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
]
},
"ios" : {
"UIBackgroundModes" : [ "audio" ],
"urlschemewhitelist" : [ "baidumap", "iosamap" ]
},
"sdkConfigs" : {
"speech" : {
"ifly" : {}
}
},
"orientation" : [ "portrait-primary" ]
},
"uniStatistics" : {
"enable" : true
}
},
"quickapp" : {},
"quickapp-native" : {
"icon" : "/static/logo.png",
"package" : "com.example.demo",
"features" : [
{
"name" : "system.clipboard"
}
]
},
"quickapp-webview" : {
"icon" : "/static/logo.png",
"package" : "com.example.demo",
"minPlatformVersion" : 1070,
"versionName" : "1.0.0",
"versionCode" : 100
},
"mp-weixin" : {
"appid" : "wxb9cf28f42ffa35e5",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "演示定位能力"
}
},
"uniStatistics" : {
"enable" : true
}
},
"mp-alipay" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-baidu" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
},
"dynamicLib" : {
"editorLib" : {
"provider" : "swan-editor"
}
}
},
"mp-toutiao" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-jd" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"h5" : {
"template" : "template.h5.html",
"router" : {
"mode" : "history",
"base" : ""
},
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : "TKUBZ-D24AF-GJ4JY-JDVM2-IBYKK-KEBCU"
}
}
},
"async" : {
"timeout" : 20000
},
"uniStatistics" : {
"enable" : true
}
},
"vueVersion" : "3",
"mp-kuaishou" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-lark" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-qq" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-huawei" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-union" : {
"uniStatistics" : {
"enable" : true
}
},
"uniStatistics" : {
"version" : "2",
"enable" : true
}
}

133
package.json Normal file
View File

@ -0,0 +1,133 @@
{
"id": "hello-uniapp",
"name": "hello-uniapp",
"displayName": "hello-uniapp 示例工程",
"version": "3.4.9",
"description": "uni-app 框架示例一套代码同时发行到iOS、Android、H5、小程序等多个平台请使用手机扫码快速体验 uni-app 的强大功能",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": "https://github.com/dcloudio/hello-uniapp.git",
"keywords": [
"hello-uniapp",
"uni-app",
"uni-ui",
"示例工程"
],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/dcloudio/hello-uniapp/issues"
},
"homepage": "https://github.com/dcloudio/hello-uniapp#readme",
"dependencies": {},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "uniapp-template-project",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "x"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": "√",
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "√",
"jd": "√",
"harmony": "√",
"qq": "√",
"lark": "√"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
},
"uni-app": {
"scripts": {
"mp-dingtalk": {
"title": "钉钉小程序",
"env": {
"UNI_PLATFORM": "mp-alipay"
},
"define": {
"MP-DINGTALK": true
}
},
"hello-uniapp-demo": {
"title": "hello-uniapp 演示网站",
"env": {
"UNI_PLATFORM": "h5"
},
"define": {
"H5-DEMO": true
}
}
}
},
"engines": {
"HBuilderX": "^3.1.0",
"uni-app": "^4.03",
"uni-app-x": ""
}
}

1198
pages.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-btn-v">
<button class="target" type="default" @tap="actionSheetTap">弹出action sheet</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'action-sheet',
buttonRect: {}
}
},
// #ifdef H5
onReady() {
this.getNodeInfo()
window.addEventListener('resize', this.getNodeInfo)
},
beforeDestroy() {
window.removeEventListener('resize', this.getNodeInfo)
},
// #endif
methods: {
actionSheetTap() {
const that = this
uni.showActionSheet({
title: '标题',
itemList: ['item1', 'item2', 'item3', 'item4'],
popover: {
// 104: navbar + topwindow fix createSelectorQuery pc top bug
top: that.buttonRect.top + 104 + that.buttonRect.height,
left: that.buttonRect.left + that.buttonRect.width / 2
},
success: (e) => {
console.log(e.tapIndex);
uni.showToast({
title: "点击了第" + e.tapIndex + "个选项",
icon: "none"
})
}
})
},
// #ifdef H5
getNodeInfo() {
uni.createSelectorQuery().select('.target').boundingClientRect().exec((ret) => {
const rect = ret[0]
if (rect) {
this.buttonRect = rect
}
});
}
// #endif
}
}
</script>

View File

@ -0,0 +1,102 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-common-mt">
<view class="uni-list">
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">名称</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" placeholder="请输入联系人名称" name="name" :value="name" @input="nameChange"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">手机号</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" placeholder="请输入联系人手机号" name="phone" :value="phone" @input="phoneChange"/>
</view>
</view>
</view>
<view class="uni-padding-wrap">
<view class="uni-btn-v">
<button type="primary" class="btn-setstorage" @tap="add">添加联系人</button>
</view>
</view>
</view>
</view>
</template>
<script>
// #ifdef APP-PLUS
import permision from "@/common/permission.js"
// #endif
export default {
data() {
return {
title: 'addPhoneContact',
name: '',
phone: ''
}
},
methods: {
nameChange: function(e) {
this.name = e.detail.value
},
phoneChange: function(e) {
this.phone = e.detail.value
},
async add() {
// #ifdef APP-PLUS
let status = await this.checkPermission();
if (status !== 1) {
return;
}
// #endif
uni.addPhoneContact({
firstName: this.name,
mobilePhoneNumber: this.phone,
success: function() {
uni.showModal({
content: '已成功添加联系人!',
showCancel: false
})
},
fail: function() {
uni.showModal({
content: '添加联系人失败!',
showCancel: false
})
}
});
}
// #ifdef APP-PLUS
,
async checkPermission() {
let status = permision.isIOS ? await permision.requestIOS('contact') :
await permision.requestAndroid('android.permission.WRITE_CONTACTS');
if (status === null || status === 1) {
status = 1;
} else {
uni.showModal({
content: "需要联系人权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
return status;
}
// #endif
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,125 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="animation-element-wrapper">
<view class="animation-element" :animation="animationData"></view>
</view>
<scroll-view class="animation-buttons" scroll-y="true">
<button class="animation-button" @tap="rotate">旋转</button>
<button class="animation-button" @tap="scale">缩放</button>
<button class="animation-button" @tap="translate">移动</button>
<button class="animation-button" @tap="skew">倾斜</button>
<button class="animation-button" @tap="rotateAndScale">旋转并缩放</button>
<button class="animation-button" @tap="rotateThenScale">旋转后缩放</button>
<button class="animation-button" @tap="all">同时展示全部</button>
<button class="animation-button" @tap="allInQueue">顺序展示全部</button>
<button class="animation-button animation-button-reset" @tap="reset">还原</button>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'createAnimation',
animationData: ''
}
},
onUnload(){
this.animationData = ''
},
onLoad() {
this.animation = uni.createAnimation()
},
methods: {
rotate: function () {
this.animation.rotate(Math.random() * 720 - 360).step()
this.animationData = this.animation.export()
},
scale: function () {
this.animation.scale(Math.random() * 2).step()
this.animationData = this.animation.export()
},
translate: function () {
this.animation.translate(Math.random() * 100 - 50, Math.random() * 100 - 50).step()
this.animationData = this.animation.export()
},
skew: function () {
this.animation.skew(Math.random() * 90, Math.random() * 90).step()
this.animationData = this.animation.export()
},
rotateAndScale: function () {
this.animation.rotate(Math.random() * 720 - 360)
.scale(Math.random() * 2)
.step()
this.animationData = this.animation.export()
},
rotateThenScale: function () {
this.animation.rotate(Math.random() * 720 - 360).step()
.scale(Math.random() * 2).step()
this.animationData = this.animation.export()
},
all: function () {
this.animation.rotate(Math.random() * 720 - 360)
.scale(Math.random() * 2)
.translate(Math.random() * 100 - 50, Math.random() * 100 - 50)
.skew(Math.random() * 90, Math.random() * 90)
.step()
this.animationData = this.animation.export()
},
allInQueue: function () {
this.animation.rotate(Math.random() * 720 - 360).step()
.scale(Math.random() * 2).step()
.translate(Math.random() * 100 - 50, Math.random() * 100 - 50).step()
.skew(Math.random() * 90, Math.random() * 90).step()
this.animationData = this.animation.export()
},
reset: function () {
this.animation.rotate(0, 0)
.scale(1)
.translate(0, 0)
.skew(0, 0)
.step({
duration: 0
})
this.animationData = this.animation.export()
}
}
}
</script>
<style>
.animation-element-wrapper {
display: flex;
width: 100%;
padding-top: 150rpx;
padding-bottom: 150rpx;
justify-content: center;
overflow: hidden;
background-color: #ffffff;
}
.animation-element {
width: 200rpx;
height: 200rpx;
background-color: #1AAD19;
}
.animation-buttons {
padding:30rpx 0;
width: 100%;
/* height: 360rpx; */
}
.animation-button {
float: left;
width: 44%;
margin: 15rpx 3%;
}
.animation-button-reset {
width: 94%;
}
</style>

View File

@ -0,0 +1,163 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-center">
<text class="time-big">{{formatedPlayTime}}</text>
</view>
<view class="uni-common-mt">
<slider class="slider" min="0" max="21" step="1" :value="playTime" @change="seek"></slider>
</view>
<view class="play-time">
<text>00:00</text>
<text>00:21</text>
</view>
<view class="uni-hello-text">注意离开当前页面后背景音乐将保持播放但退出uni-app将停止</view>
<view class="page-body-buttons">
<block v-if="playing">
<view class="page-body-button" @tap="stop">
<image src="/static/stop.png"></image>
</view>
<view class="page-body-button" @tap="pause">
<image src="/static/pause.png"></image>
</view>
</block>
<block v-if="!playing">
<view class="page-body-button"></view>
<view class="page-body-button" @tap="play">
<image src="/static/play.png"></image>
</view>
</block>
<view class="page-body-button"></view>
</view>
</view>
</view>
</template>
<script>
import * as util from '../../../common/util.js';
export default {
data() {
return {
title: 'backgroundAudio',
bgAudioMannager: null,
dataUrl: 'https://web-ext-storage.dcloud.net.cn/uni-app/ForElise.mp3',
playing: false,
playTime: 0,
formatedPlayTime: '00:00:00'
}
},
onLoad: function () {
this.playing = this.$backgroundAudioData.playing;
this.playTime = this.$backgroundAudioData.playTime;
this.formatedPlayTime = this.$backgroundAudioData.formatedPlayTime;
let bgAudioMannager = uni.getBackgroundAudioManager();
if(!bgAudioMannager.title){
bgAudioMannager.title = '致爱丽丝';
}
if(!bgAudioMannager.singer) {
bgAudioMannager.singer = '暂无';
}
if(!bgAudioMannager.coverImgUrl){
bgAudioMannager.coverImgUrl = 'https://web-assets.dcloud.net.cn/unidoc/zh/Alice.jpeg';
}
bgAudioMannager.onPlay(() => {
console.log("开始播放");
this.$backgroundAudioData.playing = this.playing = true;
})
bgAudioMannager.onPause(() => {
console.log("暂停播放");
this.$backgroundAudioData.playing = this.playing = false;
})
bgAudioMannager.onEnded(() => {
this.playing = false;
this.$backgroundAudioData.playing = false;
this.$backgroundAudioData.playTime = this.playTime = 0;
this.$backgroundAudioData.formatedPlayTime = this.formatedPlayTime = util.formatTime(0);
})
bgAudioMannager.onTimeUpdate((e) => {
if (Math.floor(bgAudioMannager.currentTime) > Math.floor(this.playTime)) {
this.$backgroundAudioData.formatedPlayTime = this.formatedPlayTime = util.formatTime(Math.floor(bgAudioMannager.currentTime));
}
this.$backgroundAudioData.playTime = this.playTime = bgAudioMannager.currentTime;
})
this.bgAudioMannager = bgAudioMannager;
},
methods: {
play: function (res) {
if (!this.bgAudioMannager.src) {
this.bgAudioMannager.startTime = this.playTime;
this.bgAudioMannager.src = this.dataUrl;
} else {
this.bgAudioMannager.seek(this.playTime);
this.bgAudioMannager.play();
}
},
seek: function (e) {
this.bgAudioMannager.seek(e.detail.value);
},
pause: function () {
this.bgAudioMannager.pause();
},
stop: function () {
this.bgAudioMannager.stop();
this.$backgroundAudioData.playing = this.playing = false;
this.$backgroundAudioData.playTime = this.playTime = 0;
this.$backgroundAudioData.formatedPlayTime = this.formatedPlayTime = util.formatTime(0);
}
}
}
</script>
<style>
image {
width: 150rpx;
height: 150rpx;
}
.page-body-text {
padding: 0 30rpx;
}
.page-body-wrapper {
margin-top: 0;
}
.page-body-info {
padding-bottom: 50rpx;
}
.time-big {
font-size: 60rpx;
margin: 20rpx;
}
.slider {
width:630rpx;
}
.play-time {
font-size: 28rpx;
width:100%;
padding: 20rpx 0;
display: flex;
justify-content: space-between;
box-sizing: border-box;
}
.page-body-buttons {
display: flex;
justify-content: space-around;
margin-top: 100rpx;
}
.page-body-button {
width: 250rpx;
text-align: center;
}
</style>

View File

@ -0,0 +1,723 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view>
本蓝牙协议只支持低功耗蓝牙协议ble如果想连接非ble蓝牙设备请在社区搜索 Native.js 蓝牙
</view>
<view class="uni-btn-v">
<button type="primary" :disabled="disabled[0]" @click="openBluetoothAdapter">
初始化蓝牙模块
</button>
<view v-if="!adapterState.available">
{{ '蓝牙适配器不可用,请初始化蓝牙模块' }}
</view>
<button
type="primary"
:loading="searchLoad"
:disabled="disabled[1]"
@click="startBluetoothDevicesDiscovery"
>
开始搜索蓝牙设备
</button>
<button
type="primary"
:disabled="disabled[2]"
@click="stopBluetoothDevicesDiscovery(false)"
>
停止搜索蓝牙设备
</button>
<button
type="primary"
:loading="newDeviceLoad"
:disabled="disabled[3]"
@click="queryDevices"
>
选择设备
</button>
<view v-if="equipment.length > 0">
{{
(connected ? '已连接设备' : '已选择设备') +
' : ' +
equipment[0].name +
' (' +
equipment[0].deviceId +
')'
}}
</view>
<button type="primary" :disabled="disabled[4]" @click="createBLEConnection">
连接蓝牙设备
</button>
<button type="primary" :disabled="disabled[5]" @click="getBLEDeviceServices">
选择设备服务
</button>
<view v-if="servicesData.length > 0">已选服务uuid{{ servicesData[0].uuid }}</view>
<button type="primary" :disabled="disabled[6]" @click="getBLEDeviceCharacteristics">
获取服务的特征值
</button>
<view v-if="characteristicsData.length > 0">
<view class="uni-list_name">uuid:{{ characteristicsData[0].uuid }}</view>
<view class="uni-list_item">
是否支持 read 操作:{{ characteristicsData[0].properties.read }}
</view>
<view class="uni-list_item">
是否支持 write 操作:{{ characteristicsData[0].properties.write }}
</view>
<view class="uni-list_item">
是否支持 notify 操作:{{ characteristicsData[0].properties.notify }}
</view>
<view class="uni-list_item">
是否支持 indicate 操作:{{ characteristicsData[0].properties.indicate }}
</view>
</view>
<!-- <button type="primary" :disabled="disabled[7]" @click="readBLECharacteristicValue">
读取特征值数据
</button>
<view v-if="valueChangeData.serviceId">
<view class="list-name">
特征值最新的值:{{ valueChangeData.value || '还没有最新值' }}
</view>
</view> -->
<!-- <button type="primary" :disabled="disabled[8]" @click="w">写入特征值数据</button> -->
<button type="primary" :disabled="disabled[9]" @click="closeBLEConnection">
断开蓝牙设备
</button>
<button type="primary" :disabled="disabled[10]" @click="closeBluetoothAdapter">
关闭蓝牙模块
</button>
</view>
</view>
<!-- 遮罩 -->
<view v-if="maskShow" class="uni-mask" @touchmove.stop.prevent="moveHandle" @click="maskclose">
<scroll-view class="uni-scroll_box" scroll-y @touchmove.stop.prevent="moveHandle" @click.stop="moveHandle">
<view class="uni-title">
已经发现{{ list.length }}{{ showMaskType === 'device' ? '台设备' : '个服务' }}:
</view>
<view
class="uni-list-box"
v-for="(item, index) in list"
:key="index"
@click="tapQuery(item)"
>
<view v-if="showMaskType === 'device'">
<view class="uni-list_name">{{ item.name || item.localName }}</view>
<view class="uni-list_item">信号强度:{{ item.RSSI }}dBm</view>
<view class="uni-list_item">UUID:{{ item.deviceId }}</view>
<!-- <view class="list-item" v-if="showMaskType === 'device'">
Service数量:{{ item.advertisServiceUUIDs.length }}
</view> -->
</view>
<view v-if="showMaskType === 'service'">
<view class="uni-list_item" style="line-height:2.2;">
UUID: {{ item.uuid }}
<text v-if="showMaskType === 'service'">
{{ item.isPrimary ? '(主服务)' : '' }}
</text>
</view>
</view>
<view v-if="showMaskType === 'characteristics'">
<view class="uni-list_name">uuid:{{ item.uuid }}</view>
<view class="uni-list_item">是否支持 read 操作:{{ item.properties.read }}</view>
<view class="uni-list_item">
是否支持 write 操作:{{ item.properties.write }}
</view>
<view class="uni-list_item">
是否支持 notify 操作:{{ item.properties.notify }}
</view>
<view class="uni-list_item">
是否支持 indicate 操作:{{ item.properties.indicate }}
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'bluetooth',
disabled: [false, true, true, true, true, true, true, true, true, true, true],
newDeviceLoad: false,
searchLoad: false,
maskShow: false,
equipment: [],
adapterState: {
discovering: false,
available: false
},
connected: false,
showMaskType: 'device',
servicesData: [],
characteristicsData: [],
valueChangeData: {},
isStop:true ,
list: []
};
},
onLoad() {
this.onBLEConnectionStateChange();
},
methods: {
moveHandle() {},
/**
* 关闭遮罩
*/
maskclose(){
this.maskShow = false;
},
/**
* 选择设备
*/
queryDevices() {
// this.newDeviceLoad = true;
this.showMaskType = 'device';
this.maskShow = true;
},
tapQuery(item) {
if (this.showMaskType === 'device') {
this.$set(this.disabled, 4, false);
if (this.equipment.length > 0) {
this.equipment[0] = item;
} else {
this.equipment.push(item);
}
this.newDeviceLoad = false;
}
if (this.showMaskType === 'service') {
this.$set(this.disabled, 6, false);
if (this.servicesData.length > 0) {
this.servicesData[0] = item;
} else {
this.servicesData.push(item);
}
}
if (this.showMaskType === 'characteristics') {
this.$set(this.disabled, 7, false);
if (this.characteristicsData.length > 0) {
this.characteristicsData[0] = item;
} else {
this.characteristicsData.push(item);
}
}
this.maskShow = false;
},
/**
* 初始化蓝牙设备
*/
openBluetoothAdapter() {
uni.openBluetoothAdapter({
success: e => {
console.log('初始化蓝牙成功:' + e.errMsg);
console.log(JSON.stringify(e));
this.isStop = false ;
this.$set(this.disabled, 0, true);
this.$set(this.disabled, 1, false);
this.$set(this.disabled, 10, false);
this.getBluetoothAdapterState();
},
fail: e => {
console.log(e)
console.log('初始化蓝牙失败,错误码:' + (e.errCode || e.errMsg));
if (e.errCode !== 0) {
initTypes(e.errCode,e.errMsg);
}
}
});
},
/**
* 开始搜索蓝牙设备
*/
startBluetoothDevicesDiscovery() {
uni.startBluetoothDevicesDiscovery({
success: e => {
console.log('开始搜索蓝牙设备:' + e.errMsg);
this.searchLoad = true;
this.$set(this.disabled, 1, true);
this.$set(this.disabled, 2, false);
this.$set(this.disabled, 3, false);
this.onBluetoothDeviceFound();
},
fail: e => {
console.log('搜索蓝牙设备失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 停止搜索蓝牙设备
*/
stopBluetoothDevicesDiscovery(types) {
uni.stopBluetoothDevicesDiscovery({
success: e => {
console.log('停止搜索蓝牙设备:' + e.errMsg);
if (types) {
this.$set(this.disabled, 1, true);
} else {
this.$set(this.disabled, 1, false);
}
this.$set(this.disabled, 2, true);
// this.$set(this.disabled, 3, true);
this.searchLoad = false;
},
fail: e => {
console.log('停止搜索蓝牙设备失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 发现外围设备
*/
onBluetoothDeviceFound() {
uni.onBluetoothDeviceFound(devices => {
console.log('开始监听寻找到新设备的事件');
// this.$set(this.disabled, 3, false);
this.getBluetoothDevices();
});
},
/**
* 获取在蓝牙模块生效期间所有已发现的蓝牙设备包括已经和本机处于连接状态的设备
*/
getBluetoothDevices() {
uni.getBluetoothDevices({
success: res => {
this.newDeviceLoad = false;
console.log('获取蓝牙设备成功:' + res.errMsg);
// console.log(JSON.stringify(res))
this.list = res.devices;
},
fail: e => {
console.log('获取蓝牙设备错误,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 获取本机蓝牙适配器状态
*/
getBluetoothAdapterState() {
console.log('--->');
uni.getBluetoothAdapterState({
success: res => {
console.log(JSON.stringify(res));
this.adapterState = res;
},
fail: e => {
console.log('获取本机蓝牙适配器状态失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 连接低功耗蓝牙
*/
createBLEConnection() {
let deviceId = this.equipment[0].deviceId;
uni.showToast({
title: '连接蓝牙...',
icon: 'loading',
duration: 99999
});
uni.createBLEConnection({
// deviceId createBLEConnection
deviceId,
success: res => {
console.log(res);
console.log('连接蓝牙成功:' + res.errMsg);
//
this.stopBluetoothDevicesDiscovery(true);
uni.hideToast();
uni.showToast({
title: '连接成功',
icon: 'success',
duration: 2000
});
this.$set(this.disabled, 3, true);
this.$set(this.disabled, 4, true);
this.$set(this.disabled, 5, false);
this.$set(this.disabled, 9, false);
this.connected = true;
},
fail: e => {
console.log('连接低功耗蓝牙失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 断开与低功耗蓝牙设备的连接
*/
closeBLEConnection() {
let deviceId = this.equipment[0].deviceId;
uni.closeBLEConnection({
deviceId,
success: res => {
console.log(res);
console.log('断开低功耗蓝牙成功:' + res.errMsg);
this.$set(this.disabled, 1, false);
this.$set(this.disabled, 3, true);
this.$set(this.disabled, 4, true);
this.$set(this.disabled, 5, true);
this.$set(this.disabled, 6, true);
this.$set(this.disabled, 7, true);
this.$set(this.disabled, 8, true);
this.$set(this.disabled, 9, true);
this.equipment = [];
this.servicesData = [];
this.characteristicsData = [];
},
fail: e => {
console.log('断开低功耗蓝牙成功,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 获取所有服务
*/
getBLEDeviceServices() {
let deviceId = this.equipment[0].deviceId;
console.log('获取所有服务的 uuid:' + deviceId);
uni.getBLEDeviceServices({
// deviceId createBLEConnection
deviceId,
success: res => {
console.log(JSON.stringify(res.services));
console.log('获取设备服务成功:' + res.errMsg);
this.$set(this.disabled, 7, true);
this.$set(this.disabled, 8, true);
this.showMaskType = 'service';
this.list = res.services;
this.characteristicsData = [];
if (this.list.length <= 0) {
toast('获取服务失败,请重试!');
return;
}
this.maskShow = true;
},
fail: e => {
console.log('获取设备服务失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 获取某个服务下的所有特征值
*/
getBLEDeviceCharacteristics() {
let deviceId = this.equipment[0].deviceId;
let serviceId = this.servicesData[0].uuid;
console.log(deviceId);
console.log(serviceId);
uni.getBLEDeviceCharacteristics({
// deviceId createBLEConnection
deviceId,
// serviceId getBLEDeviceServices
serviceId,
success: res => {
console.log(JSON.stringify(res));
console.log('获取特征值成功:' + res.errMsg);
this.$set(this.disabled, 7, true);
this.valueChangeData = {};
this.showMaskType = 'characteristics';
this.list = res.characteristics;
if (this.list.length <= 0) {
toast('获取特征值失败,请重试!');
return;
}
this.maskShow = true;
},
fail: e => {
console.log('获取特征值失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 监听低功耗蓝牙连接状态的改变事件包括开发者主动连接或断开连接设备丢失连接异常断开等等
*/
onBLEConnectionStateChange() {
uni.onBLEConnectionStateChange(res => {
//
console.log(`蓝牙连接状态 -------------------------->`);
console.log(JSON.stringify(res));
if (!res.connected) {
if(this.isStop) return ;
console.log('断开低功耗蓝牙成功:');
this.$set(this.disabled, 1, false);
this.$set(this.disabled, 3, true);
this.$set(this.disabled, 4, true);
this.$set(this.disabled, 5, true);
this.$set(this.disabled, 6, true);
this.$set(this.disabled, 7, true);
this.$set(this.disabled, 8, true);
this.$set(this.disabled, 9, true);
this.searchLoad = false;
this.equipment = [];
this.servicesData = [];
this.characteristicsData = [];
this.valueChangeData = {};
toast('已经断开当前蓝牙连接');
}
});
},
/**
* 读取低功耗蓝牙设备的特征值的二进制数据值注意必须设备的特征值支持 read 才可以成功调用
*/
readBLECharacteristicValue() {
let deviceId = this.equipment[0].deviceId;
let serviceId = this.servicesData[0].uuid;
let characteristicId = this.characteristicsData[0].uuid;
console.log(deviceId);
console.log(serviceId);
console.log(characteristicId);
uni.readBLECharacteristicValue({
// deviceId createBLEConnection
deviceId,
// serviceId getBLEDeviceServices
serviceId,
// characteristicId getBLEDeviceCharacteristics
characteristicId,
success: res => {
console.log('读取设备数据值成功');
console.log(JSON.stringify(res));
this.notifyBLECharacteristicValueChange();
},
fail(e) {
console.log('读取设备数据值失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
this.onBLECharacteristicValueChange();
},
/**
* 监听低功耗蓝牙设备的特征值变化事件必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification
*/
onBLECharacteristicValueChange() {
//
uni.onBLECharacteristicValueChange(characteristic => {
console.log('监听低功耗蓝牙设备的特征值变化事件成功');
console.log(JSON.stringify(characteristic));
this.valueChangeData = characteristic;
});
},
/**
* 订阅操作成功后需要设备主动更新特征值的 value才会触发 uni.onBLECharacteristicValueChange 回调
*/
notifyBLECharacteristicValueChange() {
let deviceId = this.equipment[0].deviceId;
let serviceId = this.servicesData[0].uuid;
let characteristicId = this.characteristicsData[0].uuid;
let notify = this.characteristicsData[0].properties.notify;
console.log(deviceId);
console.log(serviceId);
console.log(characteristicId);
console.log(notify);
uni.notifyBLECharacteristicValueChange({
state: true, // notify
// deviceId createBLEConnection
deviceId,
// serviceId getBLEDeviceServices
serviceId,
// characteristicId getBLEDeviceCharacteristics
characteristicId,
success(res) {
console.log('notifyBLECharacteristicValueChange success:' + res.errMsg);
console.log(JSON.stringify(res));
}
});
},
/**
* 断开蓝牙模块
*/
closeBluetoothAdapter(OBJECT) {
uni.closeBluetoothAdapter({
success: res => {
console.log('断开蓝牙模块成功');
this.isStop = true ;
this.$set(this.disabled, 0, false);
this.$set(this.disabled, 1, true);
this.$set(this.disabled, 2, true);
this.$set(this.disabled, 3, true);
this.$set(this.disabled, 4, true);
this.$set(this.disabled, 5, true);
this.$set(this.disabled, 6, true);
this.$set(this.disabled, 7, true);
this.$set(this.disabled, 8, true);
this.$set(this.disabled, 9, true);
this.$set(this.disabled, 10, true);
this.equipment = [];
this.servicesData = [];
this.characteristicsData = [];
this.valueChangeData = {};
this.adapterState = [];
this.searchLoad =false;
toast('断开蓝牙模块');
}
});
}
}
};
/**
* 判断初始化蓝牙状态
*/
function initTypes(code, errMsg) {
switch (code) {
case 10000:
toast('未初始化蓝牙适配器');
break;
case 10001:
toast('未检测到蓝牙,请打开蓝牙重试!');
break;
case 10002:
toast('没有找到指定设备');
break;
case 10003:
toast('连接失败');
break;
case 10004:
toast('没有找到指定服务');
break;
case 10005:
toast('没有找到指定特征值');
break;
case 10006:
toast('当前连接已断开');
break;
case 10007:
toast('当前特征值不支持此操作');
break;
case 10008:
toast('其余所有系统上报的异常');
break;
case 10009:
toast('Android 系统特有,系统版本低于 4.3 不支持 BLE');
break;
default:
toast(errMsg);
}
}
/**
* 弹出框封装
*/
function toast(content, showCancel = false) {
uni.showModal({
title: '提示',
content,
showCancel
});
}
</script>
<style>
.uni-title {
/* width: 100%; */
/* height: 80rpx; */
text-align: center;
}
.uni-mask {
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
align-items: center;
width: 100%;
background: rgba(0, 0, 0, 0.6);
padding: 0 30rpx;
box-sizing: border-box;
}
.uni-scroll_box {
height: 70%;
background: #fff;
border-radius: 20rpx;
}
.uni-list-box {
margin: 0 20rpx;
padding: 15rpx 0;
border-bottom: 1px #f5f5f5 solid;
box-sizing: border-box;
}
.uni-list:last-child {
border: none;
}
.uni-list_name {
font-size: 30rpx;
color: #333;
}
.uni-list_item {
font-size: 24rpx;
color: #555;
line-height: 1.5;
}
.uni-success_box {
position: absolute;
left: 0;
bottom: 0;
min-height: 100rpx;
width: 100%;
background: #fff;
box-sizing: border-box;
border-top: 1px #eee solid;
}
.uni-success_sub {
/* width: 100%%; */
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
}
.uni-close_button {
padding: 0 20rpx;
height: 60rpx;
line-height: 60rpx;
background: #ce3c39;
color: #ffffff;
border-radius: 10rpx;
}
.uni-success_content {
height: 600rpx;
margin: 30rpx;
margin-top: 0;
border: 1px #eee solid;
padding: 30rpx;
}
.uni-content_list {
padding-bottom: 10rpx;
border-bottom: 1px #f5f5f5 solid;
}
.uni-tips {
text-align: center;
font-size: 24rpx;
color: #666;
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<!-- #ifndef MP-TOUTIAO -->
<view class="text-box">亮度 : {{ screen }}</view>
<view class="uni-slider"><slider :value="screen" @changing="sliderChange" step="1" /></view>
<!-- #endif -->
<button type="primary" @click="keep">
{{ keepScreenOn ? '保持常亮状态' : '关闭常亮状态' }}
</button>
<view class="tips">
保持常亮时屏幕不会熄灭仅在当前应用生效离开应用后设置失效
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'brightness',
screen: 0,
keepScreenOn: true
};
},
onLoad() {
uni.getScreenBrightness({
success: res => {
this.screen = (res.value * 100).toFixed();
},
fail(e) {
console.log(e);
}
});
},
methods: {
sliderChange(e) {
let screen = e.detail.value;
//
if (this.screen !== screen) {
console.log('当前数值:' + e.detail.value);
uni.setScreenBrightness({
value: screen / 100,
success: function() {
},
fail(e) {
console.log(e);
}
});
this.screen = screen;
}
},
keep() {
uni.setKeepScreenOn({
keepScreenOn: this.keepScreenOn
});
this.keepScreenOn = !this.keepScreenOn;
}
}
};
</script>
<style>
.text-box {
margin-bottom: 40rpx;
display: flex;
justify-content: center;
align-items: center;
height: 300rpx;
background-color: #ffffff;
font-size: 32rpx;
color: #353535;
}
.uni-slider {
margin: 100rpx 0;
}
.tips {
font-size: 26rpx;
text-align: center;
margin-top: 20rpx;
color: #999;
}
</style>

366
pages/API/canvas/canvas.vue Normal file
View File

@ -0,0 +1,366 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-common-mt">
<canvas class="canvas-element" canvas-id="canvas" id="canvas"></canvas>
<scroll-view class="canvas-buttons" scroll-y="true">
<block v-for="(name, index) in names" :key="index">
<button class="canvas-button" @click="handleCanvasButton(name)">{{name}}</button>
</block>
<button class="canvas-button" @click="toTempFilePath" type="primary">toTempFilePath</button>
</scroll-view>
</view>
</view>
</template>
<script>
var context = null;
export default {
data() {
return {
title: 'createContext',
names: ["rotate", "scale", "reset", "translate", "save", "restore", "drawImage", "fillText", "fill",
"stroke", "clearRect", "beginPath", "closePath", "moveTo", "lineTo", "rect", "arc",
"quadraticCurveTo", "bezierCurveTo", "setFillStyle", "setStrokeStyle", "setGlobalAlpha",
"setShadow", "setFontSize", "setLineCap", "setLineJoin", "setLineWidth", "setMiterLimit"
]
}
},
onReady: function() {
context = uni.createCanvasContext('canvas',this)
},
methods: {
toTempFilePath: function() {
uni.canvasToTempFilePath({
canvasId: 'canvas',
success: function(res) {
console.log(res.tempFilePath)
},
fail: function(err) {
console.error(JSON.stringify(err))
}
})
},
handleCanvasButton: function(name) {
this[name] && this[name]();
},
rotate: function() {
context.beginPath()
context.rotate(10 * Math.PI / 180)
context.rect(225, 75, 20, 10)
context.fill()
context.draw()
},
scale: function() {
context.beginPath()
context.rect(25, 25, 50, 50)
context.stroke()
context.scale(2, 2)
context.beginPath()
context.rect(25, 25, 50, 50)
context.stroke()
context.draw()
},
reset: function() {
context.beginPath()
context.setFillStyle('#000000')
context.setStrokeStyle('#000000')
context.setFontSize(10)
context.setGlobalAlpha(1)
context.setShadow(0, 0, 0, 'rgba(0, 0, 0, 0)')
context.setLineCap('butt')
context.setLineJoin('miter')
context.setLineWidth(1)
context.setMiterLimit(10)
context.draw()
},
translate: function() {
context.beginPath()
context.rect(10, 10, 100, 50)
context.fill()
context.translate(70, 70)
context.beginPath()
context.fill()
context.draw()
},
save: function() {
context.beginPath()
context.setStrokeStyle('#00ff00')
context.save()
context.scale(2, 2)
context.setStrokeStyle('#ff0000')
context.rect(0, 0, 100, 100)
context.stroke()
context.restore()
context.rect(0, 0, 50, 50)
context.stroke()
context.draw()
},
restore: function() {
[3, 2, 1].forEach(function(item) {
context.beginPath()
context.save()
context.scale(item, item)
context.rect(10, 10, 100, 100)
context.stroke()
context.restore()
});
context.draw()
},
drawImage: function() {
// #ifdef APP-PLUS
context.drawImage('../../../static/app-plus/uni@2x.png', 0, 0)
// #endif
// #ifndef APP-PLUS
context.drawImage('../../../static/uni.png', 0, 0)
// #endif
context.draw()
},
fillText: function() {
context.setStrokeStyle('#ff0000')
context.beginPath()
context.moveTo(0, 10)
context.lineTo(300, 10)
context.stroke()
// context.save()
// context.scale(1.5, 1.5)
// context.translate(20, 20)
context.setFontSize(10)
context.fillText('Hello World', 0, 30)
context.setFontSize(20)
context.fillText('Hello World', 100, 30)
// context.restore()
context.beginPath()
context.moveTo(0, 30)
context.lineTo(300, 30)
context.stroke()
context.draw()
},
fill: function() {
context.beginPath()
context.rect(20, 20, 150, 100)
context.setStrokeStyle('#00ff00')
context.fill()
context.draw()
},
stroke: function() {
context.beginPath()
context.moveTo(20, 20)
context.lineTo(20, 100)
context.lineTo(70, 100)
context.setStrokeStyle('#00ff00')
context.stroke()
context.draw()
},
clearRect: function() {
context.setFillStyle('#ff0000')
context.beginPath()
context.rect(0, 0, 300, 150)
context.fill()
context.clearRect(20, 20, 100, 50)
context.draw()
},
beginPath: function() {
context.beginPath()
context.setLineWidth(5)
context.setStrokeStyle('#ff0000')
context.moveTo(0, 75)
context.lineTo(250, 75)
context.stroke()
context.beginPath()
context.setStrokeStyle('#0000ff')
context.moveTo(50, 0)
context.lineTo(150, 130)
context.stroke()
context.draw()
},
closePath: function() {
context.beginPath()
context.setLineWidth(1)
context.moveTo(20, 20)
context.lineTo(20, 100)
context.lineTo(70, 100)
context.closePath()
context.stroke()
context.draw()
},
moveTo: function() {
context.beginPath()
context.moveTo(0, 0)
context.lineTo(300, 150)
context.stroke()
context.draw()
},
lineTo: function() {
context.beginPath()
context.moveTo(20, 20)
context.lineTo(20, 100)
context.lineTo(70, 100)
context.stroke()
context.draw()
},
rect: function() {
context.beginPath()
context.rect(20, 20, 150, 100)
context.stroke()
context.draw()
},
arc: function() {
context.beginPath()
context.setLineWidth(2)
context.arc(75, 75, 50, 0, Math.PI * 2, true)
context.moveTo(110, 75)
context.arc(75, 75, 35, 0, Math.PI, false)
context.moveTo(65, 65)
context.arc(60, 65, 5, 0, Math.PI * 2, true)
context.moveTo(95, 65)
context.arc(90, 65, 5, 0, Math.PI * 2, true)
context.stroke()
context.draw()
},
quadraticCurveTo: function() {
context.beginPath()
context.moveTo(20, 20)
context.quadraticCurveTo(20, 100, 200, 20)
context.stroke()
context.draw()
},
bezierCurveTo: function() {
context.beginPath()
context.moveTo(20, 20)
context.bezierCurveTo(20, 100, 200, 100, 200, 20)
context.stroke()
context.draw()
},
setFillStyle: function() {
['#fef957', 'rgb(242,159,63)', 'rgb(242,117,63)', '#e87e51'].forEach(function(item, index) {
context.setFillStyle(item)
context.beginPath()
context.rect(0 + 75 * index, 0, 50, 50)
context.fill()
})
context.draw()
},
setStrokeStyle: function() {
['#fef957', 'rgb(242,159,63)', 'rgb(242,117,63)', '#e87e51'].forEach(function(item, index) {
context.setStrokeStyle(item)
context.beginPath()
context.rect(0 + 75 * index, 0, 50, 50)
context.stroke()
})
context.draw()
},
setGlobalAlpha: function() {
context.setFillStyle('#000000');
[1, 0.5, 0.1].forEach(function(item, index) {
context.setGlobalAlpha(item)
context.beginPath()
context.rect(0 + 75 * index, 0, 50, 50)
context.fill()
})
context.draw()
context.setGlobalAlpha(1)
},
setShadow: function() {
context.beginPath()
context.setShadow(10, 10, 10, 'rgba(0, 0, 0, 199)')
context.rect(10, 10, 100, 100)
context.fill()
context.draw()
},
setFontSize: function() {
[10, 20, 30, 40].forEach(function(item, index) {
context.setFontSize(item)
context.fillText('Hello, world', 20, 20 + 40 * index)
})
context.draw()
},
setLineCap: function() {
context.setLineWidth(10);
['butt', 'round', 'square'].forEach(function(item, index) {
context.beginPath()
context.setLineCap(item)
context.moveTo(20, 20 + 20 * index)
context.lineTo(100, 20 + 20 * index)
context.stroke()
})
context.draw()
},
setLineJoin: function() {
context.setLineWidth(10);
['bevel', 'round', 'miter'].forEach(function(item, index) {
context.beginPath()
context.setLineJoin(item)
context.moveTo(20 + 80 * index, 20)
context.lineTo(100 + 80 * index, 50)
context.lineTo(20 + 80 * index, 100)
context.stroke()
})
context.draw()
},
setLineWidth: function() {
[2, 4, 6, 8, 10].forEach(function(item, index) {
context.beginPath()
context.setLineWidth(item)
context.moveTo(20, 20 + 20 * index)
context.lineTo(100, 20 + 20 * index)
context.stroke()
})
context.draw()
},
setMiterLimit: function() {
context.setLineWidth(4);
[2, 4, 6, 8, 10].forEach(function(item, index) {
context.beginPath()
context.setMiterLimit(item)
context.moveTo(20 + 80 * index, 20)
context.lineTo(100 + 80 * index, 50)
context.lineTo(20 + 80 * index, 100)
context.stroke()
})
context.draw()
}
}
}
</script>
<style>
.canvas-element-wrapper {
display: block;
margin-bottom: 100rpx;
}
.canvas-element {
width: 100%;
height: 500rpx;
background-color: #ffffff;
}
.canvas-buttons {
padding: 30rpx 50rpx 10rpx;
width: 100%;
height: 360rpx;
box-sizing: border-box;
}
.canvas-button {
float: left;
display: inline-flex;
align-items: center;
justify-content: center;
height: 40px;
line-height: 1;
width: 300rpx;
margin: 15rpx 12rpx;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view style="background:#FFFFFF; padding:40rpx;">
<view class="uni-hello-text uni-center">当前位置信息</view>
<block v-if="hasLocation === false">
<view class="uni-h2 uni-center uni-common-mt">未选择位置</view>
</block>
<block v-if="hasLocation === true">
<view class="uni-hello-text uni-center" style="margin-top:10px;">
{{locationAddress}}
</view>
<view class="uni-h2 uni-center uni-common-mt">
<text>E: {{location.longitude[0]}}°{{location.longitude[1]}}</text>
<text>\nN: {{location.latitude[0]}}°{{location.latitude[1]}}</text>
</view>
</block>
</view>
<view class="uni-btn-v">
<button type="primary" @tap="chooseLocation">选择位置</button>
<button @tap="clear">清空</button>
</view>
</view>
</view>
</template>
<script>
import * as util from '../../../common/util.js'
var formatLocation = util.formatLocation;
export default {
data() {
return {
title: 'chooseLocation',
hasLocation: false,
location: {},
locationAddress: ''
}
},
methods: {
chooseLocation: function () {
uni.chooseLocation({
success: (res) => {
this.hasLocation = true,
this.location = formatLocation(res.longitude, res.latitude),
this.locationAddress = res.address
}
})
},
clear: function () {
this.hasLocation = false
}
}
}
</script>
<style>
.page-body-info {
padding-bottom: 0;
height: 440rpx;
}
</style>

View File

@ -0,0 +1,91 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-title">请输入剪贴板内容</view>
<view class="uni-list">
<view class="uni-list-cell">
<input class="uni-input" type="text" placeholder="请输入剪贴板内容" :value="data" @input="dataChange"/>
</view>
</view>
<view class="uni-btn-v">
<button type="primary" @click="setClipboard">存储数据</button>
<button @tap="getClipboard">读取数据</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'get/setClipboardData',
data: ''
}
},
methods: {
dataChange: function (e) {
this.data = e.detail.value
},
getClipboard: function () {
uni.getClipboardData({
success: (res) => {
console.log(res.data);
const content = res.data ? '剪贴板内容为:' + res.data : '剪贴板暂无内容';
uni.showModal({
content,
title: '读取剪贴板',
showCancel: false
})
},
fail: () => {
uni.showModal({
content: '读取剪贴板失败!',
showCancel: false
})
}
});
},
setClipboard: function () {
var data = this.data;
if (data.length === 0) {
uni.showModal({
title: '设置剪贴板失败',
content: '内容不能为空',
showCancel: false
})
} else {
uni.setClipboardData({
data: data,
success: () => {
//
// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
uni.showToast({
title: '设置剪贴板成功',
icon: "success",
mask: !1
})
// #endif
},
fail: () => {
//
// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
uni.showToast({
title: '储存数据失败!',
icon: "none",
mask: !1
})
// #endif
}
});
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,27 @@
const platformInfo = process.env.uniTestPlatformInfo.toLocaleLowerCase()
describe('pages/API/download-file/download-file.vue', () => {
let page;
const versions = ['12.4', '13.7', '15.5', '17.4', '17.5_深色主题', '18.1'];
const [platform, version] = platformInfo.split(' ');
if (platform === 'ios_simulator' && versions.includes(version)) {
it('skip', async () => {
expect(1).toBe(1);
});
return;
}
beforeAll(async () => {
page = await program.reLaunch('/pages/API/download-file/download-file')
await page.waitFor('view')
});
it('check download url', async () => {
expect.assertions(2);
await page.callMethod('downloadImage')
const waitTime = process.env.uniTestPlatformInfo.includes('firefox') ? 5000:2000
const start = Date.now();
await page.waitFor(async () => {
return await page.data('jest_result') === true || (Date.now() - start > waitTime)
})
expect(await page.data('jest_result')).toBeTruthy();
expect(await page.data('imageSrc')).toBeTruthy();
});
});

View File

@ -0,0 +1,67 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view v-if="imageSrc" class="image-container">
<image class="img" :src="imageSrc" mode="center" />
</view>
<block v-else style="margin-top: 50px;">
<view class="uni-hello-text">
点击按钮下载服务端示例图片下载网络文件到本地临时目录
</view>
<view class="uni-btn-v">
<button type="primary" @tap="downloadImage">下载</button>
</view>
</block>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'downloadFile',
imageSrc: '',
//
jest_result: false
}
},
onUnload() {
this.imageSrc = '';
},
methods: {
downloadImage: function () {
uni.showLoading({
title:'下载中'
})
var self = this
uni.downloadFile({
url: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-app.png",
success: (res) => {
this.jest_result = true
console.log('downloadFile success, res is', res)
self.imageSrc = res.tempFilePath;
uni.hideLoading();
},
fail: (err) => {
console.log('downloadFile fail, err is:', err)
this.jest_result = false
}
})
}
}
}
</script>
<style>
.img {
width: 500rpx;
height: 500rpx;
margin: 0 auto;
}
.image-container {
display: flex;
justify-content: center;
align-items: center;
}
</style>

129
pages/API/file/file.vue Normal file
View File

@ -0,0 +1,129 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<block v-if="tempFilePath">
<image :src="tempFilePath" class="image" mode="aspectFit"></image>
</block>
<block v-if="!tempFilePath && savedFilePath">
<image :src="savedFilePath" class="image" mode="aspectFit"></image>
</block>
<block v-if="!tempFilePath && !savedFilePath">
<view class="uni-hello-addfile" @click="chooseImage">+ 请选择文件</view>
</block>
<view class="uni-btn-v">
<button class="btn-savefile" @click="saveFile">保存文件</button>
<button @click="clear">删除文件</button>
</view>
<!-- #ifndef MP-ALIPAY || MP-TOUTIAO -->
<view class="btn-area">
<button @click="openDocument">打开pdf文件</button>
</view>
<!-- #endif -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'saveFile',
tempFilePath: '',
savedFilePath: ''
}
},
onLoad() {
this.savedFilePath = uni.getStorageSync('savedFilePath');
},
methods: {
chooseImage() {
uni.chooseImage({
count: 1,
success: (res) => {
this.tempFilePath = res.tempFilePaths[0];
},
fail: (err) => {
// #ifdef MP
uni.getSetting({
success: (res) => {
let authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
if (!authStatus) {
uni.showModal({
title: '授权失败',
content: 'Hello uni-app需要从您的相机或相册获取图片请在设置界面打开相关权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
}
}
})
// #endif
}
});
},
saveFile() {
if (this.tempFilePath.length > 0) {
uni.saveFile({
tempFilePath: this.tempFilePath,
success: (res) => {
this.savedFilePath = res.savedFilePath;
uni.setStorageSync('savedFilePath', res.savedFilePath);
uni.showModal({
title: '保存成功',
content: '下次进入页面时,此文件仍可用',
showCancel: false
});
},
fail: (res) => {
uni.showModal({
title: '保存失败',
content: '失败原因: ' + JSON.stringify(res),
showCancel: false
});
}
})
} else {
uni.showModal({
content: '请选择文件',
showCancel: false
});
}
},
clear() {
uni.setStorageSync('savedFilePath', '');
this.tempFilePath = '';
this.savedFilePath = '';
},
// #ifndef MP-ALIPAY || MP-TOUTIAO
openDocument() {
uni.downloadFile({
url: 'https://web-assets.dcloud.net.cn/unidoc/zh/helloworld.pdf',
success: (res) => {
uni.openDocument({
filePath: res.tempFilePath,
success: () => {
console.log('打开文档成功');
}
});
}
});
},
// #endif
}
}
</script>
<style>
.image {
width: 100%;
height: 360rpx;
}
.btn-savefile {
background-color: #007aff;
color: #ffffff;
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<button :loading="loading" :disabled="loading" type="primary" class="btn" @click="showAd">显示广告</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: '全屏视频广告',
loading: false,
loadError: false
}
},
onReady() {
// #ifdef APP-PLUS
this.adOption = {
adpid: '1507000611'
};
// #endif
this.createAd();
},
methods: {
createAd() {
var _ad = this._ad = uni.createFullScreenVideoAd(this.adOption);
_ad.onLoad(() => {
this.loading = false;
this.loadError = false;
_ad.show();
console.log('onLoad event')
});
_ad.onClose((res) => {
// 广
if (res && res.isEnded) {
//
console.log("onClose " + res.isEnded);
} else {
// 退
console.log("onClose " + res.isEnded);
}
setTimeout(() => {
uni.showToast({
title: "全屏视频" + (res.isEnded ? "成功" : "未") + "播放完毕",
duration: 10000,
position: 'bottom'
})
}, 500)
});
_ad.onError((err) => {
this.loading = false;
if (err.code) {
this.loadError = true;
}
console.log('onError event', err)
uni.showToast({
title: err.errMsg,
position: 'bottom'
})
});
},
showAd() {
this.loading = true;
this._ad.load();
}
}
}
</script>
<style>
.btn {
margin-bottom: 20px;
}
.ad-tips {
color: #999;
padding: 30px 0;
text-align: center;
}
</style>

View File

@ -0,0 +1,200 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view style="background:#FFFFFF; padding:40rpx;">
<view class="uni-hello-text uni-center">当前位置经纬度</view>
<block v-if="hasLocation === false">
<view class="uni-h2 uni-center uni-common-mt">未获取</view>
</block>
<block v-if="hasLocation === true">
<view class="uni-h2 uni-center uni-common-mt">
<text>E: {{location.longitude[0]}}°{{location.longitude[1]}}</text>
<text>\nN: {{location.latitude[0]}}°{{location.latitude[1]}}</text>
</view>
</block>
</view>
<view class="uni-btn-v">
<button type="primary" @tap="getLocation">获取位置</button>
<button @tap="clear">清空</button>
</view>
</view>
<uni-popup :show="type === 'showpopup'" mode="fixed" @hidePopup="togglePopup('')">
<view class="popup-view">
<text class="popup-title">需要用户授权位置权限</text>
<view class="uni-flex popup-buttons">
<button class="uni-flex-item" type="primary" open-type="openSetting" @tap="openSetting">设置</button>
<button class="uni-flex-item" @tap="togglePopup('')">取消</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import * as util from '../../../common/util.js'
var formatLocation = util.formatLocation;
// #ifdef APP-PLUS
import permision from "@/common/permission.js"
// #endif
export default {
data() {
return {
title: 'getLocation',
hasLocation: false,
location: {},
type: ''
}
},
methods: {
togglePopup(type) {
this.type = type;
},
showConfirm() {
this.type = 'showpopup';
},
hideConfirm() {
this.type = '';
},
async getLocation() {
// #ifdef APP-PLUS
let status = await this.checkPermission();
if (status !== 1) {
return;
}
// #endif
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-QQ
let status = await this.getSetting();
if (status === 2) {
this.showConfirm();
return;
}
// #endif
// #ifdef MP-HARMONY
uni.authorize({
scope: "scope.userLocation",
success: () => {
this.doGetLocation();
},
fail: () => {
uni.showToast({
title: "未授权获取地理位置权限"
})
}
})
// #endif
// #ifndef MP-HARMONY
this.doGetLocation();
// #endif
},
doGetLocation() {
uni.getLocation({
success: (res) => {
this.hasLocation = true;
this.location = formatLocation(res.longitude, res.latitude);
},
fail: (err) => {
// #ifdef MP-BAIDU
if (err.errCode === 202 || err.errCode === 10003) { // 202 10003 user deny
this.showConfirm();
}
// #endif
// #ifndef MP-BAIDU
if (err.errMsg.indexOf("auth deny") >= 0) {
uni.showToast({
title: "访问位置被拒绝"
})
} else {
uni.showToast({
title: err.errMsg
})
}
// #endif
}
})
},
getSetting: function() {
return new Promise((resolve, reject) => {
uni.getSetting({
success: (res) => {
if (res.authSetting['scope.userLocation'] === undefined) {
resolve(0);
return;
}
if (res.authSetting['scope.userLocation']) {
resolve(1);
} else {
resolve(2);
}
}
});
});
},
openSetting: function() {
this.hideConfirm();
uni.openSetting({
success: (res) => {
if (res.authSetting && res.authSetting['scope.userLocation']) {
this.doGetLocation();
}
},
fail: (err) => {}
})
},
async checkPermission() {
let status = permision.isIOS ? await permision.requestIOS('location') :
await permision.requestAndroid('android.permission.ACCESS_FINE_LOCATION');
if (status === null || status === 1) {
status = 1;
} else if (status === 2) {
uni.showModal({
content: "系统定位已关闭",
confirmText: "确定",
showCancel: false,
success: function(res) {
}
})
} else if (status.code) {
uni.showModal({
content: status.message
})
} else {
uni.showModal({
content: "需要定位权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
return status;
},
clear: function() {
this.hasLocation = false
}
}
}
</script>
<style>
.popup-view {
width: 500rpx;
}
.popup-title {
display: block;
font-size: 16px;
line-height: 3;
margin-bottom: 10px;
text-align: center;
}
.popup-buttons button {
margin-left: 4px;
margin-right: 4px;
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view style="background:#FFFFFF; padding:40rpx;">
<view class="uni-hello-text uni-center">网络状态</view>
<block v-if="hasNetworkType === false">
<view class="uni-h2 uni-center uni-common-mt">未获取</view>
<view class="uni-hello-text uni-center uni-common-mt">请点击下面按钮获取网络状态</view>
</block>
<block v-if="hasNetworkType === true">
<view class="uni-h2 uni-center uni-common-mt">{{networkType}}</view>
</block>
<!-- #ifndef MP-HARMONY -->
<view v-if="hasNetworkType === true && networkType === 'wifi'" class="uni-textarea uni-common-mt">
<textarea :value="connectedWifi"></textarea>
</view>
<!-- #endif -->
</view>
<view class="uni-btn-v uni-common-mt">
<button type="primary" @tap="getNetworkType">获取设备网络状态</button>
<!-- #ifdef MP-WEIXIN || MP-JD-->
<button v-if="hasNetworkType === true && networkType === 'wifi'" class="uni-common-mt" type="primary" @tap="getConnectedWifi">获取 wifi 信息</button>
<!-- #endif -->
<button class="uni-common-mt" @tap="clear">清空</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'getNetworkType',
hasNetworkType: false,
networkType: '',
connectedWifi: ''
}
},
onUnload:function(){
this.networkType = '',this.hasNetworkType = false;
},
methods: {
getNetworkType: function () {
uni.getNetworkType({
success: (res) => {
console.log(res)
this.hasNetworkType = true, this.networkType = res.subtype || res.networkType
},
fail: () => {
uni.showModal({
content:'获取失败!',
showCancel:false
})
}
})
},
clear: function () {
this.hasNetworkType = false,
this.networkType = '',
this.connectedWifi = ''
},
// #ifdef MP-WEIXIN || MP-JD
getConnectedWifi() {
const that = this
uni.startWifi({
success: function() {
uni.getConnectedWifi({
success: function(res) {
const { wifi } = res
that.connectedWifi = JSON.stringify(wifi)
},
fail: function(res) {
}
})
},
fail: function(res) {
}
})
}
// #endif
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,32 @@
describe('测试 css 变量', () => {
let page
const isApp = process.env.UNI_PLATFORM.includes('app');
beforeAll(async () => {
// 重新reLaunch至首页并获取首页page对象其中 program 是uni-automator自动注入的全局对象
page = await program.reLaunch('/pages/API/get-node-info/get-node-info')
await page.waitFor(1000)
})
it('css var', async () => {
if (isApp) {
const element1 = await page.$('.box1')
const size = await element1.size()
expect(size.height > 0).toBe(true)
const element2 = await page.$('.box2')
const size2 = await element2.size()
expect(size2.height === 0).toBe(true)
const element3 = await page.$('.box3')
const size3 = await element3.size()
expect(size3.height === 0).toBe(true)
const element4 = await page.$('.box4')
const size4 = await element4.size()
expect(size4.height > size.height).toBe(true)
} else {
expect(1).toBe(1)
}
})
});

View File

@ -0,0 +1,141 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="movable block">
<!-- #ifndef MP-TOUTIAO -->
<movable-area>
<movable-view class="target" direction="all" @change="getNodeInfo">Drag</movable-view>
</movable-area>
<!-- #endif -->
<!-- #ifdef MP-TOUTIAO -->
<view class="target" @click="setPosition" :style="{top:top,left:left}">Click</view>
<!-- #endif -->
</view>
<view class="movable">
<view class="info">
<view v-for="(item,index) in info" :key="index">
<text class="b">{{item.key}}</text>
<text class="span">{{item.val}}</text>
</view>
</view>
</view>
<!-- #ifdef APP -->
<view>
<view class="box box1"></view>
<view class="box box2"></view>
<view class="box box3"></view>
<view class="box box4"></view>
</view>
<!-- #endif -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'createSelectorQuery',
top: 0,
left: '0px',
info: []
}
},
onReady() {
this.getNodeInfo()
},
methods: {
setPosition() {
this.left = Math.random() * uni.upx2px(320) + 'px'
this.top = Math.random() * uni.upx2px(320) + 'px'
this.getNodeInfo()
},
getNodeInfo() {
uni.createSelectorQuery().select('.target').boundingClientRect().exec((ret) => {
const rect = ret[0]
if (rect) {
const sort = ['left','right','top','bottom','width','height']
const info = []
for (let i in sort) {
let key = sort[i]
info.push({
'key': key,
'val': rect[key]
})
}
this.info = info
}
});
}
},
}
</script>
<style>
.movable {
display: flex;
justify-content: center;
}
.block {
height: 400rpx;
width: 400rpx;
background-color: #FFFFFF;
border: 1px solid #ccc;
position: relative;
margin: 0 auto;
margin-bottom: 30rpx;
}
movable-area {
height: 400rpx;
width: 400rpx;
position: relative;
}
.target {
height: 80rpx;
width: 80rpx;
line-height: 80rpx;
text-align: center;
color: #FFFFFF;
background-color: #4cd964;
font-size: 28rpx;
position: absolute;
}
.info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.b {
font-weight: bold;
width: 150rpx;
display: inline-block;
}
.span {
width: 100rpx;
display: inline-block;
}
.box {
width: 50px;
}
.box1 {
height: var(--status-bar-height);
}
.box2 {
height: var(--window-bottom);
}
.box3 {
height: var(--window-top);
}
.box4 {
height: calc(var(--status-bar-height) + 2px);
}
</style>

View File

@ -0,0 +1,148 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-common-mt">
<view class="uni-list">
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">设备型号</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.model"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">客户端平台</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.platform"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">操作系统版本</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.system"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">语言</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.language"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">版本</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.version"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">屏幕宽度</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.screenWidth"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">屏幕高度</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.screenHeight"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">可使用窗口高度</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.windowHeight"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">可使用窗口的顶部位置</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.windowTop"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">可使用窗口的底部位置</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.windowBottom"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">状态栏的高度</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.statusBarHeight"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">DPI</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.pixelRatio"/>
</view>
</view>
<!-- #ifdef MP -->
<view class="uni-list-cell">
<view class="uni-pd">
<view class="uni-label" style="width:180px;">基础库版本</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.SDKVersion"/>
</view>
</view>
<!-- #endif -->
</view>
<view class="uni-padding-wrap">
<view class="uni-btn-v">
<button type="primary" @tap="getSystemInfo">获取设备系统信息</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'getSystemInfo',
systemInfo: {}
}
},
onUnload:function(){
this.systemInfo = {};
},
methods: {
getSystemInfo: function () {
uni.getSystemInfo({
success: (res) => {
this.systemInfo = res
}
})
}
}
}
</script>
<style>
.uni-pd {
padding-left: 30rpx;
}
</style>

View File

@ -0,0 +1,165 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view style="background:#FFF; padding:40rpx;">
<block v-if="hasUserInfo === false">
<view class="uni-hello-text uni-center">
<text>请点击下方按钮获取用户头像及昵称或手机号</text>
</view>
</block>
<block v-if="hasUserInfo === true">
<view class="uni-h4 uni-center uni-common-mt">{{userInfo.nickName || userInfo.nickname || userInfo.gender || userInfo.email || userInfo.phoneNumber}}</view>
<view v-if="userInfo.avatarUrl || userInfo.avatar_url " style="padding:30rpx 0; text-align:center;">
<image class="userinfo-avatar" :src="userInfo.avatarUrl||userInfo.avatar_url"></image>
</view>
</block>
</view>
<view class="uni-btn-v">
<!-- #ifdef APP-PLUS || MP-ALIPAY || MP-TOUTIAO -->
<button type="primary" :loading="btnLoading" @click="getUserInfo">获取用户信息</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-JD -->
<button type="primary" open-type="getUserInfo" @getuserinfo="mpGetUserInfo">获取用户信息</button>
<!-- #endif -->
<button @click="clear">清空</button>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations,
mapActions
} from 'vuex'
export default {
data() {
return {
title: 'getUserInfo',
hasUserInfo: false,
userInfo: {},
btnLoading: false
}
},
computed: {
...mapState([
'loginProvider',
'isUniverifyLogin'
])
},
methods: {
...mapActions(['getPhoneNumber']),
// API 使 5+App
getUserInfo() {
this.btnLoading = true;
if (this.isUniverifyLogin) {
//
this.getPhoneNumber(uni.getStorageSync('univerifyInfo')).then(phoneNumber => {
this.hasUserInfo = true;
this.userInfo = {
phoneNumber
};
}).catch(err => {
console.error('getUserInfo fail:', err);
this.hasUserInfo = false;
}).finally(() => {
this.btnLoading = false;
})
return;
}
if(this.loginProvider === 'apple'){
const nickname = uni.getStorageSync('apple_nickname')
if(nickname){
this.hasUserInfo = true;
this.userInfo = { nickName:nickname }
this.btnLoading = false;
return;
}
}
uni.getUserInfo({
provider: this.loginProvider,
success: (result) => {
this.hasUserInfo = true;
this.userInfo = result.userInfo;
},
fail: (error) => {
console.log('getUserInfo fail', error);
let content = error.errMsg;
if (~content.indexOf('uni.login')) {
content = '请在登录页面完成登录操作';
}
// #ifndef APP-PLUS
uni.getSetting({
success: (res) => {
let authStatus = res.authSetting['scope.userInfo'];
if (!authStatus) {
uni.showModal({
title: '授权失败',
content: 'Hello uni-app需要获取您的用户信息请在设置界面打开相关权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
} else {
uni.showModal({
title: '获取用户信息失败',
content: '错误原因' + content,
showCancel: false
});
}
}
})
// #endif
// #ifdef APP-PLUS
uni.showModal({
title: '获取用户信息失败',
content: '错误原因' + content,
showCancel: false
});
// #endif
},
complete: () => {
this.btnLoading = false;
}
});
},
mpGetUserInfo(result) {
console.log('mpGetUserInfo', result);
if (result.detail.errMsg !== 'getUserInfo:ok') {
uni.showModal({
title: '获取用户信息失败',
content: '错误原因' + result.detail.errMsg,
showCancel: false
});
return;
}
this.hasUserInfo = true;
if(result.detail && result.detail.userInfo){
this.userInfo = result.detail.userInfo;
}else{
// #ifdef MP-JD
this.userInfo = result.detail.user_info;
// #endif
}
},
clear() {
this.hasUserInfo = false;
this.userInfo = {};
}
}
}
</script>
<style>
.userinfo-avatar {
border-radius: 128rpx;
width: 128rpx;
height: 128rpx;
}
</style>

View File

@ -0,0 +1,300 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-btn-v">
<button type="primary" :disabled="isOpen" @click="openBluetoothAdapter">打开蓝牙模块</button>
<button type="primary" :disabled="!isOpen" @click="closeBluetoothAdapter">关闭蓝牙模块</button>
<button type="primary" :disabled="!isOpen || isStarted" :loading="isStarted" @click="startBeaconDiscovery">开始搜索附近的iBeacon设备</button>
<button type="primary" :disabled="!isStarted" @click="stopBeaconDiscovery">停止搜索附近的iBeacon设备</button>
</view>
</view>
<scroll-view class="uni-scroll_box">
<view class="uni-title" v-if="isStarted || deviceList.length > 0">已经发现 {{ deviceList.length }} 台设备:</view>
<view class="uni-list-box" v-for="(item, index) in deviceList" :key="item.uuid">
<view>
<view class="uni-list_name">UUID: {{ item.uuid }}</view>
<view class="uni-list_item">major: {{ item.major }}</view>
<view class="uni-list_item">minor: {{ item.minor }}</view>
<view class="uni-list_item">rssi: {{ item.rssi }} dBm</view>
<view class="uni-list_item">accuracy: {{ item.accuracy }}</view>
<view class="uni-list_item">heading: {{ item.heading }}</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
const DEVICE_UUID_WEICHAT = 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825';
export default {
data() {
return {
title: 'iBeacon',
isOpen: false,
isStarted: false,
deviceList: [],
isPageOpened: false
};
},
onLoad() {
this.onBeaconUpdate();
},
onShow() {
this.isPageOpened = true;
},
methods: {
maskclose() {
this.maskShow = false;
},
openBluetoothAdapter() {
uni.openBluetoothAdapter({
success: (res) => {
console.log('初始化蓝牙成功:' + res.errMsg);
console.log(res);
this.isOpen = true;
this.deviceList = [];
},
fail: (err) => {
console.log('初始化蓝牙失败,错误码:' + (err.errCode || err.errMsg));
if (err.errCode !== 0) {
initTypes(err.errCode, err.errMsg);
}
}
});
},
closeBluetoothAdapter(OBJECT) {
this.stopBeaconDiscovery();
wx.closeBluetoothAdapter({
success: (res) => {
this.isOpen = false;
console.log('断开蓝牙模块成功');
}
});
},
onBeaconUpdate() {
uni.onBeaconUpdate(res => {
if (!this.isPageOpened || !this.isOpen || !this.isStarted) {
return;
}
console.log(res);
// if (res.code !== 0) {
// return;
// }
if (res.beacons && res.beacons.length > 0) {
this.getBeacons();
} else if (!res.connected) {
this.deviceList = [];
}
});
},
startBeaconDiscovery() {
uni.startBeaconDiscovery({
uuids: this.getUUIDList(),
success: (res) => {
this.isStarted = true;
console.log(res);
},
fail: (err) => {
console.error(err);
}
});
},
stopBeaconDiscovery(types) {
if(this.isStarted) {
uni.stopBeaconDiscovery({
success: (res) => {
this.isStarted = false;
},
fail: (err) => {
console.error(err);
}
});
}
},
getBeacons() {
uni.getBeacons({
success: (res) => {
if (res.beacons && res.beacons.length > 0) {
console.log(res.beacons);
this.deviceList = res.beacons;
}
},
fail: (err) => {
console.log('获取设备服务失败,错误码:' + err.errCode);
if (errCode.errCode !== 0) {
initTypes(errCode.errCode);
}
}
});
},
getUUIDList() {
// #ifdef APP-PLUS
const sysInfo = uni.getSystemInfoSync();
if (!sysInfo) {
return [];
}
let isIOS = sysInfo.platform ? (sysInfo.platform.toLowerCase() === "ios") : false;
if (isIOS) {
return [DEVICE_UUID_WEICHAT];
}
return [];
// #endif
// #ifndef APP-PLUS
return [DEVICE_UUID_WEICHAT];
// #endif
}
},
onUnload() {
this.isPageOpened = false;
}
};
/**
* 判断初始化蓝牙状态
*/
function initTypes(code, errMsg) {
switch (code) {
case 10000:
toast('未初始化蓝牙适配器');
break;
case 10001:
toast('未检测到蓝牙,请打开蓝牙重试!');
break;
case 10002:
toast('没有找到指定设备');
break;
case 10003:
toast('连接失败');
break;
case 10004:
toast('没有找到指定服务');
break;
case 10005:
toast('没有找到指定特征值');
break;
case 10006:
toast('当前连接已断开');
break;
case 10007:
toast('当前特征值不支持此操作');
break;
case 10008:
toast('其余所有系统上报的异常');
break;
case 10009:
toast('Android 系统特有,系统版本低于 4.3 不支持 BLE');
break;
default:
toast(errMsg);
}
}
/**
* 弹出框封装
*/
function toast(content, showCancel = false) {
uni.showModal({
title: '提示',
content,
showCancel
});
}
</script>
<style>
.uni-title {
/* width: 100%; */
/* height: 80rpx; */
text-align: center;
}
.uni-mask {
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
align-items: center;
width: 100%;
background: rgba(0, 0, 0, 0.6);
padding: 0 30rpx;
box-sizing: border-box;
}
.uni-scroll_box {
height: 70%;
background: #fff;
border-radius: 20rpx;
}
.uni-list-box {
margin: 0 20rpx;
padding: 15rpx 0;
border-bottom: 1px #f5f5f5 solid;
box-sizing: border-box;
}
.uni-list:last-child {
border: none;
}
.uni-list_name {
font-size: 30rpx;
color: #333;
}
.uni-list_item {
font-size: 24rpx;
color: #555;
line-height: 1.5;
}
.uni-success_box {
position: absolute;
left: 0;
bottom: 0;
min-height: 100rpx;
width: 100%;
background: #fff;
box-sizing: border-box;
border-top: 1px #eee solid;
}
.uni-success_sub {
/* width: 100%%; */
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
}
.uni-close_button {
padding: 0 20rpx;
height: 60rpx;
line-height: 60rpx;
background: #ce3c39;
color: #ffffff;
border-radius: 10rpx;
}
.uni-success_content {
height: 600rpx;
margin: 30rpx;
margin-top: 0;
border: 1px #eee solid;
padding: 30rpx;
}
.uni-content_list {
padding-bottom: 10rpx;
border-bottom: 1px #f5f5f5 solid;
}
.uni-tips {
text-align: center;
font-size: 24rpx;
color: #666;
}
</style>

239
pages/API/image/image.vue Normal file
View File

@ -0,0 +1,239 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-common-mt">
<form>
<view class="uni-list">
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">图片来源</view>
</view>
<view class="uni-list-cell-right">
<picker :range="sourceType" @change="sourceTypeChange" :value="sourceTypeIndex" mode="selector">
<view class="uni-input">{{sourceType[sourceTypeIndex]}}</view>
</picker>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">图片质量</view>
</view>
<view class="uni-list-cell-right">
<picker :range="sizeType" @change="sizeTypeChange" :value="sizeTypeIndex" mode="selector">
<view class="uni-input">{{sizeType[sizeTypeIndex]}}</view>
</picker>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">数量限制</view>
</view>
<view class="uni-list-cell-right">
<picker :range="count" @change="countChange" mode="selector">
<view class="uni-input">{{count[countIndex]}}</view>
</picker>
</view>
</view>
</view>
<view class="uni-list list-pd">
<view class="uni-list-cell cell-pd">
<view class="uni-uploader">
<view class="uni-uploader-head">
<view class="uni-uploader-title">点击可预览选好的图片</view>
<view class="uni-uploader-info">{{imageList.length}}/9</view>
</view>
<view class="uni-uploader-body">
<view class="uni-uploader__files">
<block v-for="(image,index) in imageList" :key="index">
<view class="uni-uploader__file">
<image class="uni-uploader__img" :src="image" :data-src="image" @tap="previewImage"></image>
</view>
</block>
<view class="uni-uploader__input-box">
<view class="uni-uploader__input" @tap="chooseImage"></view>
</view>
</view>
</view>
</view>
</view>
</view>
</form>
</view>
</view>
</template>
<script>
import permision from "@/common/permission.js"
var sourceType = [
['camera'],
['album'],
['camera', 'album']
]
var sizeType = [
['compressed'],
['original'],
['compressed', 'original']
]
export default {
data() {
return {
title: 'choose/previewImage',
imageList: [],
sourceTypeIndex: 2,
sourceType: ['拍照', '相册', '拍照或相册'],
sizeTypeIndex: 2,
sizeType: ['压缩', '原图', '压缩或原图'],
countIndex: 8,
count: [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
},
onUnload() {
this.imageList = [],
this.sourceTypeIndex = 2,
this.sourceType = ['拍照', '相册', '拍照或相册'],
this.sizeTypeIndex = 2,
this.sizeType = ['压缩', '原图', '压缩或原图'],
this.countIndex = 8;
},
methods: {
sourceTypeChange: function(e) {
this.sourceTypeIndex = parseInt(e.detail.value)
},
sizeTypeChange: function(e) {
this.sizeTypeIndex = parseInt(e.detail.value)
},
countChange: function(e) {
this.countIndex = e.detail.value;
},
chooseImage: async function() {
// #ifdef APP-PLUS
// TODO actionsheet
if (this.sourceTypeIndex !== 2) {
let status = await this.checkPermission();
if (status !== 1) {
return;
}
}
// #endif
if (this.imageList.length === 9) {
let isContinue = await this.isFullImg();
console.log("是否继续?", isContinue);
if (!isContinue) {
return;
}
}
uni.chooseImage({
sourceType: sourceType[this.sourceTypeIndex],
sizeType: sizeType[this.sizeTypeIndex],
count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
success: (res) => {
this.imageList = this.imageList.concat(res.tempFilePaths);
},
fail: (err) => {
console.log("err: ",err);
// #ifdef APP-PLUS
if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
this.checkPermission(err.code);
}
// #endif
// #ifdef MP
if(err.errMsg.indexOf('cancel') !== '-1'){
return;
}
uni.getSetting({
success: (res) => {
let authStatus = false;
switch (this.sourceTypeIndex) {
case 0:
authStatus = res.authSetting['scope.camera'];
break;
case 1:
authStatus = res.authSetting['scope.album'];
break;
case 2:
authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
break;
default:
break;
}
if (!authStatus) {
uni.showModal({
title: '授权失败',
content: 'Hello uni-app需要从您的相机或相册获取图片请在设置界面打开相关权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
}
}
})
// #endif
}
})
},
isFullImg: function() {
return new Promise((res) => {
uni.showModal({
content: "已经有9张图片了,是否清空现有图片?",
success: (e) => {
if (e.confirm) {
this.imageList = [];
res(true);
} else {
res(false)
}
},
fail: () => {
res(false)
}
})
})
},
previewImage: function(e) {
var current = e.target.dataset.src
uni.previewImage({
current: current,
urls: this.imageList
})
},
async checkPermission(code) {
let type = code ? code - 1 : this.sourceTypeIndex;
let status = permision.isIOS ? await permision.requestIOS(sourceType[type][0]) :
await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
'android.permission.READ_EXTERNAL_STORAGE');
if (status === null || status === 1) {
status = 1;
} else {
uni.showModal({
content: "没有开启权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
return status;
}
}
}
</script>
<style>
.cell-pd {
padding: 22rpx 30rpx;
}
.list-pd {
margin-top: 50rpx;
}
</style>

View File

@ -0,0 +1,124 @@
<template>
<view class="uni-padding-wrap">
<page-head title="audio"></page-head>
<view class="uni-common-mt">
<slider :value="position" :min="0" :max="duration" @changing="onchanging" @change="onchange"></slider>
</view>
<!-- <view class="uni-common-mt play-time-area">
<text class="current-time">{{currentTime}}</text>
<text class="duration">{{duration}}</text>
</view> -->
<view class="play-button-area">
<image class="icon-play" :src="playImage" @click="play"></image>
</view>
</view>
</template>
<script>
const audioUrl = 'https://web-ext-storage.dcloud.net.cn/uni-app/ForElise.mp3'
export default {
data() {
return {
title: "innerAudioContext",
isPlaying: false,
isPlayEnd: false,
currentTime: 0,
duration: 100
}
},
computed: {
position() {
return this.isPlayEnd ? 0 : this.currentTime;
},
playImage() {
return this.isPlaying ? "/static/pause.png" : "/static/play.png"
}
},
onLoad() {
this._isChanging = false;
this._audioContext = null;
this.createAudio();
},
onUnload() {
if (this._audioContext != null && this.isPlaying) {
this.stop();
}
},
methods: {
createAudio() {
var innerAudioContext = this._audioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = false;
innerAudioContext.src = audioUrl;
innerAudioContext.onPlay(() => {
console.log('开始播放');
});
innerAudioContext.onTimeUpdate((e) => {
if (this._isChanging === true) {
return;
}
this.currentTime = innerAudioContext.currentTime || 0;
this.duration = innerAudioContext.duration || 0;
});
innerAudioContext.onEnded(() => {
this.currentTime = 0;
this.isPlaying = false;
this.isPlayEnd = true;
});
innerAudioContext.onError((res) => {
this.isPlaying = false;
console.log(res.errMsg);
console.log(res.errCode);
});
return innerAudioContext;
},
onchanging() {
this._isChanging = true;
},
onchange(e) {
console.log(e.detail.value);
console.log(typeof e.detail.value);
this._audioContext.seek(e.detail.value);
this._isChanging = false;
},
play() {
if (this.isPlaying) {
this.pause();
return;
}
this.isPlaying = true;
this._audioContext.play();
this.isPlayEnd = false;
},
pause() {
this._audioContext.pause();
this.isPlaying = false;
},
stop() {
this._audioContext.stop();
this.isPlaying = false;
}
}
}
</script>
<style>
.play-time-area {
display: flex;
flex-direction: row;
margin-top: 20px;
}
.duration {
margin-left: auto;
}
.play-button-area {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 50px;
}
.icon-play {
width: 60px;
height: 60px;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-title uni-common-mt">
{{appear ? '小球出现' : '小球消失'}}
</view>
<scroll-view class="scroll-view" scroll-y>
<view class="scroll-area">
<text class="notice">向下滚动让小球出现</text>
<view class="ball"></view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
let observer = null;
export default {
data() {
return {
appear: false,
title:'intersectionObserver'
}
},
onReady() {
observer = uni.createIntersectionObserver(this);
observer.relativeTo('.scroll-view').observe('.ball', (res) => {
if (res.intersectionRatio > 0 && !this.appear) {
this.appear = true;
} else if (!res.intersectionRatio > 0 && this.appear) {
this.appear = false;
}
})
},
onUnload() {
if (observer) {
observer.disconnect()
}
}
}
</script>
<style>
.scroll-view {
height: 400rpx;
background: #fff;
border: 1px solid #ccc;
box-sizing: border-box;
}
.scroll-area {
height: 1300rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.notice {
margin-top: 150rpx;
margin: 150rpx 0 400rpx 0;
}
.ball {
width: 200rpx;
height: 200rpx;
background: #4cd964;
border-radius: 50%;
}
</style>

325
pages/API/login/login.vue Normal file
View File

@ -0,0 +1,325 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view style="background:#FFF; padding:40rpx;">
<block v-if="hasLogin === true">
<view class="uni-h3 uni-center uni-common-mt">已登录
<text v-if="isUniverifyLogin" style="font-size: 0.8em;">
<i v-if="!phoneNumber.length" class="uni-icon_toast uni-loading"></i>
<i v-else>{{phoneNumber}}</i>
</text>
</view>
<view class="uni-hello-text uni-center">
<text>每个账号仅需登录 1 \n后续每次进入页面即可自动拉取用户信息</text>
</view>
</block>
<block v-if="hasLogin === false">
<view class="uni-h3 uni-center uni-common-mt">未登录</view>
<view class="uni-hello-text uni-center">
请点击按钮登录
</view>
</block>
</view>
<view class="uni-btn-v uni- uni-common-mt">
<!-- #ifdef MP-TOUTIAO -->
<button type="primary" class="page-body-button" v-for="(value,key) in providerList" @click="tologin(value)" :key="key">
登录
</button>
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO -->
<button type="primary" class="page-body-button" v-for="(value,key) in providerList" @click="tologin(value)"
:loading="value.id === 'univerify' ? univerifyBtnLoading : false" :key="key">{{value.name}}</button>
<!-- #endif -->
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations,
mapActions
} from 'vuex'
const univerifyInfoKey = 'univerifyInfo';
export default {
data() {
return {
title: 'login',
providerList: [],
phoneNumber: '',
univerifyBtnLoading: false
}
},
computed: {
...mapState(['hasLogin', 'isUniverifyLogin', 'univerifyErrorMsg'])
},
onLoad() {
uni.getProvider({
service: 'oauth',
success: (result) => {
this.providerList = result.provider.map((value) => {
let providerName = '';
switch (value) {
case 'weixin':
providerName = '微信登录'
break;
case 'qq':
providerName = 'QQ登录'
break;
case 'sinaweibo':
providerName = '新浪微博登录'
break;
case 'xiaomi':
providerName = '小米登录'
break;
case 'alipay':
providerName = '支付宝登录'
break;
case 'baidu':
providerName = '百度登录'
break;
case 'jd':
providerName = '京东登录'
break;
case 'toutiao':
providerName = '头条登录'
break;
case 'apple':
providerName = '苹果登录'
break;
case 'univerify':
providerName = '一键登录'
break;
case 'huawei':
providerName = '华为登录'
break;
}
return {
name: providerName,
id: value
}
});
},
fail: (error) => {
console.log('获取登录通道失败', error);
}
});
if (this.hasLogin && this.isUniverifyLogin) {
this.getPhoneNumber(uni.getStorageSync(univerifyInfoKey)).then((phoneNumber) => {
this.phoneNumber = phoneNumber
})
}
},
methods: {
...mapMutations(['login', 'setUniverifyLogin']),
...mapActions(['getPhoneNumber']),
Toast(data, duration = 1000) {
uni.showToast(Object.assign({}, data, {
duration
}))
},
tologin(provider) {
if (provider.id === 'univerify') {
this.univerifyBtnLoading = true;
}
// APP onLaunch
uni.login({
provider: provider.id,
// #ifdef MP-ALIPAY
scopes: 'auth_user', //
// #endif
success: async (res) => {
console.log('login success:', res);
this.Toast({
title: '登录成功'
})
// store
this.login(provider.id);
// #ifdef APP-PLUS
this.setUniverifyLogin(provider.id === 'univerify')
switch (provider.id) {
case 'univerify':
this.loginByUniverify(provider.id, res)
break;
case 'apple':
this.loginByApple(provider.id, res)
break;
}
// #endif
// #ifdef MP-WEIXIN
console.warn('如需获取openid请参考uni-id: https://uniapp.dcloud.net.cn/uniCloud/uni-id')
uni.request({
url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/user-center',
method: 'POST',
data: {
action: 'loginByWeixin',
params: {
code: res.code,
platform: 'mp-weixin'
}
},
success(res) {
console.log(res);
if (res.data.code !== 0) {
console.log('获取openid失败', res.data.errMsg);
return
}
uni.setStorageSync('openid', res.data.openid)
},
fail(err) {
console.log('获取openid失败', err);
}
})
// #endif
},
fail: (err) => {
console.log('login fail:', err);
//
if (err.code == '30002') {
uni.closeAuthView();
this.Toast({
title: '其他登录方式'
})
return;
}
//
if (err.code == 1000) {
uni.showModal({
title: '登录失败',
content: `${err.errMsg}\n错误码${err.code}`,
confirmText: '开通指南',
cancelText: '确定',
success: (res) => {
if (res.confirm) {
setTimeout(() => {
plus.runtime.openWeb('https://ask.dcloud.net.cn/article/37965')
}, 500)
}
}
});
return;
}
//
if (err.code == '30005') {
uni.showModal({
showCancel: false,
title: '预登录失败',
content: this.univerifyErrorMsg || err.errMsg
});
return;
}
//
if (err.code != '30003') {
uni.showModal({
showCancel: false,
title: '登录失败',
content: JSON.stringify(err)
});
}
},
complete: () => {
this.univerifyBtnLoading = false;
}
});
},
loginByUniverify(provider, res) {
this.setUniverifyLogin(true);
uni.closeAuthView();
const univerifyInfo = {
provider,
...res.authResult,
}
this.getPhoneNumber(univerifyInfo).then((phoneNumber) => {
this.phoneNumber = phoneNumber;
uni.setStorageSync(univerifyInfoKey, univerifyInfo)
}).catch(err => {
uni.showModal({
showCancel: false,
title: '手机号获取失败',
content: `${err.errMsg}\n错误码${err.code}`
})
console.error(res);
})
},
async loginByApple(provider, res) {
//
let getUserInfoErr, result
// #ifndef VUE3
[getUserInfoErr, result] = await uni.getUserInfo({
provider
});
// #endif
// #ifdef VUE3
try {
result = await uni.getUserInfo({
provider
});
} catch(e) {
getUserInfoErr = e
}
// #endif
if (getUserInfoErr) {
let content = getUserInfoErr.errMsg;
if (~content.indexOf('uni.login')) {
content = '请在登录页面完成登录操作';
}
uni.showModal({
title: '获取用户信息失败',
content: '错误原因' + content,
showCancel: false
});
}
// uni-id
console.warn('此处使用uni-id处理苹果登录详情参考: https://uniapp.dcloud.net.cn/uniCloud/uni-id')
uni.request({
url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/user-center',
method: 'POST',
data: {
action: 'loginByApple',
params: result.userInfo
},
success: (res) => {
console.log('uniId login success', res);
if(res.data.code !== 0){
uni.showModal({
showCancel: false,
content: `苹果登录失败: ${JSON.stringify(res.data.msg)}`,
})
} else {
uni.setStorageSync('openid', res.data.openid)
uni.setStorageSync('apple_nickname', res.data.userInfo.nickname)
}
},
fail: (e) => {
uni.showModal({
content: `苹果登录失败: ${JSON.stringify(e)}`,
showCancel: false
})
}
})
}
}
}
</script>
<style>
button {
background-color: #007aff;
color: #ffffff;
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-hello-text uni-center">请在下方输入电话号码</view>
<input class="input uni-common-mt" type="number" name="input" @input="bindInput" />
<view class="uni-btn-v uni-common-mt">
<button @tap="makePhoneCall" type="primary" :disabled="disabled">拨打</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'makePhoneCall',
disabled: true
}
},
methods: {
bindInput: function (e) {
this.inputValue = e.detail.value
if (this.inputValue.length > 0) {
this.disabled = false
} else {
this.disabled = true
}
},
makePhoneCall: function () {
uni.makePhoneCall({
phoneNumber: this.inputValue,
success: () => {
console.log("成功拨打电话")
}
})
}
}
}
</script>
<style>
.input {
height: 119rpx;
line-height: 119rpx;
font-size: 78rpx;
border-bottom: 1rpx solid #E2E2E2;
text-align:center;
}
</style>

View File

@ -0,0 +1,102 @@
<template>
<view class="content">
<map class="map" ref="dcmap" :markers="markers" @tap="selectPoint"></map>
<scroll-view class="scrollview" scroll-y="true">
<button class="button" @click="reverseGeocode">reverseGeocode</button>
<button class="button" @click="poiSearchNearBy">poiSearchNearBy</button>
</scroll-view>
</view>
</template>
<script>
// 116.397477,39.908692
let mapSearch = weex.requireModule('mapSearch')
export default {
data() {
return {
markers: [{
id: '1',
latitude: 39.9086920000,
longitude: 116.3974770000,
title: '天安门',
zIndex: '1',
iconPath: '/static/gps.png',
width: 20,
height: 20,
anchor: {
x: 0.5,
y: 1
},
callout: {
content: '首都北京\n天安门',
color: '#00BFFF',
fontSize: 12,
borderRadius: 2,
borderWidth: 0,
borderColor: '#333300',
bgColor: '#CCFF11',
padding: '1',
display: 'ALWAYS'
}
}]
}
},
methods: {
selectPoint(e) {
console.log(e);
},
reverseGeocode() {
var point = this.markers[0]
mapSearch.reverseGeocode({
point: {
latitude: point.latitude,
longitude: point.longitude
}
}, ret => {
console.log(JSON.stringify(ret));
uni.showModal({
content: JSON.stringify(ret)
})
})
},
poiSearchNearBy() {
var point = this.markers[0]
mapSearch.poiSearchNearBy({
point: {
latitude: point.latitude,
longitude: point.longitude
},
key: '停车场',
radius: 1000
}, ret => {
console.log(ret);
uni.showModal({
content: JSON.stringify(ret)
})
})
}
}
}
</script>
<style>
.content {
flex: 1;
}
.map {
width: 750rpx;
height: 500rpx;
background-color: black;
}
.scrollview {
flex: 1;
}
.button {
margin-top: 30rpx;
margin-bottom: 20rpx;
}
</style>

447
pages/API/map/map.nvue Normal file
View File

@ -0,0 +1,447 @@
<template>
<view class="content">
<map class="map" id="map1" ref="map1" :controls="controls" :scale="scale" :longitude="location.longitude"
:latitude="location.latitude" :show-location="showLocation" :enable-3D="enable3D" :rotate="rotate" :skew="skew"
:show-compass="showCompass" :enable-overlooking="enableOverlooking" :enable-zoom="enableZoom"
:enable-scroll="enableScroll" :enable-rotate="enableRotate" :enable-satellite="enableSatellite"
:enable-traffic="enableTraffic" :markers="markers" :polyline="polyline" :circles="circles" :polygons="polygons"
:include-points="includePoints" @tap="maptap" @controltap="oncontroltap" @markertap="onmarkertap"
@callouttap="oncallouttap" @poitap="onpoitap" @updated="onupdated" @regionchange="onregionchange"></map>
<scroll-view class="scrollview" scroll-y="true">
<!-- <view class="list-item">
<text class="list-text">显示3D楼块</text>
<switch :checked="enable3D" @change="enableThreeD" />
</view>
<view class="list-item">
<text class="list-text">显示指南针</text>
<switch :checked="showCompass" @change="changeShowCompass" />
</view>
<view class="list-item">
<text class="list-text">开启俯视</text>
<switch :checked="enableOverlooking" @change="changeEnableOverlooking" />
</view>
<view class="list-item">
<text class="list-text">是否支持缩放</text>
<switch :checked="enableZoom" @change="changeEnableZoom" />
</view>
<view class="list-item">
<text class="list-text">是否支持拖动</text>
<switch :checked="enableScroll" @change="changeEnableScroll" />
</view>
<view class="list-item">
<text class="list-text">是否支持旋转</text>
<switch :checked="enableRotate" @change="changeEnableRotate" />
</view>
<view class="list-item">
<text class="list-text">是否开启卫星图</text>
<switch :checked="enableSatellite" @change="changeEnableSatellite" />
</view>
<view class="list-item">
<text class="list-text">是否开启实时路况</text>
<switch :checked="enableTraffic" @change="changeEnableTraffic" />
</view> -->
<!-- #ifndef MP-JD -->
<button class="button" @click="changeScale">changeScale</button>
<!-- #endif -->
<!-- #ifndef H5 || APP || MP-JD || MP-KUAISHOU -->
<button class="button" @click="changeRotate">changeRotate</button>
<button class="button" @click="changeSkew">changeSkew</button>
<!-- #endif -->
<button class="button" @click="addMarkers">addMarkers</button>
<button class="button" @click="addPolyline">addPolyline</button>
<!-- #ifndef MP-JD -->
<button class="button" @click="addPolygons">addPolygons</button>
<!-- #endif -->
<button class="button" @click="addCircles">addCircles</button>
<button class="button" @click="includePoint">includePoints</button>
<button class="button" @click="handleGetCenterLocation">getCenterLocation</button>
<button class="button" @click="handleGetRegion">getRegion</button>
<!-- #ifndef MP-JD -->
<button class="button" @click="handleTranslateMarker">translateMarker</button>
<!-- #endif -->
</scroll-view>
</view>
</template>
<script>
const testMarkers = [{
id: 0,
latitude: 39.989631,
longitude: 116.481018,
title: '方恒国际 阜通东大街6号',
zIndex: '1',
rotate: 0,
width: 20,
height: 20,
anchor: {
x: 0.5,
y: 1
},
callout: {
content: '方恒国际 阜通东大街6号',
color: '#00BFFF',
fontSize: 10,
borderRadius: 4,
borderWidth: 1,
borderColor: '#333300',
bgColor: '#CCFF99',
padding: '5',
display: 'ALWAYS'
}
},
{
id: 1,
latitude: 39.9086920000,
longitude: 116.3974770000,
title: '天安门',
zIndex: '1',
iconPath: '/static/location.png',
width: 40,
height: 40,
anchor: {
x: 0.5,
y: 1
},
callout: {
content: '首都北京\n天安门',
color: '#00BFFF',
fontSize: 12,
borderRadius: 2,
borderWidth: 0,
borderColor: '#333300',
bgColor: '#CCFF11',
padding: '1',
display: 'ALWAYS'
}
}
];
const testPolyline = [{
points: [{
latitude: 39.925539,
longitude: 116.279037
},
{
latitude: 39.925539,
longitude: 116.520285
}
],
color: '#FFCCFF',
width: 7,
dottedLine: true,
arrowLine: true,
borderColor: '#000000',
borderWidth: 2
},
{
points: [{
latitude: 39.938698,
longitude: 116.275177
},
{
latitude: 39.966069,
longitude: 116.289253
},
{
latitude: 39.944226,
longitude: 116.306076
},
{
latitude: 39.966069,
longitude: 116.322899
},
{
latitude: 39.938698,
longitude: 116.336975
}
],
color: '#CCFFFF',
width: 5,
dottedLine: true,
arrowLine: true,
borderColor: '#CC0000',
borderWidth: 3
}
];
const testPolygons = [{
points: [{
latitude: 39.781892,
longitude: 116.293413
},
{
latitude: 39.787600,
longitude: 116.391842
},
{
latitude: 39.733187,
longitude: 116.417932
},
{
latitude: 39.704653,
longitude: 116.338255
}
],
fillColor: '#FFCCFF',
strokeWidth: 3,
strokeColor: '#CC99CC',
zIndex: 11
},
{
points: [{
latitude: 39.887600,
longitude: 116.518932
},
{
latitude: 39.781892,
longitude: 116.518932
},
{
latitude: 39.781892,
longitude: 116.428932
},
{
latitude: 39.887600,
longitude: 116.428932
}
],
fillColor: '#CCFFFF',
strokeWidth: 5,
strokeColor: '#CC0000',
zIndex: 3
}
];
const testCircles = [{
latitude: 39.996441,
longitude: 116.411146,
radius: 15000,
strokeWidth: 5,
color: '#CCFFFF',
fillColor: '#CC0000'
},
{
latitude: 40.096441,
longitude: 116.511146,
radius: 12000,
strokeWidth: 3,
color: '#CCFFFF',
fillColor: '#FFCCFF'
},
{
latitude: 39.896441,
longitude: 116.311146,
radius: 9000,
strokeWidth: 1,
color: '#CCFFFF',
fillColor: '#CC0000'
}
];
const testIncludePoints = [{
latitude: 39.989631,
longitude: 116.481018,
},
{
latitude: 39.9086920000,
longitude: 116.3974770000,
}
];
export default {
data() {
return {
location: {
longitude: 116.3974770000,
latitude: 39.9086920000
},
controls: [{
id: 1,
position: {
left: 5,
top: 180,
width: 30,
height: 30
},
iconPath: '/static/logo.png',
clickable: true
}],
showLocation: false,
scale: 13,
showCompass: true,
enable3D: true,
enableOverlooking: true,
enableZoom: true,
enableScroll: true,
enableRotate: true,
enableSatellite: false,
enableTraffic: false,
polyline: [],
markers: [],
polygons: [],
circles: [],
includePoints: [],
rotate: 0,
skew: 0
}
},
onLoad() {},
onReady() {
this.map = uni.createMapContext("map1", this);
},
methods: {
// #ifndef MP-JD
changeScale() {
this.scale = this.scale == 9 ? 15 : 9;
},
changeRotate() {
this.rotate = this.rotate == 90 ? 0 : 90;
},
changeSkew() {
this.skew = this.skew == 30 ? 0 : 30;
},
// #endif
enableThreeD(e) {
this.enable3D = e.detail.value;
},
changeShowCompass(e) {
this.showCompass = e.detail.value;
},
changeEnableOverlooking(e) {
this.enableOverlooking = e.detail.value;
},
changeEnableZoom(e) {
this.enableZoom = e.detail.value;
},
changeEnableScroll(e) {
this.enableScroll = e.detail.value;
},
changeEnableRotate(e) {
this.enableRotate = e.detail.value;
},
changeEnableSatellite(e) {
this.enableSatellite = e.detail.value;
},
changeEnableTraffic(e) {
this.enableTraffic = e.detail.value;
},
addMarkers() {
this.markers = testMarkers;
},
addPolyline() {
this.polyline = testPolyline;
},
// #ifndef MP-JD
addPolygons() {
this.polygons = testPolygons;
},
// #endif
addCircles() {
this.circles = testCircles;
},
includePoint() {
this.includePoints = testIncludePoints;
},
handleGetCenterLocation() {
this.map.getCenterLocation({
success: ret => {
console.log(JSON.stringify(ret));
uni.showModal({
content: JSON.stringify(ret)
})
}
})
},
handleGetRegion() {
this.map.getRegion({
success: ret => {
console.log(JSON.stringify(ret));
uni.showModal({
content: JSON.stringify(ret)
})
}
})
},
// #ifndef MP-JD
handleTranslateMarker() {
this.map.translateMarker({
markerId: 1,
destination: {
latitude: 39.989631,
longitude: 116.481018
},
duration: 2000
}, ret => {
console.log(JSON.stringify(ret));
uni.showModal({
content: JSON.stringify(ret)
})
});
},
// #endif
maptap(e) {
uni.showModal({
content: JSON.stringify(e)
})
},
onmarkertap(e) {
uni.showModal({
content: JSON.stringify(e)
})
},
oncontroltap(e) {
uni.showModal({
content: JSON.stringify(e)
})
},
oncallouttap(e) {
uni.showModal({
content: JSON.stringify(e)
})
},
onupdated(e) {
console.log(JSON.stringify(e))
},
onregionchange(e) {
console.log(JSON.stringify(e));
},
onpoitap(e) {
uni.showModal({
content: JSON.stringify(e)
})
}
}
}
</script>
<style>
.content {
flex: 1;
}
.map {
width: 750rpx;
/* #ifdef H5 */
width: 100%;
/* #endif */
height: 350px;
background-color: #f0f0f0;
}
.scrollview {
flex: 1;
padding: 10px;
}
.list-item {
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
padding: 5px 0px;
}
.list-text {
flex: 1;
}
.button {
margin-top: 5px;
margin-bottom: 5px;
}
</style>

40
pages/API/modal/modal.vue Normal file
View File

@ -0,0 +1,40 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-btn-v">
<button type="default" @tap="modalTap">有标题的modal</button>
<button type="default" @tap="noTitlemodalTap">无标题的modal</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'modal',
modalHidden: true,
modalHidden2: true
}
},
methods: {
modalTap: function (e) {
uni.showModal({
title: "弹窗标题",
content: "弹窗内容,告知当前状态、信息和解决方法,描述文字尽量控制在三行内",
showCancel: false,
confirmText: "确定"
})
},
noTitlemodalTap: function (e) {
uni.showModal({
content: "弹窗内容,告知当前状态、信息和解决方法,描述文字尽量控制在三行内",
confirmText: "确定",
cancelText: "取消"
})
}
}
}
</script>

View File

@ -0,0 +1,106 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-btn-v">
<button @tap="navigateTo">跳转新页面并传递数据</button>
<button @tap="navigateBack">返回上一页</button>
<button @tap="redirectTo">在当前页面打开</button>
<button @tap="switchTab">切换到模板选项卡</button>
<button v-if="!hasLeftWin" @tap="reLaunch">关闭所有页面打开首页</button>
<!-- #ifdef APP-PLUS -->
<button @tap="customAnimation">使用自定义动画打开页面</button>
<!-- #endif -->
<!-- #ifdef APP-PLUS || H5 -->
<button @tap="preloadPage">预载复杂页面</button>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<button @tap="unPreloadPage">取消页面预载</button>
<!-- #endif -->
<!-- #ifdef APP-PLUS || H5 -->
<button @tap="navigateToPreloadPage">打开复杂页面</button>
<!-- #endif -->
</view>
</view>
</view>
</template>
<script>
const preloadPageUrl = '/pages/extUI/calendar/calendar'
import { mapState } from 'vuex'
export default {
data() {
return {
title: 'navigate'
}
},
computed: {
...mapState({
hasLeftWin: state => !state.noMatchLeftWindow
})
},
methods: {
navigateTo() {
uni.navigateTo({
url: 'new-page/new-vue-page-1?data=Hello'
})
},
navigateBack() {
uni.navigateBack();
},
redirectTo() {
uni.redirectTo({
url: 'new-page/new-vue-page-1'
});
},
switchTab() {
uni.switchTab({
url: '/pages/tabBar/template/template'
});
},
reLaunch() {
if (this.hasLeftWin) {
uni.reLaunch({
url: '/pages/component/view/view'
});
return;
}
uni.reLaunch({
url: '/pages/tabBar/component/component'
});
},
customAnimation(){
uni.navigateTo({
url: 'new-page/new-vue-page-1?data=使用自定义动画打开页面',
animationType: 'slide-in-bottom',
animationDuration: 200
})
},
preloadPage(){
uni.preloadPage({
url: preloadPageUrl,
success(){
uni.showToast({
title:'页面预载成功'
})
},
fail(e){
console.error(e);
uni.showToast({
title:'页面预载失败'
})
}
})
},
unPreloadPage(){
uni.unPreloadPage({
url: preloadPageUrl
})
},
navigateToPreloadPage(){
uni.navigateTo({
url: preloadPageUrl
})
}
}
}
</script>

View File

@ -0,0 +1,83 @@
<template>
<view class="root">
<view class="page-body">
<view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
<text class="new-page__color-text">点击改变颜色</text>
</view>
<view class="new-page__text-box">
<text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
</view>
<view class="new-page__button">
<button class="new-page__button-item" @click="navToNvue">跳转NVUE页面</button>
<button class="new-page__button-item" @click="navToVue">跳转VUE页面</button>
</view>
</view>
</view>
</template>
<script>
import {mapState,mapGetters,mapMutations} from 'vuex'
export default {
data() {
return {
}
},
computed:{
...mapState(['colorIndex','colorList']),
...mapGetters(['currentColor'])
},
methods:{
...mapMutations(['setColorIndex']),
navToNvue(){
uni.navigateTo({
url:'new-nvue-page-2'
})
},
navToVue(){
uni.navigateTo({
url:'new-vue-page-2'
})
}
}
}
</script>
<style>
.new-page__text {
font-size: 14px;
color: #666666;
}
.root{
flex-direction: column;
}
.page-body{
flex: 1;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding-top: 50px;
}
.new-page__text-box{
padding: 20px;
}
.new-page__color{
width: 200px;
height: 100px;
justify-content: center;
align-items: center;
}
.new-page__color-text{
font-size: 14px;
color: #FFFFFF;
line-height: 30px;
text-align: center;
}
.new-page__button-item{
margin-top: 15px;
width: 300px;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<view class="root">
<view class="page-body">
<view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
<text class="new-page__color-text">点击改变颜色</text>
</view>
<view class="new-page__text-box">
<text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
</view>
</view>
</view>
</template>
<script>
import {mapState,mapGetters,mapMutations} from 'vuex'
export default {
data() {
return {
}
},
computed:{
...mapState(['colorIndex','colorList']),
...mapGetters(['currentColor'])
},
methods:{
...mapMutations(['setColorIndex'])
}
}
</script>
<style>
.new-page__text {
font-size: 14px;
color: #666666;
}
.root{
flex-direction: column;
}
.page-body{
flex: 1;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding-top: 50px;
}
.new-page__text-box{
padding: 20px;
}
.new-page__color{
width: 200px;
height: 100px;
justify-content: center;
align-items: center;
}
.new-page__color-text{
font-size: 14px;
color: #FFFFFF;
line-height: 30px;
text-align: center;
}
.new-page__button-item{
margin-top: 15px;
width: 300px;
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<view class="root">
<page-head :title="title"></page-head>
<view class="page-body">
<view class="new-page__text-box">
<text class="new-page__text">从上个页面接收到参数{{data}}</text>
</view>
<view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
<text class="new-page__color-text">点击改变颜色</text>
</view>
<view class="new-page__text-box">
<text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
</view>
<view class="new-page__button">
<!-- #ifndef VUE3-->
<button class="new-page__button-item" @click="navToNvue">跳转NVUE页面</button>
<!-- #endif -->
<button class="new-page__button-item" @click="navToVue">跳转VUE页面</button>
</view>
</view>
</view>
</template>
<script>
import {mapState,mapGetters,mapMutations} from 'vuex'
export default {
data() {
return {
title: '新页面',
data:""
}
},
computed:{
...mapState(['colorIndex','colorList']),
...mapGetters(['currentColor'])
},
onLoad(e){
if(e.data){
this.data = e.data;
}
uni.$on('postMsg',(res)=>{
uni.showModal({
content: `收到uni.$emit消息:${res.msg}`,
showCancel: false
})
})
},
onUnload() {
uni.$off('postMsg')
},
methods:{
...mapMutations(['setColorIndex']),
navToNvue(){
uni.navigateTo({
url:'new-nvue-page-1'
})
},
navToVue(){
uni.navigateTo({
url:'new-vue-page-2'
})
}
}
}
</script>
<style>
.new-page__text {
font-size: 14px;
color: #666666;
}
.root{
display: flex;
flex: 1;
flex-direction: column;
}
.page-body{
/* flex: 1; */
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.new-page__text-box{
padding: 20px;
}
.new-page__color{
display: flex;
width: 200px;
height: 100px;
justify-content: center;
align-items: center;
}
.new-page__color-text{
font-size: 14px;
color: #FFFFFF;
line-height: 30px;
text-align: center;
}
.new-page__button-item{
margin-top: 15px;
width: 300px;
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<view class="root">
<view class="page-body">
<view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
<text class="new-page__color-text">点击改变颜色</text>
</view>
<view class="new-page__text-box">
<text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
</view>
<view class="new-page__button">
<button class="new-page__button-item" @click="emitMsg">向上一页面传递数据</button>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapGetters,
mapMutations
} from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState(['colorIndex', 'colorList']),
...mapGetters(['currentColor'])
},
methods: {
...mapMutations(['setColorIndex']),
emitMsg() {
uni.$emit('postMsg', {
msg: 'From: Vue Page'
})
}
}
}
</script>
<style>
.new-page__text {
font-size: 14px;
color: #666666;
}
.root {
display: flex;
flex: 1;
flex-direction: column;
}
.page-body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding-top: 50px;
}
.new-page__text-box {
padding: 20px;
}
.new-page__color {
display: flex;
width: 200px;
height: 100px;
justify-content: center;
align-items: center;
}
.new-page__color-text {
font-size: 14px;
color: #FFFFFF;
line-height: 30px;
text-align: center;
}
.new-page__button-item {
margin-top: 15px;
width: 300px;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<!-- #ifdef APP-PLUS -->
<view class="uni-btn-v">
<button class="shake" @tap="shake">摇一摇</button>
</view>
<!-- #endif -->
<view class="uni-btn-v">
<button type="primary" @tap="watchAcce">监听设备的加速度变化</button>
<button type="primary" @tap="stopAcce">停止监听设备的加速度变化</button>
</view>
<view class="uni-textarea uni-common-mt">
<textarea class="acc-show" :value="value" />
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'onAccelerometerChange',
value: ''
}
},
onUnload() {
uni.stopAccelerometer();
},
methods: {
//#ifdef APP-PLUS
shake() {
uni.navigateTo({
url: '/platforms/app-plus/shake/shake'
})
},
//#endif
watchAcce() {
uni.onAccelerometerChange((res) => {
this.value = "监听设备的加速度变化:\n" + "X轴" + res.x.toFixed(2) + "\nY轴" + res.y.toFixed(2) +
"\nZ轴" + res.z.toFixed(2);
})
},
stopAcce() {
uni.stopAccelerometer()
}
}
}
</script>
<style>
.shake {
background-color: #FFCC33;
color: #ffffff;
margin-bottom: 50rpx;
}
.uni-textarea .acc-show{
height: 240rpx;
}
</style>

View File

@ -0,0 +1,91 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-hello-text uni-center" style="padding-bottom:50rpx;">
旋转手机即可获取方位信息
</view>
<view class="direction">
<view class="bg-compass-line"></view>
<image class="bg-compass" src="../../../static/compass.png" :style="'transform: rotate('+direction+'deg)'"></image>
<view class="direction-value">
<text>{{direction}}</text>
<text class="direction-degree">o</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'onCompassChange',
direction: 0
}
},
onReady: function () {
uni.onCompassChange((res) => {
this.direction = parseInt(res.direction)
})
},
onUnload() {
// #ifndef MP-ALIPAY
uni.stopCompass();
this.direction = 0;
// #endif
// #ifdef MP-ALIPAY
uni.offCompassChange();
// #endif
}
}
</script>
<style>
.direction {
position: relative;
margin-top: 70rpx;
display: flex;
width: 540rpx;
height: 540rpx;
align-items: center;
justify-content: center;
margin:0 auto;
}
.direction-value {
position: relative;
font-size: 200rpx;
color: #353535;
line-height: 1;
z-index: 1;
}
.direction-degree {
position: absolute;
top: 0;
right: -40rpx;
font-size: 60rpx;
}
.bg-compass {
position: absolute;
top: 0;
left: 0;
width: 540rpx;
height: 540rpx;
transition: .1s;
}
.bg-compass-line {
position: absolute;
left: 267rpx;
top: -10rpx;
width: 6rpx;
height: 56rpx;
background-color: #1AAD19;
border-radius: 999rpx;
z-index: 1;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-common-mt">
<form @submit="openLocation">
<view class="uni-list">
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">经度</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" value="116.39747" name="longitude"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">纬度</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" value="39.9085" name="latitude"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">位置名称</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" value="天安门" name="name"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">详细位置</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" :disabled="true" value="北京市东城区东长安街" name="address"/>
</view>
</view>
</view>
<view class="uni-padding-wrap">
<view class="uni-btn-v uni-common-mt">
<button type="primary" formType="submit">查看位置</button>
</view>
</view>
</form>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'openLocation'
}
},
methods: {
openLocation: function (e) {
console.log(e)
var value = e.detail.value
uni.openLocation({
longitude: Number(value.longitude),
latitude: Number(value.latitude),
name: value.name,
address: value.address
})
}
}
}
</script>
<style>
.uni-list-cell-left {
padding: 0 30rpx;
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view style="font-size: 12px; color: #666;">PC 不支持下拉刷新</view>
<view class="text" v-for="(num,index) in data" :key="index">list - {{num}}</view>
<view class="uni-loadmore" v-if="showLoadMore">{{loadMoreText}}</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: '下拉刷新 + 加载更多',
data: [],
loadMoreText: "加载中...",
showLoadMore: false,
max: 0
}
},
onLoad() {
this.initData();
},
onUnload() {
this.max = 0,
this.data = [],
this.loadMoreText = "加载更多",
this.showLoadMore = false;
},
onReachBottom() {
console.log("onReachBottom");
if (this.max > 40) {
this.loadMoreText = "没有更多数据了!"
return;
}
this.showLoadMore = true;
setTimeout(() => {
this.setListData();
}, 300);
},
onPullDownRefresh() {
console.log('onPullDownRefresh');
this.initData();
},
methods: {
initData(){
setTimeout(() => {
this.max = 0;
this.data = [];
let data = [];
this.max += 20;
for (var i = this.max - 19; i < this.max + 1; i++) {
data.push(i)
}
this.data = this.data.concat(data);
uni.stopPullDownRefresh();
}, 300);
},
setListData() {
let data = [];
this.max += 10;
for (var i = this.max - 9; i < this.max + 1; i++) {
data.push(i)
}
this.data = this.data.concat(data);
}
}
}
</script>
<style>
.text {
margin: 16rpx 0;
width:100%;
background-color: #fff;
height: 120rpx;
line-height: 120rpx;
text-align: center;
color: #555;
border-radius: 8rpx;
}
</style>

View File

@ -0,0 +1,263 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view style="background:#FFF; padding:50rpx 0;">
<view class="uni-hello-text uni-center"><text>支付金额</text></view>
<view class="uni-h1 uni-center uni-common-mt">
<text class="rmbLogo"></text>
<input class="price" type="digit" :value="price" maxlength="4" @input="priceChange" />
</view>
</view>
<view class="uni-btn-v uni-common-mt">
<!-- #ifdef MP-WEIXIN -->
<button type="primary" @click="weixinPay" :loading="loading">微信支付</button>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<template v-if="providerList.length > 0">
<button v-for="(item,index) in providerList" :key="index" @click="requestPayment(item,index)" :loading="item.loading">{{item.name}}支付</button>
</template>
<!-- #endif -->
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'request-payment',
loading: false,
price: 1,
providerList: []
}
},
onLoad: function() {
// #ifdef APP-PLUS
uni.getProvider({
service: "payment",
success: (e) => {
console.log("payment success:" + JSON.stringify(e));
let providerList = [];
e.provider.map((value) => {
switch (value) {
case 'alipay':
providerList.push({
name: '支付宝',
id: value,
loading: false
});
break;
case 'wxpay':
providerList.push({
name: '微信',
id: value,
loading: false
});
break;
case 'huawei':
providerList.push({
name: '华为',
id: value,
loading: false
});
break;
default:
break;
}
})
this.providerList = providerList;
},
fail: (e) => {
console.log("获取支付通道失败:", e);
}
});
// #endif
},
methods: {
loginMpWeixin() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success(res) {
console.warn('此处使用uni-id处理微信小程序登录详情参考: https://uniapp.dcloud.net.cn/uniCloud/uni-id')
uni.request({
url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/user-center',
method: 'POST',
data: {
action: 'loginByWeixin',
params: {
code: res.code,
platform: 'mp-weixin'
}
},
success(res) {
if (res.data.code !== 0) {
reject(new Error('获取openid失败', res.result.msg))
return
}
uni.setStorageSync('openid', res.data.openid)
resolve(res.data.openid)
},
fail(err) {
reject(new Error('获取openid失败' + err))
}
})
},
fail(err) {
reject(err)
}
})
})
},
async weixinPay() {
let openid = uni.getStorageSync('openid')
console.log("发起支付");
this.loading = true;
if (!openid) {
try {
openid = await this.loginMpWeixin()
} catch (e) {
console.error(e)
}
if (!openid) {
uni.showModal({
content: '获取openid失败',
showCancel: false
})
this.loading = false
return
}
}
this.openid = openid
let orderInfo = await this.getOrderInfo('wxpay')
if (!orderInfo) {
uni.showModal({
content: '获取支付信息失败',
showCancel: false
})
return
}
uni.requestPayment({
...orderInfo,
success: (res) => {
uni.showToast({
title: "感谢您的赞助!"
})
},
fail: (res) => {
uni.showModal({
content: "支付失败,原因为: " + res
.errMsg,
showCancel: false
})
},
complete: () => {
this.loading = false;
}
})
},
async requestPayment(e, index) {
this.providerList[index].loading = true;
const provider = e.id
let orderInfo = await this.getOrderInfo(provider);
if (!orderInfo) {
uni.showModal({
content: '获取支付信息失败',
showCancel: false
})
return
}
console.log('--------orderInfo--------')
console.log(orderInfo);
uni.requestPayment({
provider,
orderInfo: orderInfo,
success: (e) => {
console.log("success", e);
uni.showToast({
title: "感谢您的赞助!"
})
},
fail: (e) => {
console.log("fail", e);
uni.showModal({
content: "支付失败,原因为: " + e.errMsg,
showCancel: false
})
},
complete: () => {
this.providerList[index].loading = false;
}
})
},
getOrderInfo(provider) {
return new Promise((resolve, reject) => {
if (!this.price) {
reject(new Error('请输入金额'))
}
console.warn('此处使用uni-pay处理支付详情参考: https://uniapp.dcloud.io/uniCloud/unipay')
uni.request({
method: 'POST',
url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/pay',
data: {
provider,
openid: this.openid,
totalFee: Number(this.price) * 100, //
// #ifdef APP-PLUS
platform: 'app-plus',
// #endif
// #ifdef MP-WEIXIN
platform: 'mp-weixin',
// #endif
},
success(res) {
if (res.data.code === 0) {
resolve(res.data.orderInfo)
} else {
reject(new Error('获取支付信息失败' + res.data.msg))
}
},
fail(err) {
reject(new Error('请求支付接口失败' + err))
}
})
})
},
priceChange(e) {
console.log(e.detail.value)
this.price = e.detail.value;
}
}
}
</script>
<style>
.rmbLogo {
font-size: 40rpx;
}
button {
background-color: #007aff;
color: #ffffff;
}
.uni-h1.uni-center {
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-end;
}
.price {
border-bottom: 1px solid #eee;
width: 200rpx;
height: 80rpx;
padding-bottom: 4rpx;
}
.ipaPayBtn {
margin-top: 30rpx;
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-hello-text">
请点击按钮向服务器发起请求
</view>
<view class="uni-textarea uni-common-mt">
<textarea :value="res"></textarea>
</view>
<view class="uni-btn-v uni-common-mt">
<button type="primary" @click="sendRequest" :loading="loading">发起请求Callback</button>
<button type="primary" @click="sendRequest('promise')" :loading="loading">发起请求Promise</button>
<button type="primary" @click="sendRequest('await')" :loading="loading">发起请求Async/Await</button>
</view>
</view>
</view>
</template>
<script>
const requestUrl = 'https://unidemo.dcloud.net.cn/ajax/echo/text?name=uni-app'
const duration = 2000
export default {
data() {
return {
title: 'request',
loading: false,
res: ''
}
},
methods: {
sendRequest(mode) {
this.loading = true;
switch (mode) {
case 'promise':
this._requestPromise();
break;
case 'await':
this._requestAwait();
break;
default:
this._request();
break;
}
},
_request() {
uni.request({
url: requestUrl,
dataType: 'text',
data: {
noncestr: Date.now()
},
success: (res) => {
console.log('request success', res)
uni.showToast({
title: '请求成功',
icon: 'success',
mask: true,
duration: duration
});
this.res = '请求结果 : ' + JSON.stringify(res);
},
fail: (err) => {
console.log('request fail', err);
uni.showModal({
content: err.errMsg,
showCancel: false
});
},
complete: () => {
this.loading = false;
}
});
},
_requestPromise() {
// #ifndef VUE3
uni.request({
url: requestUrl,
dataType: 'text',
data: {
noncestr: Date.now()
}
}).then(res => {
console.log('request success', res[1]);
uni.showToast({
title: '请求成功',
icon: 'success',
mask: true,
duration: duration
});
this.res = '请求结果 : ' + JSON.stringify(res[1]);
this.loading = false;
}).catch(err => {
console.log('request fail', err);
uni.showModal({
content: err.errMsg,
showCancel: false
});
this.loading = false;
});
// #endif
// #ifdef VUE3
uni.request({
url: requestUrl,
dataType: 'text',
data: {
noncestr: Date.now()
}
}).then(res => {
console.log('request success', res);
uni.showToast({
title: '请求成功',
icon: 'success',
mask: true,
duration: duration
});
this.res = '请求结果 : ' + JSON.stringify(res);
this.loading = false;
}).catch(err => {
console.log('request fail', err);
uni.showModal({
content: err.errMsg,
showCancel: false
});
this.loading = false;
});
// #endif
},
async _requestAwait() {
let res, err
// #ifndef VUE3
[err, res] = await uni.request({
url: requestUrl,
dataType: 'text',
data: {
noncestr: Date.now()
}
});
// #endif
// #ifdef VUE3
try {
res = await uni.request({
url: requestUrl,
dataType: 'text',
data: {
noncestr: Date.now()
}
});
} catch(e){
err=e
}
// #endif
if (err) {
console.log('request fail', err);
uni.showModal({
content: err.errMsg,
showCancel: false
});
} else {
console.log('request success', res)
uni.showToast({
title: '请求成功',
icon: 'success',
mask: true,
duration: duration
});
this.res = '请求结果 : ' + JSON.stringify(res);
}
this.loading = false;
}
}
}
</script>

View File

@ -0,0 +1,109 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<button v-if="!loadError" :loading="loading" :disabled="loading" type="primary" class="btn" @click="show">显示广告</button>
<button v-if="loadError" :loading="loading" :disabled="loading" type="primary" class="btn" @click="reloadAd">重新加载广告</button>
</view>
<!-- #ifndef APP-PLUS -->
<view class="ad-tips">
<text>小程序端的广告ID由小程序平台提供</text>
</view>
<!-- #endif -->
</view>
</template>
<script>
const ERROR_CODE = [-5001, -5002, -5003, -5004, -5005, -5006];
export default {
data() {
return {
title: '激励视频广告',
loading: false,
loadError: false
}
},
onReady() {
// #ifdef APP-PLUS
this.adOption = {
adpid: '1507000689'
};
// #endif
// #ifdef MP-WEIXIN
this.adOption = {
adUnitId: ''
};
// #endif
this.createAd();
},
methods: {
createAd() {
var rewardedVideoAd = this.rewardedVideoAd = uni.createRewardedVideoAd(this.adOption);
rewardedVideoAd.onLoad(() => {
this.loading = false;
this.loadError = false;
console.log('onLoad event')
});
rewardedVideoAd.onClose((res) => {
this.loading = true;
// 广
if (res && res.isEnded) {
//
console.log("onClose " + res.isEnded);
} else {
// 退
console.log("onClose " + res.isEnded);
}
setTimeout(() => {
uni.showToast({
title: "激励视频" + (res.isEnded ? "成功" : "未") + "播放完毕",
duration: 10000,
position: 'bottom'
})
}, 500)
});
rewardedVideoAd.onError((err) => {
this.loading = false;
if (err.code && ERROR_CODE.indexOf(err.code) !== -1) {
this.loadError = true;
}
console.log('onError event', err)
});
this.loading = true;
},
show() {
const rewardedVideoAd = this.rewardedVideoAd;
rewardedVideoAd.show().catch(() => {
rewardedVideoAd.load()
.then(() => rewardedVideoAd.show())
.catch(err => {
console.log('激励视频 广告显示失败', err)
uni.showToast({
title: err.errMsg || err.message,
duration: 5000,
position: 'bottom'
})
})
})
},
reloadAd() {
this.loading = true;
this.rewardedVideoAd.load();
}
}
}
</script>
<style>
.btn {
margin-bottom: 20px;
}
.ad-tips {
color: #999;
padding: 30px 0;
text-align: center;
}
</style>

View File

@ -0,0 +1,158 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view v-if="imagePath !== ''" class="media-box image">
<image class="image" mode="widthFix" :src="imagePath" />
</view>
<button type="primary" class="uni-button" @click="saveImage">拍摄图片并保存到本地</button>
<view v-if="videoPath !== ''" class="media-box">
<video
id="myVideo"
:src="videoPath"
@error="videoErrorCallback"
enable-danmu
danmu-btn
controls
></video>
</view>
<button type="primary" class="uni-button" @click="saveVideo">录制视频并保存到本地</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'saveImage/saveVideo',
imagePath: '',
videoPath: ''
};
},
onLoad() {},
methods: {
videoErrorCallback: function() {
uni.showModal({
content: '视频加载失败',
showCancel: false
});
},
saveImage() {
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: res => {
this.imagePath = res.tempFilePaths[0];
this.getTempFilePath(res.tempFilePaths[0], 'imageTempPath');
},
fail: (err) => {
// #ifdef MP
uni.getSetting({
success: (res) => {
let authStatus = res.authSetting['scope.camera'];
if (!authStatus) {
uni.showModal({
title: '授权失败',
content: 'Hello uni-app需要从您的相机获取图片请在设置界面打开相关权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
}
}
})
// #endif
}
});
},
saveVideo() {
let _this = this;
uni.chooseVideo({
count: 1,
sourceType: ['camera'],
success: res => {
console.log(res.tempFilePath)
this.videoPath = res.tempFilePath;
this.getTempFilePath(res.tempFilePath, 'videoTempPath');
},
fail: (err) => {
// #ifdef MP
uni.getSetting({
success: (res) => {
let authStatus = res.authSetting['scope.camera'];
if (!authStatus) {
uni.showModal({
title: '授权失败',
content: 'Hello uni-app需要从您的相机获取视频请在设置界面打开相关权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
}
}
})
// #endif
}
});
},
getTempFilePath(url, types) {
//
let obj = {
filePath: url,
success: () => {
console.log('save success');
uni.showModal({
content: '保存成功',
showCancel: false
});
uni.hideLoading();
},
fail: e => {
uni.hideLoading();
uni.showModal({
content: '保存失败',
showCancel: false
});
}
};
uni.showLoading({
title: '保存中...'
});
if (types === 'videoTempPath') {
uni.saveVideoToPhotosAlbum(obj);
} else {
uni.saveImageToPhotosAlbum(obj);
}
}
}
};
</script>
<style>
.media-box {
display: flex;
justify-content: center;
align-items: center;
margin: 30rpx 0;
width: 100%;
}
.image {
height: 400rpx;
overflow: hidden;
}
.image image {
width: 100%;
height: 100%;
}
video {
width: 100%;
}
.uni-button {
margin: 30rpx 0;
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-title">扫码结果</view>
<view class="uni-list" v-if="result">
<view class="uni-cell">
<view class="scan-result">
{{result}}
</view>
</view>
</view>
<view class="uni-btn-v">
<button type="primary" @click="scan">扫一扫</button>
</view>
</view>
</view>
</template>
<script>
import permision from "@/common/permission.js"
export default {
data() {
return {
title: 'scanCode',
result: ''
}
},
methods: {
async scan() {
// #ifdef APP-PLUS
let status = await this.checkPermission();
if (status !== 1) {
return;
}
// #endif
uni.scanCode({
success: (res) => {
this.result = res.result
},
fail: (err) => {
//
}
});
}
// #ifdef APP-PLUS
,
async checkPermission(code) {
let status = permision.isIOS ? await permision.requestIOS('camera') :
await permision.requestAndroid('android.permission.CAMERA');
if (status === null || status === 1) {
status = 1;
} else {
uni.showModal({
content: "需要相机权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
return status;
}
// #endif
}
}
</script>
<style>
.scan-result {
min-height: 50rpx;
line-height: 50rpx;
}
</style>

View File

@ -0,0 +1,14 @@
describe('pages/API/set-navigation-bar-title/set-navigation-bar-title.vue', () => {
let page
beforeAll(async () => {
// 重新reLaunch至首页并获取首页page对象其中 program 是uni-automator自动注入的全局对象
page = await program.reLaunch('/pages/API/set-navigation-bar-title/set-navigation-bar-title')
const waitTime = process.env.UNI_PLATFORM === "mp-weixin" ? 10000 : 5000
await page.waitFor(waitTime)
page = await program.currentPage()
})
it('set-navigation-bar-title 组件标题', async () => {
let view = await page.$('.common-page-head-title')
expect(await view.text()).toBe('nav-default')
})
})

View File

@ -0,0 +1,44 @@
<template>
<view class="page">
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-helllo-text">
本页标题栏是uni-app的默认配置开发者可在pages.json里配置文字内容及标题颜色也可通过api接口将其改变
</view>
<view class="uni-btn-v">
<button type="default" @click="setText">改变标题栏文字</button>
<button type="primary" @click="setBg">改变标题栏颜色</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'nav-default',
hasSetText:false,
hasSetBg:false
}
},
methods: {
setText() {
this.hasSetText = !this.hasSetText;
uni.setNavigationBarTitle({
title: this.hasSetText ? "Hello uni-app" : "默认导航栏"
})
},
setBg() {
this.hasSetBg = !this.hasSetBg;
uni.setNavigationBarColor({
frontColor: this.hasSetBg ? "#000000" : "#ffffff",
backgroundColor: this.hasSetBg ? "#F8F8F8" : "#007AFF"
})
}
}
}
</script>
<style>
</style>

293
pages/API/share/share.vue Normal file
View File

@ -0,0 +1,293 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-title">分享内容</view>
<view class="uni-textarea">
<textarea class="textarea" v-model="shareText" />
</view>
<view class="uni-title">分享图片</view>
<view class="uni-uploader" style="padding:15rpx; background:#FFF;">
<view class="uni-uploader__input-box" v-if="!image" @tap="chooseImage"></view>
<image class="uni-uploader__img" v-if="image" :src="image"></image>
</view>
<!-- #ifdef APP-PLUS -->
<view class="uni-title">分享类型</view>
<view>
<radio-group @change="radioChange">
<label class="radio">
<radio value="1" checked="true"/>文字
</label>
<label class="radio">
<radio value="2" />图片
</label>
<label class="radio">
<radio value="0" />图文
</label>
<label class="radio">
<radio value="5" />小程序
</label>
</radio-group>
</view>
<view class="uni-btn-v uni-common-mt" v-if="providerList.length > 0">
<block v-for="(value,index) in providerList" :key="index">
<button type="primary" v-if="value" :disabled="isDisableButton(value)" @tap="share(value)">{{value.name}}</button>
</block>
</view>
<!-- #endif -->
<!-- #ifdef MP || QUICKAPP-WEBVIEW -->
<view class="uni-btn-v uni-common-mt">
<button type="primary" open-type="share">分享</button>
</view>
<!-- #endif -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'share',
shareText: 'uni-app可以同时发布成原生App、小程序、H5邀请你一起体验',
href:"https://uniapp.dcloud.io",
image: '',
shareType:1,
providerList: []
}
},
computed:{
isDisableButton() {
return function(item) {
if(this.shareType === 0 && item.id === 'qq'){
return true;
}
if(this.shareType === 5 && item.name !== '分享到微信好友'){
return true;
}
return false;
}
}
},
onShareAppMessage() {
return {
title: this.shareText ? this.shareText : "欢迎体验uni-app",
path: '/pages/tabBar/component/component',
imageUrl:this.image ? this.image : 'https://web-assets.dcloud.net.cn/unidoc/zh/share-logo@3.png'
}
},
onUnload:function(){
this.shareText='uni-app可以同时发布成原生App、小程序、H5邀请你一起体验',
this.href = 'https://uniapp.dcloud.io',
this.image='';
},
onLoad: function () {
uni.getProvider({
service: 'share',
success: (e) => {
console.log('success', e);
let data = []
for (let i = 0; i < e.provider.length; i++) {
switch (e.provider[i]) {
case 'weixin':
data.push({
name: '分享到微信好友',
id: 'weixin',
sort:0
})
data.push({
name: '分享到微信朋友圈',
id: 'weixin',
type:'WXSenceTimeline',
sort:1
})
break;
case 'sinaweibo':
data.push({
name: '分享到新浪微博',
id: 'sinaweibo',
sort:2
})
break;
case 'qq':
data.push({
name: '分享到QQ',
id: 'qq',
sort:3
})
break;
default:
break;
}
}
this.providerList = data.sort((x,y) => {
return x.sort - y.sort
});
},
fail: (e) => {
console.log('获取分享通道失败', e);
uni.showModal({
content:'获取分享通道失败',
showCancel:false
})
}
});
},
methods: {
async share(e) {
console.log('分享通道:'+ e.id +' 分享类型:' + this.shareType);
if(!this.shareText && (this.shareType === 1 || this.shareType === 0)){
uni.showModal({
content:'分享内容不能为空',
showCancel:false
})
return;
}
if(!this.image && (this.shareType === 2 || this.shareType === 0)){
uni.showModal({
content:'分享图片不能为空',
showCancel:false
})
return;
}
let shareOPtions = {
provider: e.id,
scene: e.type && e.type === 'WXSceneTimeline' ? 'WXSceneTimeline' : 'WXSceneSession', //WXSceneSessionWXSceneTimelineWXSceneFavorite
type: this.shareType,
success: (e) => {
console.log('success', e);
uni.showModal({
content: '已分享',
showCancel:false
})
},
fail: (e) => {
console.log('fail', e)
uni.showModal({
content: e.errMsg,
showCancel:false
})
},
complete:function(){
console.log('分享操作结束!')
}
}
switch (this.shareType){
case 0:
shareOPtions.summary = this.shareText;
shareOPtions.imageUrl = this.image;
shareOPtions.title = '欢迎体验uniapp';
shareOPtions.href = 'https://uniapp.dcloud.io';
break;
case 1:
shareOPtions.summary = this.shareText;
break;
case 2:
shareOPtions.imageUrl = this.image;
break;
case 5:
shareOPtions.imageUrl = this.image ? this.image : 'https://web-ext-storage.dcloud.net.cn/hello-uni-app/share.png'
shareOPtions.title = '欢迎体验uniapp';
shareOPtions.miniProgram = {
id:'gh_33446d7f7a26',
path:'/pages/tabBar/component/component',
webUrl:'https://uniapp.dcloud.io',
type:0
};
break;
default:
break;
}
if(shareOPtions.type === 0 && plus.os.name === 'iOS'){//ios
shareOPtions.imageUrl = await this.compress();
}
if(shareOPtions.type === 1 && shareOPtions.provider === 'qq'){//qqhreftitle
shareOPtions.href = 'https://uniapp.dcloud.io';
shareOPtions.title = '欢迎体验uniapp';
}
uni.share(shareOPtions);
},
radioChange(e){
console.log('type:' + e.detail.value);
this.shareType = parseInt(e.detail.value);
},
chooseImage() {
uni.chooseImage({
count: 1,
sourceType: ['album', 'camera'],
sizeType: ['compressed', 'original'],
success: (res) => {
this.image = res.tempFilePaths[0];
},
fail: (err) => {
// #ifdef MP
uni.getSetting({
success: (res) => {
let authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
if (!authStatus) {
uni.showModal({
title: '授权失败',
content: 'Hello uni-app需要从您的相机或相册获取图片请在设置界面打开相关权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
}
}
})
// #endif
}
})
},
compress(){// 20Kb
console.log('开始压缩');
let img = this.image;
return new Promise((res) => {
var localPath = plus.io.convertAbsoluteFileSystem(img.replace('file://', ''));
console.log('after' + localPath);
// size
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.file((file) => {// entry
console.log('getFile:' + JSON.stringify(file));
if(file.size > 20480) {// size 20Kb
plus.zip.compressImage({
src: img,
dst: img.replace('.jpg', '2222.jpg').replace('.JPG', '2222.JPG'),
width: '10%',
height: '10%',
quality: 1,
overwrite: true
}, (event) => {
console.log('success zip****' + event.size);
let newImg = img.replace('.jpg', '2222.jpg').replace('.JPG', '2222.JPG');
res(newImg);
}, function(error) {
uni.showModal({
content:'分享图片太大,需要请重新选择图片!',
showCancel:false
})
});
}
});
}, (e) => {
console.log('Resolve file URL failed: ' + e.message);
uni.showModal({
content:'分享图片太大,需要请重新选择图片!',
showCancel:false
})
});
})
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,49 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-btn-v">
<button class="btn-load" type="primary" @click="showLoading">显示 loading 提示框</button>
<!-- #ifndef MP-ALIPAY -->
<button @click="hideLoading">隐藏 loading 提示框</button>
<!-- #endif -->
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'loading'
}
},
methods: {
showLoading: function() {
uni.showLoading({
title: 'loading'
});
// #ifdef MP-ALIPAY
this._showTimer && clearTimeout(this._showTimer);
this._showTimer = setTimeout(() => {
this.hideLoading();
}, 3000)
// #endif
},
hideLoading: function() {
uni.hideLoading();
}
}
// #ifdef MP-ALIPAY
,
onUnload() {
this._showTimer && clearTimeout(this._showTimer);
}
// #endif
}
</script>
<style>
</style>

141
pages/API/soter/soter.vue Normal file
View File

@ -0,0 +1,141 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-btn-v">
<button type="primary" @click="checkIsSupportSoterAuthentication">检查支持的认证方式</button>
<button type="primary" @click="checkIsSoterEnrolledInDeviceFingerPrint">检查是否录入指纹</button>
<button type="primary" @click="checkIsSoterEnrolledInDeviceFaceID">检查是否录入FaceID</button>
<button type="primary" @click="startSoterAuthenticationFingerPrint">开始指纹认证</button>
<button type="primary" @click="startSoterAuthenticationFaceID">开始FaceID认证</button>
</view>
<view style="width: 100%;text-align: center;">{{ result }}</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: '生物认证',
result: ''
};
},
onLoad() {
},
methods: {
checkIsSupportSoterAuthentication() {
uni.checkIsSupportSoterAuthentication({
success(res) {
uni.showModal({
content: '支持的认证方式:' + res.supportMode,
showCancel: false
})
console.log(res);
},
fail(err) {
console.log(err);
}
})
},
checkIsSoterEnrolledInDeviceFingerPrint() {
uni.checkIsSoterEnrolledInDevice({
checkAuthMode: 'fingerPrint',
success(res) {
if(res.isEnrolled) {
uni.showToast({
icon: 'none',
title: '已录入指纹'
})
}else {
uni.showModal({
content: '未录入指纹',
showCancel: false
})
}
console.log(res);
},
fail(err) {
uni.showModal({
content: '未录入指纹',
showCancel: false
})
console.log(err);
}
})
},
checkIsSoterEnrolledInDeviceFaceID() {
uni.checkIsSoterEnrolledInDevice({
checkAuthMode: 'facial',
success(res) {
if(res.isEnrolled) {
uni.showToast({
icon: 'none',
title: '已录入FaceID'
})
}else {
uni.showModal({
content: '未录入FaceID',
showCancel: false
})
}
console.log(res);
},
fail(err) {
uni.showModal({
content: '未录入FaceID',
showCancel: false
})
console.log(err);
}
})
},
startSoterAuthenticationFingerPrint() {
uni.startSoterAuthentication({
requestAuthModes: ['fingerPrint'],
challenge: '123456',
authContent: '请用指纹解锁',
success(res) {
uni.showToast({
icon: 'none',
title: '指纹验证成功'
})
console.log(res);
},
fail(err) {
uni.showModal({
content: '指纹验证失败errCode' + err.errCode,
showCancel: false
})
console.log(err);
}
})
},
startSoterAuthenticationFaceID() {
uni.startSoterAuthentication({
requestAuthModes: ['facial'],
challenge: '123456',
authContent: '请用FaceID解锁',
success(res) {
uni.showToast({
icon: 'none',
title: 'FaceID验证成功'
})
console.log(res);
},
fail(err) {
uni.showModal({
content: 'FaceID验证失败errCode' + err.errCode,
showCancel: false
})
console.log(err);
}
})
}
}
};
</script>
<style></style>

120
pages/API/sqlite/sqlite.vue Normal file
View File

@ -0,0 +1,120 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-btn-v"><button type="primary" @click="openDB">打开数据库test.db</button></view>
<view class="uni-btn-v"><button type="primary" @click="executeSQL">创建表database及插入数据</button></view>
<view class="uni-btn-v"><button type="primary" @click="selectSQL">查询表database的数据</button></view>
<view class="uni-btn-v"><button type="primary" @click="droptable">删除表database</button></view>
<view class="uni-btn-v"><button type="primary" @click="closeDB">关闭数据库test.db</button></view>
<view class="uni-btn-v"><button type="primary" @click="isOpenDB">查询是否打开数据库</button></view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'SQLite'
};
},
methods: {
openDB: function() {
plus.sqlite.openDatabase({
name: 'first',
path: '_doc/test.db',
success: function(e) {
plus.nativeUI.alert('打开数据库test.db成功 ');
},
fail: function(e) {
plus.nativeUI.alert('打开数据库test.db失败: ' + JSON.stringify(e));
}
});
},
// SQL
executeSQL: function() {
plus.sqlite.executeSql({
name: 'first',
sql: 'create table if not exists database("name" CHAR(110),"sex" CHAR(10),"age" INT(11))',
success: function(e) {
plus.sqlite.executeSql({
name: 'first',
sql: "insert into database values('彦','女','7000')",
success: function(e) {
plus.nativeUI.alert('创建表table和插入数据成功');
},
fail: function(e) {
plus.nativeUI.alert('创建表table成功但插入数据失败: ' + JSON.stringify(e));
}
});
},
fail: function(e) {
plus.nativeUI.alert('创建表table失败: ' + JSON.stringify(e));
}
});
},
// SQL
selectSQL: function() {
plus.sqlite.selectSql({
name: 'first',
sql: 'select * from database',
success: function(e) {
plus.nativeUI.alert('查询SQL语句成功: ' + JSON.stringify(e));
},
fail: function(e) {
plus.nativeUI.alert('查询SQL语句失败: ' + JSON.stringify(e));
}
});
},
//
droptable: function() {
plus.sqlite.executeSql({
name: 'first',
sql: 'drop table database',
success: function(e) {
plus.nativeUI.alert('删除表database成功');
},
fail: function(e) {
plus.nativeUI.alert('删除表database失败: ' + JSON.stringify(e));
}
});
},
//
closeDB: function() {
plus.sqlite.closeDatabase({
name: 'first',
success: function(e) {
plus.nativeUI.alert('关闭数据库成功');
},
fail: function(e) {
plus.nativeUI.alert('关闭数据库失败: ' + JSON.stringify(e));
}
});
},
isOpenDB: function() {
if (
plus.sqlite.isOpenDatabase({
name: 'first',
path: '_doc/test.db'
})
) {
plus.nativeUI.alert('Opened!');
} else {
plus.nativeUI.alert('Unopened!');
}
}
}
};
</script>
<style>
.uni-btn-v {
margin: 20rpx 0;
padding: 0;
}
</style>

View File

@ -0,0 +1,131 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-common-mt">
<view class="uni-list">
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">key</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" placeholder="请输入key" name="key" :value="key" @input="keyChange"/>
</view>
</view>
<view class="uni-list-cell">
<view class="uni-list-cell-left">
<view class="uni-label">value</view>
</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="text" placeholder="请输入value" name="data" :value="data" @input="dataChange"/>
</view>
</view>
</view>
<view class="uni-padding-wrap">
<view class="uni-btn-v">
<button type="primary" class="btn-setstorage" @tap="setStorage">存储数据</button>
<button @tap="getStorage">读取数据</button>
<button @tap="clearStorage">清理数据</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'get/set/clearStorage',
key: '',
data: ''
}
},
methods: {
keyChange: function (e) {
this.key = e.detail.value
},
dataChange: function (e) {
this.data = e.detail.value
},
getStorage: function () {
var key = this.key,
data = this.data;
if (key.length === 0) {
uni.showModal({
title: '读取数据失败',
content: "key 不能为空",
showCancel:false
})
} else {
uni.getStorage({
key: key,
success: (res) => {
uni.showModal({
title: '读取数据成功',
content: "data: '" + res.data + "'",
showCancel:false
})
},
fail: () => {
uni.showModal({
title: '读取数据失败',
content: "找不到 key 对应的数据",
showCancel:false
})
}
})
}
},
setStorage: function () {
var key = this.key
var data = this.data
if (key.length === 0) {
uni.showModal({
title: '保存数据失败',
content: 'key 不能为空',
showCancel:false
})
} else {
uni.setStorage({
key: key,
data: data,
success: (res) => {
uni.showModal({
title: '存储数据成功',
// #ifndef MP-ALIPAY
content: JSON.stringify(res.errMsg),
// #endif
// #ifdef MP-ALIPAY
content: data,
// #endif
showCancel:false
})
},
fail: () => {
uni.showModal({
title: '储存数据失败!',
showCancel:false
})
}
})
}
},
clearStorage: function () {
uni.clearStorageSync()
this.key = '',
this.data = ''
uni.showModal({
title: '清除数据成功',
content: ' ',
showCancel:false
})
}
}
}
</script>
<style>
.btn-setstorage {
background-color: #007aff;
color: #ffffff;
}
</style>

View File

@ -0,0 +1,137 @@
<template>
<view class="content">
<page-head :title="title"></page-head>
<view class="example">
<view class="example-title">从左侧滑出</view>
<button @click="showDrawer">显示抽屉</button>
</view>
<view class="example">
<view class="example-title">从上侧竖向滑出</view>
<button @click="showPopup">显示 弹出层</button>
</view>
<view style="width: 100%;">
<video v-if="showVideo" id="video"
@play="playVideo"
@pause="closeMask"
:controls="false"
src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20181126-lite.m4v"
@error="videoErrorCallback" poster="https://web-assets.dcloud.net.cn/unidoc/zh/poster.png"></video>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'SubNvue',
showVideo: false
};
},
onLoad() {
this.closeMask();
// popup
uni.$on('popup-page', (data) => {
switch(data.type){
case 'interactive':
uni.showModal({
title: '来自Popup的消息',
content: data.info
})
break;
default:
uni.showToast({
title: data.title,
})
break;
}
})
// drawer
uni.$on('drawer-page', (data) => {
uni.showToast({
title: '点击了第' + data + '项',
icon:"none"
});
})
},
onUnload() {
uni.$off('popup-page')
uni.$off('drawer-page')
},
onReady() {
this.showVideo = true
},
methods: {
showDrawer() {
uni.getSubNVueById('drawer').show('slide-in-left', 200);
},
showPopup() {
// popup
uni.$emit('page-popup', {
title: '请阅读软件内容',
content: 'uni-app 是一个使用 Vue.js 开发跨平台应用的前端框架开发者编写一套代码可编译到iOS、Android、H5、小程序等多个平台。'
});
const subNVue = uni.getSubNVueById('popup')
subNVue.show('slide-in-top', 250)
},
videoErrorCallback: function() {
uni.showModal({
content: '视频加载失败',
showCancel: false
})
},
playVideo() {
let subNVue = uni.getSubNVueById('video_mask')
subNVue.show('fade-in', 200, () => {
uni.$emit('play-video', {
status: 'open',
})
})
},
closeMask() {
let subNVue = uni.getSubNVueById('video_mask')
uni.$emit('close-video', {
status: 'close',
})
subNVue.hide('fade-out', 200)
},
}
}
</script>
<style>
.content {
align-content: center;
height: 100%;
background-color: #F4F5F6;
}
.example {
padding: 0 10px 10px
}
.example-title {
font-size: 14px;
line-height: 14px;
color: #777;
margin: 40px 2rpx;
position: relative
}
video {
position: absolute;
bottom: 30px;
left: 0;
width: 100%;
height: 200px;
}
.example .example-title {
margin: 40rpx 0
}
button {
background-color: #f8f8f8;
}
.title {
font-size: 20px;
text-align: center;
color: #8f8f94;
}
</style>

Some files were not shown because too many files have changed in this diff Show More