
模块化开发 Mui (Mui-Vue-Pug-Sass-Starter)
dcloud 的 markdown 编辑器很难用,格式不太对,下面的链接可查看格式没问题的版本
本文档推荐IDE:VS Code
Github: Mui-Vue-Pug-Sass-Starter
Mui-Vue-Pug-Sass-Starter
本文做为个人近几个月接触 Mui 的总结,主要通过 Vue 来模块化开发
-
NodeJs & cnpm
此部分跳过,请自行脑补
Vue-Cli
全局安装
cnpm install --global vue-cli
初始化Vue
vue init webpack mui-vue-pug-sass-starter
PS: vue-router 这一步,输入 n,其余全部默认
? Project name mui-vue-pug-sass-starter
? Project description A Vue.js project
? Author jun jun@***.cn
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? Yes
? Should we runnpm install
for you after the project has been created? (recommended) npm
要最大化利用 Webview 运行效率,采用 Mpa 方案来进行 Vue 的开发,所以关闭 vue-router
漫长的等待后...
配置篇
安装所需依赖
cnpm install --save-dev glob node-sass sass-loader pug pug-loader pug-filters clean-webpack-plugin
cnpm install --save axios
cnpm install
-
目录结构
+-- builder
+-- config
+-- src
| +-- app // 多页面入口,每个目录为一个页面,build 输出以目录为名的 html
| | +-- page1
| | | --- App.vue // 主模块
| | | --- page1.js // 入口,文件名同目录名
| | | --- page1.pug // html 模板,文件名同目录名
| | +-- page2
| | +-- ...
| +-- assets // 存放一些公共静态文件及 js 库
| | +-- img
| | +-- js
| | +-- sass
| +-- components // 公共 vue 组件目录
+-- static
| --- mui.min.css // 需修改 mui.ttf 路径为 ./
| --- mui.min.js
| --- mui.ttf
修改 Vue 为 Mpa 多页面入口模式
为最大化利用 Webview,需修改 Vue 为多页面入口模式
build/utils.js
添加输出遍历多页面入口的函数
// 使用glob模块遍历导入多页面入口
const glob = require('glob')
exports.entries = (globPath) => {
let entries = {}, baseName, tmp, pathName
glob.sync(globPath)
.forEach(entry => {
baseName = path.basename(entry, path.extname(entry))
tmp = entry.split('/').splice(-3)
pathName = tmp.splice(0, 1) + '/' + baseName
entries[pathName] = entry
})
return entries
}
build/webpack.base.conf.js
修改 module.exports.entry
/**
* 遍历 app 目录中所有子目录,生成多页面入口
*/
entry: utils.entries('./src/app/**/*.js'),
build/webpack.dev.conf.js
注释掉单页面入口
/* 关闭单页面入口
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
*/
在最后添加多页面入口代码段
// 多页面入口配置
let templates = utils.entries('./src/app/**/*.pug')
for (let pathName in templates) {
let conf = {
filename: pathName + '.html',
template: templates[pathName],
inject: true,
chunksSortMode: 'dependency'
}
if (pathName in devWebpackConfig.entry) {
conf.chunks = ['manifest', 'vendor', pathName]
conf.hash = true
}
devWebpackConfig.plugins.push(new HtmlWebpackPlugin(conf))
}
build/webpack.prod.conf.js
注释掉单页面入口代码段
/* 关闭单面页入口
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
*/
在最后添加多页面入口代码段
// 多页面入口配置
let templates = utils.entries('./src/app/**/*.pug')
for (let pathName in templates) {
let conf = {
filename: pathName + '.html',
template: templates[pathName],
inject: true,
chunksSortMode: 'dependency'
}
if (pathName in module.exports.entry) {
conf.chunks = ['manifest', 'vendor', pathName]
conf.hash = true
}
module.exports.plugins.push(new HtmlWebpackPlugin(conf))
}
配置 pug
build/webpack.base.conf.js
在 module.exports.module.rules 中添加 pug 定义
{ test: /\.pug$/, loader: 'pug-loader' },
其它配置
build/utils.js
修正 css 中引入外部文件(如字体、图片等)路径问题
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
/**
* 修正css 引入外部字体、图片等路径
*/
publicPath: '../../../'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
build/webpack.base.conf.js
定义路径别名 module.exports.resolve
resolve: {
extensions: ['.js', '.vue', '.json', 'scss', 'css'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components'),
'js': resolve('src/assets/js'),
'img': resolve('src/assets/img'),
'@img': resolve('src/assets/img'),
'@fonts': resolve('src/assets/fonts'),
'@sass': resolve('src/assets/sass')
}
},
build/webpack.prod.conf.js
run build 时自动清空 dist 目录
// 在头部 require
const CleanPlugin = require('clean-webpack-plugin')
// 在 const webpackConfig 代码段中的 plugins 数组内添加如下代码
// build时清空dist目录
new CleanPlugin(['../dist']),
config/index.js
修正 run build 后的 html 页面内的 路径问题
// module.exports.build.assertsPublicPath
assetsPublicPath: '../',
package.json
修改 browserslist 项,自动适配浏览器
"browserslist": [
"> 1%",
"not ie <= 8",
"iOS >= 7",
"Android > 4",
"Firefox > 20",
"last 5 versions"
]
应用篇
关于 ESLint
本文配置项中已开启 ESLint Standard 的支持,目的为规范代码的编写,具体 ESLint 的作用和用法,请自行脑补
vue 如何整合 mui ?
经过多次试验,最终还是在主html模板中引入mui这种方式最为合适
复制 mui.min.js、mui.min.css、mui.ttf 到 static 目录
需要修改 mui.min.css 中 mui.ttf 的路径为 ./mui.ttf
创建页面入口
src/app 目录为所有页面入口,每一个子目录代表一个页面,包含一个主模板、一个入口JS、一个主VUE模块
范例(基于 Webview 的 tab bar)
在 src/app 目录下新建 index 目录和 nav 目录
+-- src
| +-- app
| | +-- index
| | +-- nav
新建 index 模块
// src/app/index/index.pug
// 主模板,文件名称需和目录名一致
doctype html
html
head
meta(charset='UTF-8')
title index
meta(name='viewport' content='width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no')
link(rel="stylesheet", href="../static/mui.min.css")
body
script(src='../static/mui.min.js')
#app
// src/app/index/index.js
// 入口文件,需和目录名一致
// 基本所有入口文件都可如下一致
import Vue from 'vue'
import App from './App'
// eslint-disable-next-line
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
// src/app/index/App.vue
// 主模块文件
<template lang="pug">
</template>
<style lang="sass">
</style>
<script>
/* global mui */
export default {
name: 'index',
data () {
return {}
},
mounted () {
mui.init({
wipeBack: true,
subpages: [{
url: './home.html',
id: 'home',
styles: {
top: 0,
bottom: '45px',
zindex: 1
}
}, {
url: './nav.html',
id: 'nav',
styles: {
bottom: 0,
height: '45px',
zindex: 9
}
}]
})
}
}
</script>
因 mui 是在主模板中 script src 引入,在 模块和入口里没有定义,需在 script 段第一行加入 / global mui / 将mui全局化,如不加这一行,编译时会报错,如果用到了 plus ,则为 / global mui plus /
mui.init 或 mui.plusReady 等初始化函数,需写入 vue 生命周期 mounted 内
新建 nav 模块
可以直接将 src/app/index/index.js 和 src/app/index/index.pug 复制到 src/app/nav 目录下,并分别改名为 nav.js 和 nav.pug,这两个文件的内容可以不改动
// src/app/nav/App.vue
<template lang="pug">
nav.mui-bar.mui-bar-tab
a.mui-tab-item(v-for='tab in tabs', :class='{ "mui-active": activeIndex == tab.index }', v-on:tap='openTabPage(tab.index)')
span.mui-icon(:class='tab.icon')
span.mui-tab-label {{ tab.name }}
</template>
<script>
/* global mui plus */
export default {
name: 'tabs',
data () {
return {
// 当前激活的 tab 序号
activeIndex: 0,
// 定义 4 个 tab
tabs: [
{ index: 0, id: 'tab1', name: '首页', icon: 'mui-icon-home', url: './home.html' },
{ index: 1, id: 'tab2', name: '消息', icon: 'mui-icon-email', url: 'http://www.dcloud.io/hellomui/examples/tableviews.html' },
{ index: 2, id: 'tab3', name: '通讯录', icon: 'mui-icon-contact', url: 'http://www.dcloud.io/hellomui/examples/indexed-list-select.html' },
{ index: 3, id: 'tab4', name: '设置', icon: 'mui-icon-gear', url: 'http://www.dcloud.io/hellomui/examples/icons.html' }
]
}
},
methods: {
openTabPage: function (index) {
let styles = { top: 0, bottome: '45px', zindex: 1 }
// 获取父 webview,即 index.html 所属 webview
let main = plus.webview.currentWebview().parent()
// 如果当前 tab 已被激活,则返回
if (index === this.activeIndex) return
// 如 plus 中不存在当前要打开的子 webview id,则新建并追加到父 webview
if (!plus.webview.getWebviewById(this.tabs[index].id)) {
let subWebview = plus.webview.create(this.tabs[index].url, this.tabs[index].id, styles)
main.append(subWebview)
}
// 显示要打开的子 webview
plus.webview.show(this.tabs[index].id)
// 设置当前 tab index
this.activeIndex = index
}
},
mounted () {
mui.init()
}
}
</script>
本示例中应用到了 vue 的特性,v-on:tap, v-bind:class, v-for
新建 home 模块
同 nav 模块,可直接复制 pug 与 js 文件到 src/app/home
// src/app/home/App.vue
<template lang="pug">
#app
header.mui-bar.mui-bar-nav
h1.mui-title Mui-Vue-Pug-Sass-Starter
.mui-content
.mui-content-padded
button.mu-btn.mui-btn-primary.mui-btn-block(type='button', v-on:tap='openAxios') 打开 Axios 测试页
</template>
<script>
/* global mui */
export default {
name: 'home',
data () {
return {}
},
methods: {
openAxios () {
mui.openWindow({
url: './axios.html',
id: 'axios'
})
}
},
mounted () {
mui.init()
}
}
</script>
新建 axios 模块
同上操作复制 pug 与 js 到 src/app/axios 目录
本示例示范 vue 官方推荐的 ajax 库 axios 的简单操作
点击查看 axios 中文文档
// src/app/axios/App.vue
<template lang="pug">
#app
header.mui-bar.mui-bar-nav
a.mui-action-back.mui-icon.mui-icon-left-nav.mui-pull-left
h1.mui-title Axios 测试
.mui-content
.mui-content-padded 本示例引用了一个淘宝 api 接口,接口作用未知,请在下面输入框内任意输入一个产品关键词
br
| 例如:“老婆”
.mui-input-group
.mui-input-row
input(type='text', placeholder='任意输入一个产品关键词', v-model='inputStr')
.mui-button-row
button.mui-btn.mui-btn-primary(type='button', v-on:tap='getJson') 获取 Json 数据
ul.mui-table-view
li.mui-table-view-cell(v-for='item in result') {{ item[0] }}
span.mui-badge {{ item[1] }}
</template>
<script>
/* global mui */
// 从 node_module 中引入 axios
import axios from 'axios'
// 设置 axios 默认请求 url 前缀
axios.defaults.baseURL = 'https://suggest.taobao.com'
export default {
name: 'axios',
data () {
return {
inputStr: '老婆',
result: []
}
},
methods: {
getJson: function () {
// 如果输入框为空,则返回,并显示 toast 层
if (!this.inputStr) return mui.toast('请输入任意一个产品关键词')
let params = {
params: {
code: 'utf-8',
q: this.inputStr
}
}
axios
.get('/sug', params)
.then(res => {
this.result = res.data.result
})
.catch(() => mui.toast('axios 请求失败'))
}
},
mounted () {
mui.init()
}
}
</script>
至此,一个简单的,基于 Vue 的 mui 模块化开发示范大体结束
调试篇
npm run build
输入上面一行命令,将我们的开发成果 build 出成品
HBuilder 入场
请出我们久违了的 HBuilder,开始调试我们的成果
- HBuilder >> 文件菜单 >> 选择目录
- 选择 build 后生成的 dist 目录,并起个项目名称,然后完成
- 左侧项目管理器中,鼠标右键点击上一步完成后出现的项目,选择右键菜单中 “转换成移动App”,此时,HBuilder 将会在 dist 目录下添加 manifest.json 文件
- HBuilder 中打开 manifest.json 开始配置我们的 App,请注意要选择一下 页面入口这一项,这里我们设置成 app/index.html
- 其他相关 manifest.json 的设置请参照 dcloud 官方文档
至此,本文档大体结束,可以按照我们之前的操作习惯在 HBuilder 中进行真机测试了。
后续,计划在本文档的基础上,再次整理一个新的文档出来
名称拟定为 Mui-Vue-TypeScript-Starter
本文档推荐IDE:VS Code
Github: Mui-Vue-Pug-Sass-Starter
dcloud 的 markdown 编辑器很难用,格式不太对,下面的链接可查看格式没问题的版本
本文档推荐IDE:VS Code
Github: Mui-Vue-Pug-Sass-Starter
Mui-Vue-Pug-Sass-Starter
本文做为个人近几个月接触 Mui 的总结,主要通过 Vue 来模块化开发
-
NodeJs & cnpm
此部分跳过,请自行脑补
Vue-Cli
全局安装
cnpm install --global vue-cli
初始化Vue
vue init webpack mui-vue-pug-sass-starter
PS: vue-router 这一步,输入 n,其余全部默认
? Project name mui-vue-pug-sass-starter
? Project description A Vue.js project
? Author jun jun@***.cn
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? Yes
? Should we runnpm install
for you after the project has been created? (recommended) npm
要最大化利用 Webview 运行效率,采用 Mpa 方案来进行 Vue 的开发,所以关闭 vue-router
漫长的等待后...
配置篇
安装所需依赖
cnpm install --save-dev glob node-sass sass-loader pug pug-loader pug-filters clean-webpack-plugin
cnpm install --save axios
cnpm install
-
目录结构
+-- builder
+-- config
+-- src
| +-- app // 多页面入口,每个目录为一个页面,build 输出以目录为名的 html
| | +-- page1
| | | --- App.vue // 主模块
| | | --- page1.js // 入口,文件名同目录名
| | | --- page1.pug // html 模板,文件名同目录名
| | +-- page2
| | +-- ...
| +-- assets // 存放一些公共静态文件及 js 库
| | +-- img
| | +-- js
| | +-- sass
| +-- components // 公共 vue 组件目录
+-- static
| --- mui.min.css // 需修改 mui.ttf 路径为 ./
| --- mui.min.js
| --- mui.ttf
修改 Vue 为 Mpa 多页面入口模式
为最大化利用 Webview,需修改 Vue 为多页面入口模式
build/utils.js
添加输出遍历多页面入口的函数
// 使用glob模块遍历导入多页面入口
const glob = require('glob')
exports.entries = (globPath) => {
let entries = {}, baseName, tmp, pathName
glob.sync(globPath)
.forEach(entry => {
baseName = path.basename(entry, path.extname(entry))
tmp = entry.split('/').splice(-3)
pathName = tmp.splice(0, 1) + '/' + baseName
entries[pathName] = entry
})
return entries
}
build/webpack.base.conf.js
修改 module.exports.entry
/**
* 遍历 app 目录中所有子目录,生成多页面入口
*/
entry: utils.entries('./src/app/**/*.js'),
build/webpack.dev.conf.js
注释掉单页面入口
/* 关闭单页面入口
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
*/
在最后添加多页面入口代码段
// 多页面入口配置
let templates = utils.entries('./src/app/**/*.pug')
for (let pathName in templates) {
let conf = {
filename: pathName + '.html',
template: templates[pathName],
inject: true,
chunksSortMode: 'dependency'
}
if (pathName in devWebpackConfig.entry) {
conf.chunks = ['manifest', 'vendor', pathName]
conf.hash = true
}
devWebpackConfig.plugins.push(new HtmlWebpackPlugin(conf))
}
build/webpack.prod.conf.js
注释掉单页面入口代码段
/* 关闭单面页入口
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
*/
在最后添加多页面入口代码段
// 多页面入口配置
let templates = utils.entries('./src/app/**/*.pug')
for (let pathName in templates) {
let conf = {
filename: pathName + '.html',
template: templates[pathName],
inject: true,
chunksSortMode: 'dependency'
}
if (pathName in module.exports.entry) {
conf.chunks = ['manifest', 'vendor', pathName]
conf.hash = true
}
module.exports.plugins.push(new HtmlWebpackPlugin(conf))
}
配置 pug
build/webpack.base.conf.js
在 module.exports.module.rules 中添加 pug 定义
{ test: /\.pug$/, loader: 'pug-loader' },
其它配置
build/utils.js
修正 css 中引入外部文件(如字体、图片等)路径问题
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
/**
* 修正css 引入外部字体、图片等路径
*/
publicPath: '../../../'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
build/webpack.base.conf.js
定义路径别名 module.exports.resolve
resolve: {
extensions: ['.js', '.vue', '.json', 'scss', 'css'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components'),
'js': resolve('src/assets/js'),
'img': resolve('src/assets/img'),
'@img': resolve('src/assets/img'),
'@fonts': resolve('src/assets/fonts'),
'@sass': resolve('src/assets/sass')
}
},
build/webpack.prod.conf.js
run build 时自动清空 dist 目录
// 在头部 require
const CleanPlugin = require('clean-webpack-plugin')
// 在 const webpackConfig 代码段中的 plugins 数组内添加如下代码
// build时清空dist目录
new CleanPlugin(['../dist']),
config/index.js
修正 run build 后的 html 页面内的 路径问题
// module.exports.build.assertsPublicPath
assetsPublicPath: '../',
package.json
修改 browserslist 项,自动适配浏览器
"browserslist": [
"> 1%",
"not ie <= 8",
"iOS >= 7",
"Android > 4",
"Firefox > 20",
"last 5 versions"
]
应用篇
关于 ESLint
本文配置项中已开启 ESLint Standard 的支持,目的为规范代码的编写,具体 ESLint 的作用和用法,请自行脑补
vue 如何整合 mui ?
经过多次试验,最终还是在主html模板中引入mui这种方式最为合适
复制 mui.min.js、mui.min.css、mui.ttf 到 static 目录
需要修改 mui.min.css 中 mui.ttf 的路径为 ./mui.ttf
创建页面入口
src/app 目录为所有页面入口,每一个子目录代表一个页面,包含一个主模板、一个入口JS、一个主VUE模块
范例(基于 Webview 的 tab bar)
在 src/app 目录下新建 index 目录和 nav 目录
+-- src
| +-- app
| | +-- index
| | +-- nav
新建 index 模块
// src/app/index/index.pug
// 主模板,文件名称需和目录名一致
doctype html
html
head
meta(charset='UTF-8')
title index
meta(name='viewport' content='width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no')
link(rel="stylesheet", href="../static/mui.min.css")
body
script(src='../static/mui.min.js')
#app
// src/app/index/index.js
// 入口文件,需和目录名一致
// 基本所有入口文件都可如下一致
import Vue from 'vue'
import App from './App'
// eslint-disable-next-line
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
// src/app/index/App.vue
// 主模块文件
<template lang="pug">
</template>
<style lang="sass">
</style>
<script>
/* global mui */
export default {
name: 'index',
data () {
return {}
},
mounted () {
mui.init({
wipeBack: true,
subpages: [{
url: './home.html',
id: 'home',
styles: {
top: 0,
bottom: '45px',
zindex: 1
}
}, {
url: './nav.html',
id: 'nav',
styles: {
bottom: 0,
height: '45px',
zindex: 9
}
}]
})
}
}
</script>
因 mui 是在主模板中 script src 引入,在 模块和入口里没有定义,需在 script 段第一行加入 / global mui / 将mui全局化,如不加这一行,编译时会报错,如果用到了 plus ,则为 / global mui plus /
mui.init 或 mui.plusReady 等初始化函数,需写入 vue 生命周期 mounted 内
新建 nav 模块
可以直接将 src/app/index/index.js 和 src/app/index/index.pug 复制到 src/app/nav 目录下,并分别改名为 nav.js 和 nav.pug,这两个文件的内容可以不改动
// src/app/nav/App.vue
<template lang="pug">
nav.mui-bar.mui-bar-tab
a.mui-tab-item(v-for='tab in tabs', :class='{ "mui-active": activeIndex == tab.index }', v-on:tap='openTabPage(tab.index)')
span.mui-icon(:class='tab.icon')
span.mui-tab-label {{ tab.name }}
</template>
<script>
/* global mui plus */
export default {
name: 'tabs',
data () {
return {
// 当前激活的 tab 序号
activeIndex: 0,
// 定义 4 个 tab
tabs: [
{ index: 0, id: 'tab1', name: '首页', icon: 'mui-icon-home', url: './home.html' },
{ index: 1, id: 'tab2', name: '消息', icon: 'mui-icon-email', url: 'http://www.dcloud.io/hellomui/examples/tableviews.html' },
{ index: 2, id: 'tab3', name: '通讯录', icon: 'mui-icon-contact', url: 'http://www.dcloud.io/hellomui/examples/indexed-list-select.html' },
{ index: 3, id: 'tab4', name: '设置', icon: 'mui-icon-gear', url: 'http://www.dcloud.io/hellomui/examples/icons.html' }
]
}
},
methods: {
openTabPage: function (index) {
let styles = { top: 0, bottome: '45px', zindex: 1 }
// 获取父 webview,即 index.html 所属 webview
let main = plus.webview.currentWebview().parent()
// 如果当前 tab 已被激活,则返回
if (index === this.activeIndex) return
// 如 plus 中不存在当前要打开的子 webview id,则新建并追加到父 webview
if (!plus.webview.getWebviewById(this.tabs[index].id)) {
let subWebview = plus.webview.create(this.tabs[index].url, this.tabs[index].id, styles)
main.append(subWebview)
}
// 显示要打开的子 webview
plus.webview.show(this.tabs[index].id)
// 设置当前 tab index
this.activeIndex = index
}
},
mounted () {
mui.init()
}
}
</script>
本示例中应用到了 vue 的特性,v-on:tap, v-bind:class, v-for
新建 home 模块
同 nav 模块,可直接复制 pug 与 js 文件到 src/app/home
// src/app/home/App.vue
<template lang="pug">
#app
header.mui-bar.mui-bar-nav
h1.mui-title Mui-Vue-Pug-Sass-Starter
.mui-content
.mui-content-padded
button.mu-btn.mui-btn-primary.mui-btn-block(type='button', v-on:tap='openAxios') 打开 Axios 测试页
</template>
<script>
/* global mui */
export default {
name: 'home',
data () {
return {}
},
methods: {
openAxios () {
mui.openWindow({
url: './axios.html',
id: 'axios'
})
}
},
mounted () {
mui.init()
}
}
</script>
新建 axios 模块
同上操作复制 pug 与 js 到 src/app/axios 目录
本示例示范 vue 官方推荐的 ajax 库 axios 的简单操作
点击查看 axios 中文文档
// src/app/axios/App.vue
<template lang="pug">
#app
header.mui-bar.mui-bar-nav
a.mui-action-back.mui-icon.mui-icon-left-nav.mui-pull-left
h1.mui-title Axios 测试
.mui-content
.mui-content-padded 本示例引用了一个淘宝 api 接口,接口作用未知,请在下面输入框内任意输入一个产品关键词
br
| 例如:“老婆”
.mui-input-group
.mui-input-row
input(type='text', placeholder='任意输入一个产品关键词', v-model='inputStr')
.mui-button-row
button.mui-btn.mui-btn-primary(type='button', v-on:tap='getJson') 获取 Json 数据
ul.mui-table-view
li.mui-table-view-cell(v-for='item in result') {{ item[0] }}
span.mui-badge {{ item[1] }}
</template>
<script>
/* global mui */
// 从 node_module 中引入 axios
import axios from 'axios'
// 设置 axios 默认请求 url 前缀
axios.defaults.baseURL = 'https://suggest.taobao.com'
export default {
name: 'axios',
data () {
return {
inputStr: '老婆',
result: []
}
},
methods: {
getJson: function () {
// 如果输入框为空,则返回,并显示 toast 层
if (!this.inputStr) return mui.toast('请输入任意一个产品关键词')
let params = {
params: {
code: 'utf-8',
q: this.inputStr
}
}
axios
.get('/sug', params)
.then(res => {
this.result = res.data.result
})
.catch(() => mui.toast('axios 请求失败'))
}
},
mounted () {
mui.init()
}
}
</script>
至此,一个简单的,基于 Vue 的 mui 模块化开发示范大体结束
调试篇
npm run build
输入上面一行命令,将我们的开发成果 build 出成品
HBuilder 入场
请出我们久违了的 HBuilder,开始调试我们的成果
- HBuilder >> 文件菜单 >> 选择目录
- 选择 build 后生成的 dist 目录,并起个项目名称,然后完成
- 左侧项目管理器中,鼠标右键点击上一步完成后出现的项目,选择右键菜单中 “转换成移动App”,此时,HBuilder 将会在 dist 目录下添加 manifest.json 文件
- HBuilder 中打开 manifest.json 开始配置我们的 App,请注意要选择一下 页面入口这一项,这里我们设置成 app/index.html
- 其他相关 manifest.json 的设置请参照 dcloud 官方文档
至此,本文档大体结束,可以按照我们之前的操作习惯在 HBuilder 中进行真机测试了。
后续,计划在本文档的基础上,再次整理一个新的文档出来
名称拟定为 Mui-Vue-TypeScript-Starter
本文档推荐IDE:VS Code
Github: Mui-Vue-Pug-Sass-Starter
收起阅读 »
php编程之如何调用支付宝支付接口的实现
对于任何一款软件来说,支付功能都是核心的,那么目前的主流支付接口主要是支付宝、微信和银联卡,而大多数开发技术人员对于如何调用支付宝的支付接口还存在很大的困扰,今天就来跟大家分享一下如何正确调用支付宝支付接口的实现,代码如下所示:
public function zfbpay1(){
require_once './ThinkPHP/Extend/Vendor/alipay1/config.php';
require_once './ThinkPHP/Extend/Vendor/alipay1/pagepay/service/AlipayTradeService.php';
require_once './ThinkPHP/Extend/Vendor/alipay1/pagepay/buildermodel/AlipayTradePagePayContentBuilder.php';
$model=M('zfbpay');
$add['oid']=I('nid');
$add['type']=1;
$add['WIDout_trade_no']=trim($_POST['WIDout_trade_no']);
$add['WIDtotal_amount']=trim($_POST['WIDtotal_amount']);
$add['WIDsubject']=trim($_POST['WIDsubject']);
// dump($_POST);die;
$re=$model->add($add);
if($re){
//商户订单号,商户网站订单系统中唯一订单号,必填
$out_trade_no = trim($_POST['WIDout_trade_no']);
//订单名称,必填
$subject = trim($_POST['WIDsubject']);
//付款金额,必填
// $total_amount = trim($_POST['WIDtotal_amount']);
$total_amount = 0.01;
//商品描述,可空
$body = trim($_POST['WIDbody']);
//构造参数
$payRequestBuilder = new AlipayTradePagePayContentBuilder();
$payRequestBuilder->setBody($body);
$payRequestBuilder->setSubject($subject);
$payRequestBuilder->setTotalAmount($total_amount);
$payRequestBuilder->setOutTradeNo($out_trade_no);
$aop = new AlipayTradeService($config);
/**
* pagePay 电脑网站支付请求
* @param $builder 业务参数,使用buildmodel中的对象生成。
* @param $return_url 同步跳转地址,公网可以访问
* @param $notify_url 异步通知地址,公网可以访问
* @return $response 支付宝返回的信息
*/
$response = $aop->pagePay($payRequestBuilder,$config['return_url'],$config['notify_url']);
$map['nid']=I('nid');
$map['out_trade_no']=$out_trade_no;
M('need')->save($map);
//输出表单
var_dump($response);
}
}
回调
public function updatezfb(){
require_once './ThinkPHP/Extend/Vendor/alipay1/config.php';
require_once './ThinkPHP/Extend/Vendor/alipay1/pagepay/service/AlipayTradeService.php';
$arr=$_POST;
$alipaySevice = new AlipayTradeService($config);
$alipaySevice->writeLog(var_export($_POST,true));
$result = $alipaySevice->check($arr);
/* 实际验证过程建议商户添加以下校验。
1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
4、验证app_id是否为该商户本身。
*/
if($result) {//验证成功
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代
// $add['content']=$_POST['out_trade_no'];
// M('a')->add($add);
// $add['content']=json_encode($_POST);
// M('a')->add($add);
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
//获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
//商户订单号
$out_trade_no = $_POST['out_trade_no'];
//支付宝交易号
$trade_no = $_POST['trade_no'];
//交易状态
$trade_status = $_POST['trade_status'];
if($_POST['trade_status'] == 'TRADE_FINISHED') {
$zfbmodel=M('zfbpay');
$arr=$zfbmodel->where("WIDout_trade_no=$out_trade_no")->find();
if($arr){
$type=$arr['type'];//1购买商品 2充值余额 3做单支付
if($type==1){
//购买商品
$nid=$arr['id'];
$model=M('need');
$orderinfo=$model->where("nid=$nid")->find();
$tuoguan=$orderinfo['tuoguan'];
$province=$orderinfo['province'];
$finish_state=$orderinfo['finish_state'];
$arr1['tuoguan']=1;
if($finish_state==1){
$arr1['finish_state']=2;
}
if ($orderinfo['money']!=0) {
$arr1['money']=$orderinfo['money'];
}else{
$arr1['money']=intval($orderinfo['money'])+intval($arr['WIDtotal_amount']);
}
$arr1['paytype']=1;
$res=$model->save($arr1);
}elseif($type==2){
//余额充值
$rid=$arr['id'];
//获取充值信息
$model=M('recharge');
$usermodel=M('user');
$rechargeinfo=$model->where("rid=$rid")->find();
$paystate=$rechargeinfo['paystate'];
if($paystate==0){
$uid=$rechargeinfo['uid'];
$num=$rechargeinfo['amoney'];
//更新订单状态
$update['paystate']=1;
$res=$model->where("rid=$rid")->save($update);
if($res){
//更新用户余额
$re2=$usermodel->where("id=$uid")->setInc('yue',$num);
}
}
}elseif($type==3){
}
}
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_amount与通知时获取的total_fee为一致的
//如果有做过处理,不执行商户的业务程序
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
}
else if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
$zfbmodel=M('zfbpay');
$arr=$zfbmodel->where("WIDout_trade_no=$out_trade_no")->find();
if($arr){
$type=$arr['type'];//1购买商品 2充值余额 3做单支付
if($type==1){
//购买商品
$oid=$arr['id'];
$model=M('order_goods');
$orderinfo=$model->where("oid=$oid")->find();
$paystate=$orderinfo['paystate'];
if($paystate==0){
$update['paystate']=1;
$res=$model->where("oid=$oid")->save($update);
//更新惠点
$umod=M('user');
$ordersone=$model->where("oid=$oid")->find();
$uid=$ordersone['uid'];
$usertel=$umod->where("id=$uid")->find();
$sys=M('sys')->find();
$mmp['id']=$usertel['id'];
$mmp['money']=$usertel['money']+$ordersone['oprice'];
$mmp['huidian']=floor($mmp['money']/$sys['zshd']);
$userSave=$umod->save($mmp);
}
}elseif($type==2){
//余额充值
$rid=$arr['id'];
//获取充值信息
$model=M('recharge');
$usermodel=M('user');
$rechargeinfo=$model->where("rid=$rid")->find();
$paystate=$rechargeinfo['paystate'];
if($paystate==0){
$uid=$rechargeinfo['uid'];
$num=$rechargeinfo['amoney'];
//更新订单状态
$update['paystate']=1;
$res=$model->where("rid=$rid")->save($update);
if($res){
//更新用户余额
$re2=$usermodel->where("id=$uid")->setInc('yue',$num);
}
}
}elseif($type==3){
}
}
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_amount与通知时获取的total_fee为一致的
//如果有做过处理,不执行商户的业务程序
//注意:
//付款完成后,支付宝系统发送该交易状态通知
}
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
echo "success"; //请不要修改或删除
}else {
//验证失败
echo "fail";
}
}
好了,看到这里相信各位技术开发人员已经知道自己的问题出现在哪些地方了,那么大家可以赶紧去尝试一下,如果还存在其他方面问题的话,可以留言咨询我们哦。
本文由专业的app开发报价燚轩科技编辑发布,如需转载请注明原文作者及出处!
对于任何一款软件来说,支付功能都是核心的,那么目前的主流支付接口主要是支付宝、微信和银联卡,而大多数开发技术人员对于如何调用支付宝的支付接口还存在很大的困扰,今天就来跟大家分享一下如何正确调用支付宝支付接口的实现,代码如下所示:
public function zfbpay1(){
require_once './ThinkPHP/Extend/Vendor/alipay1/config.php';
require_once './ThinkPHP/Extend/Vendor/alipay1/pagepay/service/AlipayTradeService.php';
require_once './ThinkPHP/Extend/Vendor/alipay1/pagepay/buildermodel/AlipayTradePagePayContentBuilder.php';
$model=M('zfbpay');
$add['oid']=I('nid');
$add['type']=1;
$add['WIDout_trade_no']=trim($_POST['WIDout_trade_no']);
$add['WIDtotal_amount']=trim($_POST['WIDtotal_amount']);
$add['WIDsubject']=trim($_POST['WIDsubject']);
// dump($_POST);die;
$re=$model->add($add);
if($re){
//商户订单号,商户网站订单系统中唯一订单号,必填
$out_trade_no = trim($_POST['WIDout_trade_no']);
//订单名称,必填
$subject = trim($_POST['WIDsubject']);
//付款金额,必填
// $total_amount = trim($_POST['WIDtotal_amount']);
$total_amount = 0.01;
//商品描述,可空
$body = trim($_POST['WIDbody']);
//构造参数
$payRequestBuilder = new AlipayTradePagePayContentBuilder();
$payRequestBuilder->setBody($body);
$payRequestBuilder->setSubject($subject);
$payRequestBuilder->setTotalAmount($total_amount);
$payRequestBuilder->setOutTradeNo($out_trade_no);
$aop = new AlipayTradeService($config);
/**
* pagePay 电脑网站支付请求
* @param $builder 业务参数,使用buildmodel中的对象生成。
* @param $return_url 同步跳转地址,公网可以访问
* @param $notify_url 异步通知地址,公网可以访问
* @return $response 支付宝返回的信息
*/
$response = $aop->pagePay($payRequestBuilder,$config['return_url'],$config['notify_url']);
$map['nid']=I('nid');
$map['out_trade_no']=$out_trade_no;
M('need')->save($map);
//输出表单
var_dump($response);
}
}
回调
public function updatezfb(){
require_once './ThinkPHP/Extend/Vendor/alipay1/config.php';
require_once './ThinkPHP/Extend/Vendor/alipay1/pagepay/service/AlipayTradeService.php';
$arr=$_POST;
$alipaySevice = new AlipayTradeService($config);
$alipaySevice->writeLog(var_export($_POST,true));
$result = $alipaySevice->check($arr);
/* 实际验证过程建议商户添加以下校验。
1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
4、验证app_id是否为该商户本身。
*/
if($result) {//验证成功
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代
// $add['content']=$_POST['out_trade_no'];
// M('a')->add($add);
// $add['content']=json_encode($_POST);
// M('a')->add($add);
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
//获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
//商户订单号
$out_trade_no = $_POST['out_trade_no'];
//支付宝交易号
$trade_no = $_POST['trade_no'];
//交易状态
$trade_status = $_POST['trade_status'];
if($_POST['trade_status'] == 'TRADE_FINISHED') {
$zfbmodel=M('zfbpay');
$arr=$zfbmodel->where("WIDout_trade_no=$out_trade_no")->find();
if($arr){
$type=$arr['type'];//1购买商品 2充值余额 3做单支付
if($type==1){
//购买商品
$nid=$arr['id'];
$model=M('need');
$orderinfo=$model->where("nid=$nid")->find();
$tuoguan=$orderinfo['tuoguan'];
$province=$orderinfo['province'];
$finish_state=$orderinfo['finish_state'];
$arr1['tuoguan']=1;
if($finish_state==1){
$arr1['finish_state']=2;
}
if ($orderinfo['money']!=0) {
$arr1['money']=$orderinfo['money'];
}else{
$arr1['money']=intval($orderinfo['money'])+intval($arr['WIDtotal_amount']);
}
$arr1['paytype']=1;
$res=$model->save($arr1);
}elseif($type==2){
//余额充值
$rid=$arr['id'];
//获取充值信息
$model=M('recharge');
$usermodel=M('user');
$rechargeinfo=$model->where("rid=$rid")->find();
$paystate=$rechargeinfo['paystate'];
if($paystate==0){
$uid=$rechargeinfo['uid'];
$num=$rechargeinfo['amoney'];
//更新订单状态
$update['paystate']=1;
$res=$model->where("rid=$rid")->save($update);
if($res){
//更新用户余额
$re2=$usermodel->where("id=$uid")->setInc('yue',$num);
}
}
}elseif($type==3){
}
}
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_amount与通知时获取的total_fee为一致的
//如果有做过处理,不执行商户的业务程序
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
}
else if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
$zfbmodel=M('zfbpay');
$arr=$zfbmodel->where("WIDout_trade_no=$out_trade_no")->find();
if($arr){
$type=$arr['type'];//1购买商品 2充值余额 3做单支付
if($type==1){
//购买商品
$oid=$arr['id'];
$model=M('order_goods');
$orderinfo=$model->where("oid=$oid")->find();
$paystate=$orderinfo['paystate'];
if($paystate==0){
$update['paystate']=1;
$res=$model->where("oid=$oid")->save($update);
//更新惠点
$umod=M('user');
$ordersone=$model->where("oid=$oid")->find();
$uid=$ordersone['uid'];
$usertel=$umod->where("id=$uid")->find();
$sys=M('sys')->find();
$mmp['id']=$usertel['id'];
$mmp['money']=$usertel['money']+$ordersone['oprice'];
$mmp['huidian']=floor($mmp['money']/$sys['zshd']);
$userSave=$umod->save($mmp);
}
}elseif($type==2){
//余额充值
$rid=$arr['id'];
//获取充值信息
$model=M('recharge');
$usermodel=M('user');
$rechargeinfo=$model->where("rid=$rid")->find();
$paystate=$rechargeinfo['paystate'];
if($paystate==0){
$uid=$rechargeinfo['uid'];
$num=$rechargeinfo['amoney'];
//更新订单状态
$update['paystate']=1;
$res=$model->where("rid=$rid")->save($update);
if($res){
//更新用户余额
$re2=$usermodel->where("id=$uid")->setInc('yue',$num);
}
}
}elseif($type==3){
}
}
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_amount与通知时获取的total_fee为一致的
//如果有做过处理,不执行商户的业务程序
//注意:
//付款完成后,支付宝系统发送该交易状态通知
}
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
echo "success"; //请不要修改或删除
}else {
//验证失败
echo "fail";
}
}
好了,看到这里相信各位技术开发人员已经知道自己的问题出现在哪些地方了,那么大家可以赶紧去尝试一下,如果还存在其他方面问题的话,可以留言咨询我们哦。
本文由专业的app开发报价燚轩科技编辑发布,如需转载请注明原文作者及出处!
收起阅读 »
关于huilder真机调式频繁掉线
找到HBuilder安装目录/plugins目录下,先备份一下/plugins目录下的所有文件(防止失败恢复回来),然后解压用下面的压缩包,得到一个jar文件,把这个jar文件拷贝到/plugins目录下面,重启huilder即可
找到HBuilder安装目录/plugins目录下,先备份一下/plugins目录下的所有文件(防止失败恢复回来),然后解压用下面的压缩包,得到一个jar文件,把这个jar文件拷贝到/plugins目录下面,重启huilder即可

对H5+的建议
现在尝试用H5+开发webApp,发现H5+总体是不错的,但是对硬件的支持比较弱,特别是在小视频大行其道的今天,H5+的视频拍摄功能居然是残废的,致使这个框架的可用性大打折扣。我觉得如果官方没精力搞,何不放开让第三方开发者来搞,开放第三方插件商店,平台和开发者进行分成,源代码由平台托管,平台按一定的标准对代码的维护情况进行定期审核,如果通不过审核的有权对源代码开源处理,这样对平台,开发者和使用者都有利,形成一个完善的生态系统,这样才能走远!
现在尝试用H5+开发webApp,发现H5+总体是不错的,但是对硬件的支持比较弱,特别是在小视频大行其道的今天,H5+的视频拍摄功能居然是残废的,致使这个框架的可用性大打折扣。我觉得如果官方没精力搞,何不放开让第三方开发者来搞,开放第三方插件商店,平台和开发者进行分成,源代码由平台托管,平台按一定的标准对代码的维护情况进行定期审核,如果通不过审核的有权对源代码开源处理,这样对平台,开发者和使用者都有利,形成一个完善的生态系统,这样才能走远!
收起阅读 »
Android 图片存储到指定路径和相册
我们在平常项目中,可能会存储一些头像,二维码之类的。这篇文章主要也是介绍自己在存储中会遇到的问题以及一些改进方案。
1.首先是长按保存:这个可以去参照网络上的,无非是自己先要拼接好一个文件路径。注意:IO流只能帮忙建文件,但是不能帮忙建目录(路径)。
// 先拼接好一个路径:在内存卡/或是手机内存上做好文件夹
String filePath = Environment.getExternalStorageDirectory()+savePath;
File localFile = new File(filePath);
if (!localFile.exists()) {
localFile.mkdir();
}123456
2.引导具体的文件名和路径:
//拼接好文件路径和名称
File finalImageFile = new File(localFile, System.currentTimeMillis() + ".jpg");
if (finalImageFile.exists()) {
finalImageFile.delete();
}
try {
finalImageFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}12345678910
3.文件的读取:
FileOutputStream fos = null;
try {
fos = new FileOutputStream(finalImageFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (bitmap == null) {
Toast.makeText(this, "图片不存在", 0).show();
return;
}
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
try {
fos.flush();
fos.close();
Toast.makeText(this, "图片保存在:"+ finalImageFile.getAbsolutePath(), 0).show();
} catch (IOException e) {
e.printStackTrace();
}12345678910111213141516171819
4.对于图片,我们也希望存储在固定路径之后,希望也可以在相册中查看该图片。这是可以利用一个广播告诉相册有图片更新。
//发广播告诉相册有图片需要更新,这样可以在图册下看到保存的图片了
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(finalImageFile);
intent.setData(uri);
sendBroadcast(intent);12345
通过以上步骤: 我们可以在指定路径的文件夹和相册中查看存储好的图片了。本文由专业的app开发报价燚轩科技整理发布,如需转载请注明出处!
我们在平常项目中,可能会存储一些头像,二维码之类的。这篇文章主要也是介绍自己在存储中会遇到的问题以及一些改进方案。
1.首先是长按保存:这个可以去参照网络上的,无非是自己先要拼接好一个文件路径。注意:IO流只能帮忙建文件,但是不能帮忙建目录(路径)。
// 先拼接好一个路径:在内存卡/或是手机内存上做好文件夹
String filePath = Environment.getExternalStorageDirectory()+savePath;
File localFile = new File(filePath);
if (!localFile.exists()) {
localFile.mkdir();
}123456
2.引导具体的文件名和路径:
//拼接好文件路径和名称
File finalImageFile = new File(localFile, System.currentTimeMillis() + ".jpg");
if (finalImageFile.exists()) {
finalImageFile.delete();
}
try {
finalImageFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}12345678910
3.文件的读取:
FileOutputStream fos = null;
try {
fos = new FileOutputStream(finalImageFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (bitmap == null) {
Toast.makeText(this, "图片不存在", 0).show();
return;
}
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
try {
fos.flush();
fos.close();
Toast.makeText(this, "图片保存在:"+ finalImageFile.getAbsolutePath(), 0).show();
} catch (IOException e) {
e.printStackTrace();
}12345678910111213141516171819
4.对于图片,我们也希望存储在固定路径之后,希望也可以在相册中查看该图片。这是可以利用一个广播告诉相册有图片更新。
//发广播告诉相册有图片需要更新,这样可以在图册下看到保存的图片了
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(finalImageFile);
intent.setData(uri);
sendBroadcast(intent);12345
通过以上步骤: 我们可以在指定路径的文件夹和相册中查看存储好的图片了。本文由专业的app开发报价燚轩科技整理发布,如需转载请注明出处!
收起阅读 »
天气系统小程序中的配置及逻辑层如何实现
小程序配置
使用app.json文件来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
{
"pages":[
"pages/index/index"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "天气预报",
"navigationBarTextStyle":"black"
},
"networkTimeout": {
"request": 10000
},
"debug": true
}
由于项目只有一个页面,所以不需要底部tab。另外设置网络请求时间为10秒,并且启用调试模式。
小程序逻辑层
首先在common.js中使用获取用户当前地理位置接口获取用户的坐标地址,坐标类型选择gcj02。
//获取当前位置坐标
function getLocation(callback) {
wx.getLocation({
type: 'gcj02',
success: function(res) {
callback(true, res.latitude, res.longitude);
},
fail: function() {
callback(false);
}
})
}
Wx.getlocation调用成功之后,将坐标信息返回给callback函数。失败时将false传给callback函数。
获取到坐标之后,再使用百度接口查询天气。相应的查询代码如下所示。
function getWeather(latitude, longitude, callback) {
var ak = "";//换成自己的ak,
var url = "https://api.map.baidu.com/telematics/v3/weather?location=" + longitude + "," + latitude + "&output=json&ak=" + ak;
wx.request({
url: url,
success: function(res){
console.log(res);
callback(res.data);
}
});
}
在上述代码中,先定义百度接口的ak,再通过拼接参数构造url的其他部分。然后调用www.appsaa.com和wx.request 请求天气预报数据。
接下来把上述接口组合起来,组成给应用层的接口,相应代码如下所示。
function loadWeatherData(callback) {
getLocation(function(success, latitude, longitude){
getWeather(latitude, longitude, function(weatherData){
callback(weatherData);
});
});
}
最后通过 module.exports对外暴露该接口。代码如下所示。
module.exports = { loadWeatherData: loadWeatherData}
小程序配置
使用app.json文件来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
{
"pages":[
"pages/index/index"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "天气预报",
"navigationBarTextStyle":"black"
},
"networkTimeout": {
"request": 10000
},
"debug": true
}
由于项目只有一个页面,所以不需要底部tab。另外设置网络请求时间为10秒,并且启用调试模式。
小程序逻辑层
首先在common.js中使用获取用户当前地理位置接口获取用户的坐标地址,坐标类型选择gcj02。
//获取当前位置坐标
function getLocation(callback) {
wx.getLocation({
type: 'gcj02',
success: function(res) {
callback(true, res.latitude, res.longitude);
},
fail: function() {
callback(false);
}
})
}
Wx.getlocation调用成功之后,将坐标信息返回给callback函数。失败时将false传给callback函数。
获取到坐标之后,再使用百度接口查询天气。相应的查询代码如下所示。
function getWeather(latitude, longitude, callback) {
var ak = "";//换成自己的ak,
var url = "https://api.map.baidu.com/telematics/v3/weather?location=" + longitude + "," + latitude + "&output=json&ak=" + ak;
wx.request({
url: url,
success: function(res){
console.log(res);
callback(res.data);
}
});
}
在上述代码中,先定义百度接口的ak,再通过拼接参数构造url的其他部分。然后调用www.appsaa.com和wx.request 请求天气预报数据。
接下来把上述接口组合起来,组成给应用层的接口,相应代码如下所示。
function loadWeatherData(callback) {
getLocation(function(success, latitude, longitude){
getWeather(latitude, longitude, function(weatherData){
callback(weatherData);
});
});
}
最后通过 module.exports对外暴露该接口。代码如下所示。
module.exports = { loadWeatherData: loadWeatherData}
收起阅读 »
php编程之kindeditor上传图片加水印实现
对于如何在图片上添加水印是许多技术人员遇到的一个难题,那大家都见过微信公众号及一些其他技术平台是可以实现这个功能的,但是对于源码是如何实现的,却没有头绪,那么今天就为大家介绍一下关于kindeditor上传图片加水印的方法,看完之后相信各位技术人员就知道了。
第一步:修改upload_json.php文件
在编辑器的/php/目录下可以找到这个文件,新增一个函数:
/*
- 功能:PHP图片水印,水印支持图片或文字
- 参数:
- $groundImage 背景图片,即需要加水印的图片,暂只支持GIF,JPG,PNG格式;
- $waterPos 水印位置,有10种状态,0为随机位置;
- 1为顶端居左,2为顶端居中,3为顶端居右;
- 4为中部居左,5为中部居中,6为中部居右;
- 7为底端居左,8为底端居中,9为底端居右;
- $waterImage 图片水印,即作为水印的图片,暂只支持GIF,JPG,PNG格式;
- $alpha 水印透明度,取值1-100;
- $waterText 文字水印,即把文字作为为水印,支持ASCII码,不支持中文;
- $textFont 文字大小,值为1、2、3、4或5,默认为5;
- $textColor 文字颜色,值为十六进制颜色值,默认为#FF0000(红色);
- $waterImage 和 $waterText 最好不要同时使用,选其中之一即可,优先使用 $waterImage。
- 当$waterImage有效时,参数$waterString、$stringFont、$stringColor均不生效。
- 加水印后的图片的文件名和 $groundImage 一样。
*/
function imageWaterMark($groundImage, $waterPos=0, $waterImage='', $alpha=80, $waterText='', $water_fontfile, $textFont=9, $textColor='#FF0000'){
$isWaterImage = FALSE;
$formatMsg = '不支持该图片格式!请使用GIF、JPG、PNG格式的图片。';
$fontFile = $water_fontfile;
//读取水印文件
if(!empty($waterImage) && file_exists($waterImage)){
$isWaterImage = TRUE;
$water_info = getimagesize($waterImage);
$water_w = $water_info[0];//取得水印图片的宽
$water_h = $water_info[1];//取得水印图片的高
switch($water_info[2]){//取得水印图片的格式
case 1:$water_im = imagecreatefromgif($waterImage);break;
case 2:$water_im = imagecreatefromjpeg($waterImage);break;
case 3:$water_im = imagecreatefrompng($waterImage);break;
default:die($formatMsg);
}
}
//读取背景图片
if(!empty($groundImage) && file_exists($groundImage)){
$ground_info = getimagesize($groundImage);
$ground_w = $ground_info[0];//取得背景图片的宽
$ground_h = $ground_info[1];//取得背景图片的高
switch($ground_info[2]){//取得背景图片的格式
case 1:$ground_im = imagecreatefromgif($groundImage);break;
case 2:$ground_im = imagecreatefromjpeg($groundImage);break;
case 3:$ground_im = imagecreatefrompng($groundImage);break;
default:die($formatMsg);
}
}else{
alert("水印图片不存在!");
}
//水印位置
if($isWaterImage){//图片水印
$w = $water_w;
$h = $water_h;
$label = "图片的";
}else{//文字水印
$temp = imagettfbbox($textFont, 0, $fontFile, $waterText);//取得使用 TrueType 字体的文本的范围
$w = $temp[2] - $temp[6];
$h = $temp[3] - $temp[7];
unset($temp);
$label = "文字区域";
}
if(($ground_w<$w) || ($ground_h<$h)){
echo "需要加水印的图片的长度或宽度比水印".$label."还小,无法生成水印!";
return;
}
switch($waterPos){
case 0://随机
$posX = rand(0,($ground_w - $w));
$posY = rand(0,($ground_h - $h));
break;
case 1://1为顶端居左
$posX = 0;
$posY = 0;
break;
case 2://2为顶端居中
$posX = ($ground_w - $w) / 2;
$posY = 0;
break;
case 3://3为顶端居右
$posX = $ground_w - $w;
$posY = 0;
break;
case 4://4为中部居左
$posX = 0;
$posY = ($ground_h - $h) / 2;
break;
case 5://5为中部居中
$posX = ($ground_w - $w) / 2;
$posY = ($ground_h - $h) / 2;
break;
case 6://6为中部居右
$posX = $ground_w - $w;
$posY = ($ground_h - $h) / 2;
break;
case 7://7为底端居左
$posX = 0;
$posY = $ground_h - $h;
break;
case 8://8为底端居中
$posX = ($ground_w - $w) / 2;
$posY = $ground_h - $h;
break;
case 9://9为底端居右
$posX = $ground_w - $w;
$posY = $ground_h - $h;
if(!$isWaterImage){
$posY = $ground_h - $h-20;
}
break;
default://随机
$posX = rand(0,($ground_w - $w));
$posY = rand(0,($ground_h - $h));
break;
}
//设定图像的混色模式
imagealphablending($ground_im, true);
if($isWaterImage){//图片水印
//imagecopy($ground_im, $water_im, $posX, $posY, 0, 0, $water_w,$water_h);//拷贝水印到目标文件
//生成混合图像
imagecopymerge($ground_im, $water_im, $posX, $posY, 0, 0, $water_w, $water_h, $alpha);
} else {//文字水印
if( !empty($textColor) && (strlen($textColor)==7)){
$R = hexdec(substr($textColor,1,2));
$G = hexdec(substr($textColor,3,2));
$B = hexdec(substr($textColor,5));
} else {
die("水印文字颜色格式不正确!");
}
imagestring($ground_im, $textFont, $posX, $posY, $waterText, imagecolorallocate($ground_im, $R, $G, $B));
}
//生成水印后的图片
@unlink($groundImage);
switch($ground_info[2]){//取得背景图片的格式
case 1:imagegif($ground_im,$groundImage);break;
case 2:imagejpeg($ground_im,$groundImage);break;
case 3:imagepng($ground_im,$groundImage);break;
default:die($errorMsg);
}
//释放内存
if(isset($water_info)) unset($water_info);
if(isset($water_im)) imagedestroy($water_im);
unset($ground_info);
imagedestroy($ground_im);
}
第二步:找到$json = new Services_JSON();注意有两个地方,不是在alert函数里的那个,添加如下代码:
/ 水印配置开始 /
$water_mark = 1;//1为加水印, 其它为不加
$water_pos = 9;//水印位置,10种状态【0为随机,1为顶端居左,2为顶端居中,3为顶端居右;4为中部居左,5为中部居中,6为中部居右;7为底端居左,8为底端居中,9为底端居】
$water_img = $_SERVER['DOCUMENT_ROOT'].'/upfiles/water.gif';//水印图片,默认填写空,请将图片上传至网站根目录的upfiles下,例: water.gif
$water_alpha = 50;//水印透明度
$water_text = '';//水印字符串,默认填写空;
//$water_fontfile = $_SERVER['DOCUMENT_ROOT'] .'/upfiles/fonts/arial.ttf';//文字水印使用的字体;
if($water_mark == 1){
imageWaterMark($file_path, $water_pos, $water_img, $water_alpha, $water_text, $water_fontfile);
}
/ 水印配置结束 /
以上就是代码的实现了,那么这种方法主要适用于php编程中,其他语言大家可以融会贯通一下,如果还存在不理解的地方,可以在下方留言讨论。
本文由专业的app开发燚轩科技整理发布,如需转载请注明原文作者及出处!
对于如何在图片上添加水印是许多技术人员遇到的一个难题,那大家都见过微信公众号及一些其他技术平台是可以实现这个功能的,但是对于源码是如何实现的,却没有头绪,那么今天就为大家介绍一下关于kindeditor上传图片加水印的方法,看完之后相信各位技术人员就知道了。
第一步:修改upload_json.php文件
在编辑器的/php/目录下可以找到这个文件,新增一个函数:
/*
- 功能:PHP图片水印,水印支持图片或文字
- 参数:
- $groundImage 背景图片,即需要加水印的图片,暂只支持GIF,JPG,PNG格式;
- $waterPos 水印位置,有10种状态,0为随机位置;
- 1为顶端居左,2为顶端居中,3为顶端居右;
- 4为中部居左,5为中部居中,6为中部居右;
- 7为底端居左,8为底端居中,9为底端居右;
- $waterImage 图片水印,即作为水印的图片,暂只支持GIF,JPG,PNG格式;
- $alpha 水印透明度,取值1-100;
- $waterText 文字水印,即把文字作为为水印,支持ASCII码,不支持中文;
- $textFont 文字大小,值为1、2、3、4或5,默认为5;
- $textColor 文字颜色,值为十六进制颜色值,默认为#FF0000(红色);
- $waterImage 和 $waterText 最好不要同时使用,选其中之一即可,优先使用 $waterImage。
- 当$waterImage有效时,参数$waterString、$stringFont、$stringColor均不生效。
- 加水印后的图片的文件名和 $groundImage 一样。
*/
function imageWaterMark($groundImage, $waterPos=0, $waterImage='', $alpha=80, $waterText='', $water_fontfile, $textFont=9, $textColor='#FF0000'){
$isWaterImage = FALSE;
$formatMsg = '不支持该图片格式!请使用GIF、JPG、PNG格式的图片。';
$fontFile = $water_fontfile;
//读取水印文件
if(!empty($waterImage) && file_exists($waterImage)){
$isWaterImage = TRUE;
$water_info = getimagesize($waterImage);
$water_w = $water_info[0];//取得水印图片的宽
$water_h = $water_info[1];//取得水印图片的高
switch($water_info[2]){//取得水印图片的格式
case 1:$water_im = imagecreatefromgif($waterImage);break;
case 2:$water_im = imagecreatefromjpeg($waterImage);break;
case 3:$water_im = imagecreatefrompng($waterImage);break;
default:die($formatMsg);
}
}
//读取背景图片
if(!empty($groundImage) && file_exists($groundImage)){
$ground_info = getimagesize($groundImage);
$ground_w = $ground_info[0];//取得背景图片的宽
$ground_h = $ground_info[1];//取得背景图片的高
switch($ground_info[2]){//取得背景图片的格式
case 1:$ground_im = imagecreatefromgif($groundImage);break;
case 2:$ground_im = imagecreatefromjpeg($groundImage);break;
case 3:$ground_im = imagecreatefrompng($groundImage);break;
default:die($formatMsg);
}
}else{
alert("水印图片不存在!");
}
//水印位置
if($isWaterImage){//图片水印
$w = $water_w;
$h = $water_h;
$label = "图片的";
}else{//文字水印
$temp = imagettfbbox($textFont, 0, $fontFile, $waterText);//取得使用 TrueType 字体的文本的范围
$w = $temp[2] - $temp[6];
$h = $temp[3] - $temp[7];
unset($temp);
$label = "文字区域";
}
if(($ground_w<$w) || ($ground_h<$h)){
echo "需要加水印的图片的长度或宽度比水印".$label."还小,无法生成水印!";
return;
}
switch($waterPos){
case 0://随机
$posX = rand(0,($ground_w - $w));
$posY = rand(0,($ground_h - $h));
break;
case 1://1为顶端居左
$posX = 0;
$posY = 0;
break;
case 2://2为顶端居中
$posX = ($ground_w - $w) / 2;
$posY = 0;
break;
case 3://3为顶端居右
$posX = $ground_w - $w;
$posY = 0;
break;
case 4://4为中部居左
$posX = 0;
$posY = ($ground_h - $h) / 2;
break;
case 5://5为中部居中
$posX = ($ground_w - $w) / 2;
$posY = ($ground_h - $h) / 2;
break;
case 6://6为中部居右
$posX = $ground_w - $w;
$posY = ($ground_h - $h) / 2;
break;
case 7://7为底端居左
$posX = 0;
$posY = $ground_h - $h;
break;
case 8://8为底端居中
$posX = ($ground_w - $w) / 2;
$posY = $ground_h - $h;
break;
case 9://9为底端居右
$posX = $ground_w - $w;
$posY = $ground_h - $h;
if(!$isWaterImage){
$posY = $ground_h - $h-20;
}
break;
default://随机
$posX = rand(0,($ground_w - $w));
$posY = rand(0,($ground_h - $h));
break;
}
//设定图像的混色模式
imagealphablending($ground_im, true);
if($isWaterImage){//图片水印
//imagecopy($ground_im, $water_im, $posX, $posY, 0, 0, $water_w,$water_h);//拷贝水印到目标文件
//生成混合图像
imagecopymerge($ground_im, $water_im, $posX, $posY, 0, 0, $water_w, $water_h, $alpha);
} else {//文字水印
if( !empty($textColor) && (strlen($textColor)==7)){
$R = hexdec(substr($textColor,1,2));
$G = hexdec(substr($textColor,3,2));
$B = hexdec(substr($textColor,5));
} else {
die("水印文字颜色格式不正确!");
}
imagestring($ground_im, $textFont, $posX, $posY, $waterText, imagecolorallocate($ground_im, $R, $G, $B));
}
//生成水印后的图片
@unlink($groundImage);
switch($ground_info[2]){//取得背景图片的格式
case 1:imagegif($ground_im,$groundImage);break;
case 2:imagejpeg($ground_im,$groundImage);break;
case 3:imagepng($ground_im,$groundImage);break;
default:die($errorMsg);
}
//释放内存
if(isset($water_info)) unset($water_info);
if(isset($water_im)) imagedestroy($water_im);
unset($ground_info);
imagedestroy($ground_im);
}
第二步:找到$json = new Services_JSON();注意有两个地方,不是在alert函数里的那个,添加如下代码:
/ 水印配置开始 /
$water_mark = 1;//1为加水印, 其它为不加
$water_pos = 9;//水印位置,10种状态【0为随机,1为顶端居左,2为顶端居中,3为顶端居右;4为中部居左,5为中部居中,6为中部居右;7为底端居左,8为底端居中,9为底端居】
$water_img = $_SERVER['DOCUMENT_ROOT'].'/upfiles/water.gif';//水印图片,默认填写空,请将图片上传至网站根目录的upfiles下,例: water.gif
$water_alpha = 50;//水印透明度
$water_text = '';//水印字符串,默认填写空;
//$water_fontfile = $_SERVER['DOCUMENT_ROOT'] .'/upfiles/fonts/arial.ttf';//文字水印使用的字体;
if($water_mark == 1){
imageWaterMark($file_path, $water_pos, $water_img, $water_alpha, $water_text, $water_fontfile);
}
/ 水印配置结束 /
以上就是代码的实现了,那么这种方法主要适用于php编程中,其他语言大家可以融会贯通一下,如果还存在不理解的地方,可以在下方留言讨论。
本文由专业的app开发燚轩科技整理发布,如需转载请注明原文作者及出处!
收起阅读 »
视频全屏播放 - wap2app教程
需求明确
实现页面中video播放器点击全屏按钮时横屏播放。退出全屏时,切换为竖屏。
在全屏时横向拖动条改变播放进度,纵向拖动改变亮度,声音。
配置说明
点击全屏按钮实现横竖屏功能,wap2app框架已集成不需开发者额外配置。
全屏时是否根据手势改变进度的功能,wap2app提供sitemap配置,开发者可根据需求是否启用,默认为true。
手势功能在sitmap.json里面进行如下配置(在global节点下配置,应用所有的webview均生效;在指定的webview里面配置,只在该webview里面生效):
"easyConfig":{
"video":{
"controls":false /*关闭手势拖动功能,默认为true*/
}
}
注意
- 手势拖动功能仅在HBuilder-alpha版打包的安卓应用生效,正式版下个版本更新生效。
- 如果是使用的h5默认的video元素进行全屏播放,点击video默认的全屏按钮控件,那么可能导致全屏时,手势功能生效但改变的进度无法显示,建议换成div全屏,网上有很多开源的h5播放器,集成一下即可。
- 如果是视频是在iframe里面,则ios可能出现全屏后不能横屏。解决办法如下:
//如果不是同源的页面,暂时无法解决
//如果是同源的页面,用户需要找到iframe里面的video元素,并对其进行如下操作
var videoElem = document.querySelector('video'); //video元素
// video元素开始全屏
videoElem.addEventListener('webkitbeginfullscreen', function() {//目的是监听video的全屏事件,改变手机的横竖屏
plus.screen.lockOrientation('landscape');
});
// video元素全屏结束
videoElem.addEventListener('webkitendfullscreen', function() {
plus.screen.lockOrientation('portrait');
console.log('video元素全屏结束');
});
- 如果是视频是在iframe里面,则滑动功能不生效,暂时该问题还未解决。
- 如果原站的视屏含有拖动功能,可配置"controls":false,关闭wap2app的拖动,也对原站进行判断,在5+环境下不启用原站的滑动功能。
- 开发中发现全屏功能和拖动功能不起作用,且不是以上几点问题,那么检查wap2app版本是否大于等于3.9.4,不是的话尝试升级HBuilder-alpha最新版,确保wap2app版本大于等于3.9.4,升级完还有问题的话,可在论坛发帖提出(记得贴出测试流应用的二维码或写明网址)。
需求明确
实现页面中video播放器点击全屏按钮时横屏播放。退出全屏时,切换为竖屏。
在全屏时横向拖动条改变播放进度,纵向拖动改变亮度,声音。
配置说明
点击全屏按钮实现横竖屏功能,wap2app框架已集成不需开发者额外配置。
全屏时是否根据手势改变进度的功能,wap2app提供sitemap配置,开发者可根据需求是否启用,默认为true。
手势功能在sitmap.json里面进行如下配置(在global节点下配置,应用所有的webview均生效;在指定的webview里面配置,只在该webview里面生效):
"easyConfig":{
"video":{
"controls":false /*关闭手势拖动功能,默认为true*/
}
}
注意
- 手势拖动功能仅在HBuilder-alpha版打包的安卓应用生效,正式版下个版本更新生效。
- 如果是使用的h5默认的video元素进行全屏播放,点击video默认的全屏按钮控件,那么可能导致全屏时,手势功能生效但改变的进度无法显示,建议换成div全屏,网上有很多开源的h5播放器,集成一下即可。
- 如果是视频是在iframe里面,则ios可能出现全屏后不能横屏。解决办法如下:
//如果不是同源的页面,暂时无法解决
//如果是同源的页面,用户需要找到iframe里面的video元素,并对其进行如下操作
var videoElem = document.querySelector('video'); //video元素
// video元素开始全屏
videoElem.addEventListener('webkitbeginfullscreen', function() {//目的是监听video的全屏事件,改变手机的横竖屏
plus.screen.lockOrientation('landscape');
});
// video元素全屏结束
videoElem.addEventListener('webkitendfullscreen', function() {
plus.screen.lockOrientation('portrait');
console.log('video元素全屏结束');
});
- 如果是视频是在iframe里面,则滑动功能不生效,暂时该问题还未解决。
- 如果原站的视屏含有拖动功能,可配置"controls":false,关闭wap2app的拖动,也对原站进行判断,在5+环境下不启用原站的滑动功能。
- 开发中发现全屏功能和拖动功能不起作用,且不是以上几点问题,那么检查wap2app版本是否大于等于3.9.4,不是的话尝试升级HBuilder-alpha最新版,确保wap2app版本大于等于3.9.4,升级完还有问题的话,可在论坛发帖提出(记得贴出测试流应用的二维码或写明网址)。

原生支付 - wap2app教程
支付功能是app重要的组重部分,wap2app支持两种支付方式:原生支付、H5网页支付。
H5支付
H5网页支付主要是通过schame调起客户端达到支付的目的。
hello wap2app示例里的支付示例采用的就是这种(在HBuilder新建wap2app选择hello wap2app模版示例即可体验),在需要支付时将参数传入后台,生成相关参数然后跳转支付页面,在支付页面,会自动通过schame调起客户端进行支付。
开发wap2app时,使用H5支付的方式,改动较少,只需要将页面的url配置到的webview里面即可。
在实际开发中,可将微信或支付宝的支付页面配置到同一个webview里面简单命名为pay,支付成功或失败后新开到另外的webview,命名为paySuccess,在paySuccess这个页面onShow()的时候,关闭pay页面,这样,执行back操作的时候就可以直接退回订单生成的页面,开发者可根据自己的需求设置页面逻辑。
原生支付
wap2app是运行在5+runtime下的应用,所以支持5+plus的各种api,其中包含支付功能。
5+支付可参考教程http://ask.dcloud.net.cn/article/71,也可在HBuilder里面新建Hello H5+,在里面查看相关demo源码。
使用5+的支付方式需要改造原站,判断是5+引擎的环境下,调用pay模块的api即可实现。
document.getElementById("pay").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生支付
} else {
//原有wap站H5支付
}
});
使用原生支付体验较好,支持各种回调,可以得到不同的返回参数,开发起来逻辑更加清晰。
注意
支付功能是app重要的组重部分,wap2app支持两种支付方式:原生支付、H5网页支付。
H5支付
H5网页支付主要是通过schame调起客户端达到支付的目的。
hello wap2app示例里的支付示例采用的就是这种(在HBuilder新建wap2app选择hello wap2app模版示例即可体验),在需要支付时将参数传入后台,生成相关参数然后跳转支付页面,在支付页面,会自动通过schame调起客户端进行支付。
开发wap2app时,使用H5支付的方式,改动较少,只需要将页面的url配置到的webview里面即可。
在实际开发中,可将微信或支付宝的支付页面配置到同一个webview里面简单命名为pay,支付成功或失败后新开到另外的webview,命名为paySuccess,在paySuccess这个页面onShow()的时候,关闭pay页面,这样,执行back操作的时候就可以直接退回订单生成的页面,开发者可根据自己的需求设置页面逻辑。
原生支付
wap2app是运行在5+runtime下的应用,所以支持5+plus的各种api,其中包含支付功能。
5+支付可参考教程http://ask.dcloud.net.cn/article/71,也可在HBuilder里面新建Hello H5+,在里面查看相关demo源码。
使用5+的支付方式需要改造原站,判断是5+引擎的环境下,调用pay模块的api即可实现。
document.getElementById("pay").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生支付
} else {
//原有wap站H5支付
}
});
使用原生支付体验较好,支持各种回调,可以得到不同的返回参数,开发起来逻辑更加清晰。
注意
- 无论那种支付方式都得细看官方的支付文档(微信支付文档,支付宝支付文档),还有就是是H5网页支付和app支付是不一样的流程,需要区分开;
- 网页集成原生支付后,在真机调试不能调起原生支付,检查调用plus的相关代码与服务端代码写得是否正确;
- 真机调试能正常调起原生支付,云端打包后支付失败,检查腾讯与阿里的后台签名密钥appid之类的是否正确;
- 还有其他问题,可在本帖或论坛提出。

自定义View之跑马灯效果
此控件效果不是很难,不过很适合对于自定义View不熟悉的同学练一练,尤其是里面那隐隐约约的数学计算和分析,还是蛮有一番滋味的,有需要的朋友也可以省点时间直接用了。
下面来具体说一下实现思路:
自定义控件属性
按照控件特性,就能很容易的自定义出来,这里不多解释了,属性如下:
实现文本展示与滚动
首先是文本展示,这个很简单,直接在onDraw方法里面调用 drawText(String text, float x, float y, Paint paint)就可以实现,就可以把文本画出来,不过这里面存在个大大的坑,就是这里面的y坐标getHeight() / 2 + textHeight / 2,文本高度计算如果直接调用rect.height()方法获得的值是比实际高度大的,就导致了文本会偏下一点,准确的获取方法为:
private float getContentHeight() { Paint.FontMetrics fontMetrics = paint.getFontMetrics(); return Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2; }
下面就是让他滚动起来,实现方式是开启死循环,在很短的时间内,改变xLocation的值,再调用 postInvalidate() 方法重新绘制文本就实现了滚动。
@Override public void run() { while (isRoll && !TextUtils.isEmpty(content)) { try { Thread.sleep(10); xLocation = xLocation - speed; postInvalidate();//每隔10毫秒重绘视图 } catch (InterruptedException e) { e.printStackTrace(); } } }
实现三种滚动模式
需求的地基已经实现了,很简单,难的就是添枝加叶了,先来看下MarqueeView的重要属性:
private String string;//最终绘制的文本 private float speed = 1;//移动速度 private int textColor = Color.BLACK;//文字颜色,默认黑色 private float textSize = 12;//文字颜色,默认黑色 private int textDistance1= 10;//item间距,dp单位,默认10dp private int textdistance ;//textDistance1 转化而来的px宽度 private String black_count = "";//间距转化成空格距离 private int repetType = REPET_INTERVAL;//滚动模式 public static final int REPET_ONCETIME = 0;//一次结束 public static final int REPET_INTERVAL = 1;//一次结束以后,再继续第二次 public static final int REPET_CONTINUOUS = 2;//紧接着 滚动第二次 private float xLocation = 0;//文本的x坐标 private int contentWidth;//内容的宽度 private float startLocationDistance = 1.0f;//开始的位置选取,百分比来的,距离左边,0~1,0代表不间距,1的话代表,从右面,1/2代表中间。 private boolean isClickStop = false;//点击是否暂停 private boolean isResetLocation = true;//默认为true private boolean isRoll = false;//是否继续滚动 private float oneBlack_width;//空格的宽度
其中repetType是该View的核心围绕点,根据repetType的值,在滚动的时候根据 contentWidth(注意是内容的宽度)与xLocation(滚出屏幕左边的距离)的值的大小来采取相应的措施就可以了。本文由专业app开发报价燚轩科技整理发布,如需转载请注明出处。
此控件效果不是很难,不过很适合对于自定义View不熟悉的同学练一练,尤其是里面那隐隐约约的数学计算和分析,还是蛮有一番滋味的,有需要的朋友也可以省点时间直接用了。
下面来具体说一下实现思路:
自定义控件属性
按照控件特性,就能很容易的自定义出来,这里不多解释了,属性如下:
实现文本展示与滚动
首先是文本展示,这个很简单,直接在onDraw方法里面调用 drawText(String text, float x, float y, Paint paint)就可以实现,就可以把文本画出来,不过这里面存在个大大的坑,就是这里面的y坐标getHeight() / 2 + textHeight / 2,文本高度计算如果直接调用rect.height()方法获得的值是比实际高度大的,就导致了文本会偏下一点,准确的获取方法为:
private float getContentHeight() { Paint.FontMetrics fontMetrics = paint.getFontMetrics(); return Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2; }
下面就是让他滚动起来,实现方式是开启死循环,在很短的时间内,改变xLocation的值,再调用 postInvalidate() 方法重新绘制文本就实现了滚动。
@Override public void run() { while (isRoll && !TextUtils.isEmpty(content)) { try { Thread.sleep(10); xLocation = xLocation - speed; postInvalidate();//每隔10毫秒重绘视图 } catch (InterruptedException e) { e.printStackTrace(); } } }
实现三种滚动模式
需求的地基已经实现了,很简单,难的就是添枝加叶了,先来看下MarqueeView的重要属性:
private String string;//最终绘制的文本 private float speed = 1;//移动速度 private int textColor = Color.BLACK;//文字颜色,默认黑色 private float textSize = 12;//文字颜色,默认黑色 private int textDistance1= 10;//item间距,dp单位,默认10dp private int textdistance ;//textDistance1 转化而来的px宽度 private String black_count = "";//间距转化成空格距离 private int repetType = REPET_INTERVAL;//滚动模式 public static final int REPET_ONCETIME = 0;//一次结束 public static final int REPET_INTERVAL = 1;//一次结束以后,再继续第二次 public static final int REPET_CONTINUOUS = 2;//紧接着 滚动第二次 private float xLocation = 0;//文本的x坐标 private int contentWidth;//内容的宽度 private float startLocationDistance = 1.0f;//开始的位置选取,百分比来的,距离左边,0~1,0代表不间距,1的话代表,从右面,1/2代表中间。 private boolean isClickStop = false;//点击是否暂停 private boolean isResetLocation = true;//默认为true private boolean isRoll = false;//是否继续滚动 private float oneBlack_width;//空格的宽度
其中repetType是该View的核心围绕点,根据repetType的值,在滚动的时候根据 contentWidth(注意是内容的宽度)与xLocation(滚出屏幕左边的距离)的值的大小来采取相应的措施就可以了。本文由专业app开发报价燚轩科技整理发布,如需转载请注明出处。
收起阅读 »
拍照和相册选择上传图片并压缩加thinkcmf后台
经过一番折磨终于搞出来了,经验分享给大家
下面的jscss在mui的案例代码里都有的,我就不上传了,要是没有的可以加我qq,1576245636
这是前台代码
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link href="../../css/mui.min.css" rel="stylesheet" />
<link href="../css/mui.picker.css" rel="stylesheet" />
<link href="../css/mui.poppicker.css" rel="stylesheet" />
<script type="text/javascript" src="../../js/exif.min.js"></script>
</head>
<style>
#picturedisplay{
text-align:center;
margin-top:10%;
}
#image{
width:60%;
height:400px;
}
</style>
<body>
<header class="mui-bar mui-bar-nav" id="headportrait">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left" style="color:red;"></a>
<h1 class="mui-title">公司证书</h1>
<a class="mui-icon mui-icon-camera mui-pull-right" style="color:red;" id="useravatar"></a>
</header>
<div class="mui-content">
<div id="picturedisplay">
<img src="" id="image" />
</div>
</div>
<script src="../../js/mui.min.js"></script>
<script src="../js/mui.picker.js"></script>
<script src="../js/mui.poppicker.js"></script>
<script src="../js/city.data.js" type="text/javascript" charset="utf-8"></script>
<script src="../js/city.data-3.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
mui.init();
mui.plusReady(function(){
var companyinfo = plus.storage.getItem('companyinfo');//企业会员简历
if(!companyinfo){
}else{
var company= JSON.parse(companyinfo);//从本地存储获取简历信息
var picone = document.getElementById("image");
if(!company.com_certificate){
picone.src='../../img/zanwu.gif';
}else{
picone.src='http://www.*******.cn/'+company.com_certificate;
}
}
var route='';//存储图片路径
var userinfo = plus.storage.getItem('userinfo'); //获取本地存储
var user = JSON.parse(userinfo);//转为json
if(!userinfo){
}else{
//时间戳转换
function timestampToTime(timestamp) {
var date = new Date(timestamp * 1000);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
Y = date.getFullYear() + '-';
M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
D = date.getDate();
return Y+M+D;
}
//头像修改
mui("#headportrait").on("tap", "#useravatar", function(e) {
if(mui.os.plus){
var a = [{
title: "拍照"
}, {
title: "从手机相册选择"
}];
plus.nativeUI.actionSheet({
title: "上传证书复印件",
cancel: "取消",
buttons: a
}, function(b) {
switch (b.index) {
case 0:
break;
case 1:
getImage();
break;
case 2:
galleryImg();
break;
default:
break
}
})
}
});
// 拍照方式更换图片
function getImage() {
var c = plus.camera.getCamera();
c.captureImage(function(e) {
plus.io.resolveLocalFileSystemURL(e, function(entry) {
var s = entry.toLocalURL() + "?version=" + new Date().getTime();
console.log(s);
appendFile(s);
//变更大图预览的src
//目前仅有一张图片,暂时如此处理,后续需要通过标准组件实现
document.querySelector("#__mui-imageview__group .mui-slider-item img").src = s + "?version=" + new Date().getTime();;;
}, function(e) {
console.log("读取拍照文件错误:" + e.message);
});
}, function(s) {
console.log("error" + s);
}, {
filename: "_doc/heads.jpg"
})
}
// 选择相册方式更换图片
function galleryImg() {
plus.gallery.pick(function(a) {
plus.io.resolveLocalFileSystemURL(a, function(entry) {
plus.io.resolveLocalFileSystemURL("_doc/", function(root) {
root.getFile("heads.jpg", {}, function(file) {
//文件已存在
file.remove(function() {
console.log("file remove success");
entry.copyTo(root, 'heads.jpg', function(e) {
var e = e.fullPath + "?version=" + new Date().getTime();
appendFile(e);
//变更大图预览的src
//目前仅有一张图片,暂时如此处理,后续需要通过标准组件实现
document.querySelector("#__mui-imageview__group .mui-slider-item img").src = e + "?version=" + new Date().getTime();;
},
function(e) {
console.log('copy image fail:' + e.message);
});
}, function() {
console.log("delete image fail:" + e.message);
});
}, function() {
//文件不存在
entry.copyTo(root, 'heads.jpg', function(e) {
var path = e.fullPath + "?version=" + new Date().getTime();
appendFile(path);
//变更大图预览的src
//目前仅有一张图片,暂时如此处理,后续需要通过标准组件实现
document.querySelector("#__mui-imageview__group .mui-slider-item img").src = path;
},
function(e) {
console.log('copy image fail:' + e.message);
});
});
}, function(e) {
console.log("get _www folder fail");
})
}, function(e) {
console.log("读取拍照文件错误:" + e.message);
});
}, function(a) {}, {
filter: "image"
})
};
// 图片压缩
var f1=null;
function appendFile(path){
var img = new Image();
img.src = path;
img.onload = function () {
var that = this;
//生成比例
var w = that.width,
h = that.height,
scale = w / h;
w =300; //480 你想压缩到多大,改这里
h =400;
//生成canvas
var canvas = document.createElement('canvas');
canvas.height=400;
canvas.width=300;
var ctx = canvas.getContext('2d');
// // $(canvas).attr({width : w, height : h});
ctx.drawImage(that, 0, 0, w, h);
ctx.arc(70,70,35,0,Math.PI*2);
var base64 = canvas.toDataURL('image/jpeg', 2 ); //1最清晰,越低越模糊。有一点不清楚这里明明设置的是jpeg。弹出 base64 开头的一段 data:image/png;却是png。哎开心就好,开心就好
// alert(base64);
f1 =base64; // 把base64数据丢过去,上传要用。
var pic = document.getElementById("image");
pic.src=base64;// 传过来的图片路径在这里用。
upload();//这里丢到img 的 src 里面就能看到效果了
}
}
function upload(){
var wt=plus.nativeUI.showWaiting();
mui.post('http://www.jiukeyou.cn/robot/public/portal/resumeapp/enterprisecertificate',{
files1:f1,
u_id:user.id
},function(data){
//服务器返回响应,根据响应结果,分析是否登录成功;
if(data.code=200){
plus.nativeUI.closeWaiting();
mui.toast('上传成功');
}else{
mui.toast('网络出小差了');
}
},'json'
);
}
}
});
</script>
</body>
</html>
这是后台代码
//更换头像
function changetheavatar(){
$s=cmf_get_root(); //获的服务器路劲
$route="/var/www/html/".$s;
$time =time();//获得当前时间戳
$id=$_POST['userid'];
$fileone=$_POST['avatar'];
$filetwo = substr($fileone,22);
$tmp = base64_decode($filetwo);
$position="/uploads/touxiang/".$time.".jpg";
$fp=$route.$position;
file_put_contents($fp,$tmp); //给图片文件写入数据
$file=Db::table('cmf_user')->where('id',$id)->field('avatar')->find();
if(!empty($file['avatar'])){
$file="/var/www/html".$file['avatar'];
if(file_exists($file)){
unlink($file);
}else{
$update=Db::table('cmf_user')->where('id',$id)->update(['avatar'=>'']);
}
}
$avatar=$s.$position;
$update=Db::table('cmf_user')->where('id',$id)->update(['avatar'=>$avatar]);
if(isset($update)){
$userinfo=Db::table('cmf_user')->where('id',$id)->find();
$array=array('code'=>200,'userinfo'=>$userinfo);
}else{
$array=array('code'=>404);
}
echo json_encode($array);
}
如果有社么不好的地方大神多多指教
经过一番折磨终于搞出来了,经验分享给大家
下面的jscss在mui的案例代码里都有的,我就不上传了,要是没有的可以加我qq,1576245636
这是前台代码
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link href="../../css/mui.min.css" rel="stylesheet" />
<link href="../css/mui.picker.css" rel="stylesheet" />
<link href="../css/mui.poppicker.css" rel="stylesheet" />
<script type="text/javascript" src="../../js/exif.min.js"></script>
</head>
<style>
#picturedisplay{
text-align:center;
margin-top:10%;
}
#image{
width:60%;
height:400px;
}
</style>
<body>
<header class="mui-bar mui-bar-nav" id="headportrait">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left" style="color:red;"></a>
<h1 class="mui-title">公司证书</h1>
<a class="mui-icon mui-icon-camera mui-pull-right" style="color:red;" id="useravatar"></a>
</header>
<div class="mui-content">
<div id="picturedisplay">
<img src="" id="image" />
</div>
</div>
<script src="../../js/mui.min.js"></script>
<script src="../js/mui.picker.js"></script>
<script src="../js/mui.poppicker.js"></script>
<script src="../js/city.data.js" type="text/javascript" charset="utf-8"></script>
<script src="../js/city.data-3.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
mui.init();
mui.plusReady(function(){
var companyinfo = plus.storage.getItem('companyinfo');//企业会员简历
if(!companyinfo){
}else{
var company= JSON.parse(companyinfo);//从本地存储获取简历信息
var picone = document.getElementById("image");
if(!company.com_certificate){
picone.src='../../img/zanwu.gif';
}else{
picone.src='http://www.*******.cn/'+company.com_certificate;
}
}
var route='';//存储图片路径
var userinfo = plus.storage.getItem('userinfo'); //获取本地存储
var user = JSON.parse(userinfo);//转为json
if(!userinfo){
}else{
//时间戳转换
function timestampToTime(timestamp) {
var date = new Date(timestamp * 1000);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
Y = date.getFullYear() + '-';
M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
D = date.getDate();
return Y+M+D;
}
//头像修改
mui("#headportrait").on("tap", "#useravatar", function(e) {
if(mui.os.plus){
var a = [{
title: "拍照"
}, {
title: "从手机相册选择"
}];
plus.nativeUI.actionSheet({
title: "上传证书复印件",
cancel: "取消",
buttons: a
}, function(b) {
switch (b.index) {
case 0:
break;
case 1:
getImage();
break;
case 2:
galleryImg();
break;
default:
break
}
})
}
});
// 拍照方式更换图片
function getImage() {
var c = plus.camera.getCamera();
c.captureImage(function(e) {
plus.io.resolveLocalFileSystemURL(e, function(entry) {
var s = entry.toLocalURL() + "?version=" + new Date().getTime();
console.log(s);
appendFile(s);
//变更大图预览的src
//目前仅有一张图片,暂时如此处理,后续需要通过标准组件实现
document.querySelector("#__mui-imageview__group .mui-slider-item img").src = s + "?version=" + new Date().getTime();;;
}, function(e) {
console.log("读取拍照文件错误:" + e.message);
});
}, function(s) {
console.log("error" + s);
}, {
filename: "_doc/heads.jpg"
})
}
// 选择相册方式更换图片
function galleryImg() {
plus.gallery.pick(function(a) {
plus.io.resolveLocalFileSystemURL(a, function(entry) {
plus.io.resolveLocalFileSystemURL("_doc/", function(root) {
root.getFile("heads.jpg", {}, function(file) {
//文件已存在
file.remove(function() {
console.log("file remove success");
entry.copyTo(root, 'heads.jpg', function(e) {
var e = e.fullPath + "?version=" + new Date().getTime();
appendFile(e);
//变更大图预览的src
//目前仅有一张图片,暂时如此处理,后续需要通过标准组件实现
document.querySelector("#__mui-imageview__group .mui-slider-item img").src = e + "?version=" + new Date().getTime();;
},
function(e) {
console.log('copy image fail:' + e.message);
});
}, function() {
console.log("delete image fail:" + e.message);
});
}, function() {
//文件不存在
entry.copyTo(root, 'heads.jpg', function(e) {
var path = e.fullPath + "?version=" + new Date().getTime();
appendFile(path);
//变更大图预览的src
//目前仅有一张图片,暂时如此处理,后续需要通过标准组件实现
document.querySelector("#__mui-imageview__group .mui-slider-item img").src = path;
},
function(e) {
console.log('copy image fail:' + e.message);
});
});
}, function(e) {
console.log("get _www folder fail");
})
}, function(e) {
console.log("读取拍照文件错误:" + e.message);
});
}, function(a) {}, {
filter: "image"
})
};
// 图片压缩
var f1=null;
function appendFile(path){
var img = new Image();
img.src = path;
img.onload = function () {
var that = this;
//生成比例
var w = that.width,
h = that.height,
scale = w / h;
w =300; //480 你想压缩到多大,改这里
h =400;
//生成canvas
var canvas = document.createElement('canvas');
canvas.height=400;
canvas.width=300;
var ctx = canvas.getContext('2d');
// // $(canvas).attr({width : w, height : h});
ctx.drawImage(that, 0, 0, w, h);
ctx.arc(70,70,35,0,Math.PI*2);
var base64 = canvas.toDataURL('image/jpeg', 2 ); //1最清晰,越低越模糊。有一点不清楚这里明明设置的是jpeg。弹出 base64 开头的一段 data:image/png;却是png。哎开心就好,开心就好
// alert(base64);
f1 =base64; // 把base64数据丢过去,上传要用。
var pic = document.getElementById("image");
pic.src=base64;// 传过来的图片路径在这里用。
upload();//这里丢到img 的 src 里面就能看到效果了
}
}
function upload(){
var wt=plus.nativeUI.showWaiting();
mui.post('http://www.jiukeyou.cn/robot/public/portal/resumeapp/enterprisecertificate',{
files1:f1,
u_id:user.id
},function(data){
//服务器返回响应,根据响应结果,分析是否登录成功;
if(data.code=200){
plus.nativeUI.closeWaiting();
mui.toast('上传成功');
}else{
mui.toast('网络出小差了');
}
},'json'
);
}
}
});
</script>
</body>
</html>
这是后台代码
//更换头像
function changetheavatar(){
$s=cmf_get_root(); //获的服务器路劲
$route="/var/www/html/".$s;
$time =time();//获得当前时间戳
$id=$_POST['userid'];
$fileone=$_POST['avatar'];
$filetwo = substr($fileone,22);
$tmp = base64_decode($filetwo);
$position="/uploads/touxiang/".$time.".jpg";
$fp=$route.$position;
file_put_contents($fp,$tmp); //给图片文件写入数据
$file=Db::table('cmf_user')->where('id',$id)->field('avatar')->find();
if(!empty($file['avatar'])){
$file="/var/www/html".$file['avatar'];
if(file_exists($file)){
unlink($file);
}else{
$update=Db::table('cmf_user')->where('id',$id)->update(['avatar'=>'']);
}
}
$avatar=$s.$position;
$update=Db::table('cmf_user')->where('id',$id)->update(['avatar'=>$avatar]);
if(isset($update)){
$userinfo=Db::table('cmf_user')->where('id',$id)->find();
$array=array('code'=>200,'userinfo'=>$userinfo);
}else{
$array=array('code'=>404);
}
echo json_encode($array);
}
如果有社么不好的地方大神多多指教