
中国技术界小奇迹:HBuilder开发者突破200万
更新:目前HBuilder的开发者数量已经突破600万,以下为历史文章
截止2018年5月2日,国产IDE--HBuilder开发者数量正式突破200万!
在众多开发者的支持下,DCloud公司创造了一个小奇迹,取得中国开发者服务团队历史上未曾达到的成绩。
目前HBuilder与sublime、webstorm、vscode并驾齐驱为前端界四大开发工具,
下图是四大工具在第一季度的百度指数对比,可以看到HBuilder仍然在快速增长。

百度指数链接:http://zhishu.baidu.com/?tpl=trend&type=0&area=0&time=13&word=hbuilder%2Csublime%2Cwebstorm%2Cvscode
开发工具一直是歪果仁称霸的地方,还从来没有国人成功过。
HBuilder在列强环绕的局面里立住了脚,过程非常艰辛。
-
在技术难度方面:
全球掌握代码分析提示技术的团队并不多,DCloud成为其中之一。 -
在商业难度方面:
外国竞品在国外销售许可,赚取足够的利润,或由IT巨头资助开源项目。
而在中国,销售软件许可走不通,投入足够多心血做免费开发工具,需要一个团队有极其执著的梦想。
在技术和商业的多重压力下,我们能取得今天的成绩,确属奇迹。
在此也感谢你的支持和包容,没有你对HBuilder的支持,我们没有信心走到今天。
我们对过去的成绩心存敬畏,但对未来心怀更大梦想。
下一代开发工具HBuilderX已经推出,我们期待这个全新的产品能够超越国外竞品,并成功出海,让外国人用用中国人做的开发工具!
上一代HBuilder由于Eclipse架构问题,还是偏重。
全新的HBuilderX是基于C++架构的:
- 轻巧 (安装包十余M)
- 极速(1秒启动),大文档打开速度更超过其他优秀编辑器
- 强大,HBuilder惯以强大的代码提示著称
对了,HBuilderX还是体验远超竞品的markdown编辑器。
HBuilderX的主页:
欢迎你前来体验,并给我们反馈意见,帮助我们完善这个新产品。
一起共创下一个奇迹!
DCloud作为一个为开发者而生的公司。还有更多服务提供给开发者。
- 1.手机跨屏开发引擎
DCloud的mui,与react native并列成为最受欢迎的跨平台开发框架,遥遥领先于其他框架。
下图是主流跨平台引擎的近一年百度指数对比:
百度指数链接如下:http://zhishu.baidu.com/?tpl=trend&type=0&area=0&time=13&word=mui%2Ccordova%2Creact+native%2Cweex
当然mui也有下一代产品了,叫Uni-App,具备了强大的纯原生渲染引擎,大幅提升了App的性能体验。
并且下一代产品,不止是要跨iOS和Android,还要跨微信、支付宝、百度的小程序、手机厂商快应用,以及输出到浏览器。
移动互联网的下半场,应用的形态不止是iOS和Android的App,更多平台需要支持,我们相信Uni-App会取得比mui更大的成功。
详见
- DCloud除了帮助开发者做出App,还要帮助开发者运营好App
开发者要做成一个App,不止考虑技术。DCloud为其他关键环节也提供了免费工具,降低App的成功门槛。让天下没有难做的App!
D-AD广告联盟,帮助开发者变现赚钱
D-Portal应用发布平台,帮助开发者获取用户
Uni-Stat统计平台,帮助开发者提供运营分析、收集错误和用户反馈,改善留存
DCloud的各项运营服务都与开发工具无缝结合,应用打包时打个勾就能开通广告。
-
2.支持创新、抵制侵权
APICloud公司之前破解抄袭HBuilder源码,并抵赖和欺瞒公众。目前法院已经正式宣判APICloud赔偿DCloud损失并公开道歉,为保护中国自主创新知识产权树立了一个标杆案例。详见
在中国做技术创新本也不易,保护中国的知识产权环境,人人有责。
希望大家抵制侵权、破解。
用正版、用国产,我自豪! -
3.国产的进步
不管是在制造业还是在IT技术领域,“外国的月亮比较圆”的帽子正在被一批批有激情有才华的人摘掉。vue框架、HBuilder、微信小程序,都取得了成功。接下来DCloud团队也会加强对vue、小程序的支持,打造国产闭环开发体验。
不管是免费的开发工具、还是运营工具,DCloud都致力于帮助开发者成功。
开发者的成功是对我们最大的鼓励。
未来仍然任重道远,我们也仍有很多不足,但我们相信在各位共同的支持下,未来会越来越好,下一个奇迹在等待我们!
更新:目前HBuilder的开发者数量已经突破600万,以下为历史文章
截止2018年5月2日,国产IDE--HBuilder开发者数量正式突破200万!
在众多开发者的支持下,DCloud公司创造了一个小奇迹,取得中国开发者服务团队历史上未曾达到的成绩。
目前HBuilder与sublime、webstorm、vscode并驾齐驱为前端界四大开发工具,
下图是四大工具在第一季度的百度指数对比,可以看到HBuilder仍然在快速增长。
百度指数链接:http://zhishu.baidu.com/?tpl=trend&type=0&area=0&time=13&word=hbuilder%2Csublime%2Cwebstorm%2Cvscode
开发工具一直是歪果仁称霸的地方,还从来没有国人成功过。
HBuilder在列强环绕的局面里立住了脚,过程非常艰辛。
-
在技术难度方面:
全球掌握代码分析提示技术的团队并不多,DCloud成为其中之一。 -
在商业难度方面:
外国竞品在国外销售许可,赚取足够的利润,或由IT巨头资助开源项目。
而在中国,销售软件许可走不通,投入足够多心血做免费开发工具,需要一个团队有极其执著的梦想。
在技术和商业的多重压力下,我们能取得今天的成绩,确属奇迹。
在此也感谢你的支持和包容,没有你对HBuilder的支持,我们没有信心走到今天。
我们对过去的成绩心存敬畏,但对未来心怀更大梦想。
下一代开发工具HBuilderX已经推出,我们期待这个全新的产品能够超越国外竞品,并成功出海,让外国人用用中国人做的开发工具!
上一代HBuilder由于Eclipse架构问题,还是偏重。
全新的HBuilderX是基于C++架构的:
- 轻巧 (安装包十余M)
- 极速(1秒启动),大文档打开速度更超过其他优秀编辑器
- 强大,HBuilder惯以强大的代码提示著称
对了,HBuilderX还是体验远超竞品的markdown编辑器。
HBuilderX的主页:
欢迎你前来体验,并给我们反馈意见,帮助我们完善这个新产品。
一起共创下一个奇迹!
DCloud作为一个为开发者而生的公司。还有更多服务提供给开发者。
- 1.手机跨屏开发引擎
DCloud的mui,与react native并列成为最受欢迎的跨平台开发框架,遥遥领先于其他框架。
下图是主流跨平台引擎的近一年百度指数对比:
百度指数链接如下:http://zhishu.baidu.com/?tpl=trend&type=0&area=0&time=13&word=mui%2Ccordova%2Creact+native%2Cweex
当然mui也有下一代产品了,叫Uni-App,具备了强大的纯原生渲染引擎,大幅提升了App的性能体验。
并且下一代产品,不止是要跨iOS和Android,还要跨微信、支付宝、百度的小程序、手机厂商快应用,以及输出到浏览器。
移动互联网的下半场,应用的形态不止是iOS和Android的App,更多平台需要支持,我们相信Uni-App会取得比mui更大的成功。
详见
- DCloud除了帮助开发者做出App,还要帮助开发者运营好App
开发者要做成一个App,不止考虑技术。DCloud为其他关键环节也提供了免费工具,降低App的成功门槛。让天下没有难做的App!
D-AD广告联盟,帮助开发者变现赚钱
D-Portal应用发布平台,帮助开发者获取用户
Uni-Stat统计平台,帮助开发者提供运营分析、收集错误和用户反馈,改善留存
DCloud的各项运营服务都与开发工具无缝结合,应用打包时打个勾就能开通广告。
-
2.支持创新、抵制侵权
APICloud公司之前破解抄袭HBuilder源码,并抵赖和欺瞒公众。目前法院已经正式宣判APICloud赔偿DCloud损失并公开道歉,为保护中国自主创新知识产权树立了一个标杆案例。详见
在中国做技术创新本也不易,保护中国的知识产权环境,人人有责。
希望大家抵制侵权、破解。
用正版、用国产,我自豪! -
3.国产的进步
不管是在制造业还是在IT技术领域,“外国的月亮比较圆”的帽子正在被一批批有激情有才华的人摘掉。vue框架、HBuilder、微信小程序,都取得了成功。接下来DCloud团队也会加强对vue、小程序的支持,打造国产闭环开发体验。
不管是免费的开发工具、还是运营工具,DCloud都致力于帮助开发者成功。
开发者的成功是对我们最大的鼓励。
未来仍然任重道远,我们也仍有很多不足,但我们相信在各位共同的支持下,未来会越来越好,下一个奇迹在等待我们!

模块化开发 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开发报价燚轩科技整理发布,如需转载请注明出处。
收起阅读 »