├── .editorconfig ├── .gitignore ├── README.md ├── config.xml ├── custom-tslint-rules ├── dist │ └── commentSpaceRule.js ├── package.json ├── src │ └── commentSpaceRule.ts ├── tsconfig.json └── tslint.json ├── ionic.config.json ├── package-lock.json ├── package.json ├── scripts └── use-hashname.js ├── src ├── app │ ├── app.component.ts │ ├── app.html │ ├── app.module.ts │ ├── app.scss │ └── main.ts ├── assets │ ├── icon │ │ └── favicon.ico │ ├── img │ │ └── desktop_logo.jpg │ └── lib │ │ └── alloy-lever.js ├── index.html ├── manifest.json ├── model │ ├── FileObj.d.ts │ ├── UserInfo.d.ts │ └── WxUserInfo.d.ts ├── pages │ ├── demo │ │ ├── normal-demo │ │ │ ├── normal-demo.html │ │ │ ├── normal-demo.module.ts │ │ │ ├── normal-demo.scss │ │ │ └── normal-demo.ts │ │ └── wx-jssdk │ │ │ ├── wx-jssdk.html │ │ │ ├── wx-jssdk.module.ts │ │ │ ├── wx-jssdk.scss │ │ │ └── wx-jssdk.ts │ └── index │ │ ├── index.html │ │ ├── index.module.ts │ │ ├── index.scss │ │ └── index.ts ├── providers │ ├── Constants.ts │ ├── FileService.ts │ ├── GlobalData.ts │ ├── Helper.ts │ ├── HttpService.ts │ ├── Logger.ts │ ├── NativeService.ts │ ├── Utils.ts │ └── Validators.ts ├── service-worker.js ├── shared │ └── select-picture │ │ ├── select-picture.html │ │ ├── select-picture.module.ts │ │ ├── select-picture.scss │ │ └── select-picture.ts └── theme │ └── variables.scss ├── tsconfig.json ├── tslint.json └── typings └── index.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | # We recommend you to keep these unchanged 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.tmp 8 | *.tmp.* 9 | log.txt 10 | *.sublime-project 11 | *.sublime-workspace 12 | .vscode/ 13 | npm-debug.log* 14 | 15 | .idea/ 16 | .sass-cache/ 17 | .tmp/ 18 | .versions/ 19 | .sourcemaps/ 20 | coverage/ 21 | node_modules/ 22 | tmp/ 23 | temp/ 24 | hooks/ 25 | www/ 26 | $RECYCLE.BIN/ 27 | 28 | .DS_Store 29 | Thumbs.db 30 | UserInterfaceState.xcuserstate 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | * 本项目只提供ionic微信公众号基础开发功能和微信JsSdk调用demo,更多demo请查看[ionic2_tabs](https://github.com/yanxiaojun617/ionic2_tabs/) 3 | * [简书-ionic开发微信公众号](https://www.jianshu.com/p/66f4f7e928a6) 4 | 5 | # 环境配置 6 | 1. [安装nodejs](http://www.jianshu.com/p/81072e9be3e4) 7 | 8 | * [配置cnpm](http://www.jianshu.com/p/79d4430e0a9d) 9 | 10 | * 安装ionic2 11 | `cnpm i -g ionic` 12 | 13 | 14 | 15 | # 运行app 16 | 1. 使用命令行工具进入到app根目录 17 | 如 `cd ionic2_wx` 18 | 19 | * 安装app依赖 20 | `cnpm i` 21 | 22 | * 运行app 23 | `ionic serve` 24 | 25 | 26 | # 最后 27 | [Ionic学习资源](http://www.jianshu.com/p/7d1577539183) 28 | 29 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ionic2_wx 4 | An awesome Ionic/Cordova app. 5 | Ionic Framework Team 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /custom-tslint-rules/dist/commentSpaceRule.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var tslib_1 = require("tslib"); 4 | var Lint = require("tslint"); 5 | var utils = require("tsutils"); 6 | var ts = require("typescript"); 7 | var RULE_NAME = 'comment-space'; 8 | var Rule = (function (_super) { 9 | tslib_1.__extends(Rule, _super); 10 | function Rule() { 11 | return _super !== null && _super.apply(this, arguments) || this; 12 | } 13 | Rule.prototype.apply = function (sourceFile) { 14 | return this.applyWithFunction(sourceFile, walk); 15 | }; 16 | Rule.FAILURE_STRING = 'comment must start with a space'; 17 | Rule.metadata = { 18 | ruleName: RULE_NAME, 19 | hasFix: false, 20 | description: 'comment must start with a space', 21 | rationale: '', 22 | optionsDescription: '', 23 | options: {}, 24 | optionExamples: [(_a = ["\n \"", "\": true\n "], _a.raw = ["\n \"", "\": true\n "], Lint.Utils.dedent(_a, RULE_NAME))], 25 | typescriptOnly: false, 26 | type: 'style' 27 | }; 28 | return Rule; 29 | }(Lint.Rules.AbstractRule)); 30 | exports.Rule = Rule; 31 | function walk(ctx) { 32 | utils.forEachComment(ctx.sourceFile, function (fullText, _a) { 33 | var kind = _a.kind, pos = _a.pos, end = _a.end; 34 | var start = pos + 2; 35 | if (kind !== ts.SyntaxKind.SingleLineCommentTrivia || 36 | start === end || 37 | fullText[start] === '/' && ctx.sourceFile.referencedFiles.some(function (ref) { return ref.pos >= pos && ref.end <= end; })) { 38 | return; 39 | } 40 | while (fullText[start] === '/') { 41 | ++start; 42 | } 43 | if (start === end) { 44 | return; 45 | } 46 | var commentText = fullText.slice(start, end); 47 | if (/^(?:#(?:end)?region|noinspection\s)/.test(commentText)) { 48 | return; 49 | } 50 | if (commentText[0] !== ' ') { 51 | var fix = [Lint.Replacement.appendText(start, ' ')]; 52 | ctx.addFailure(start, end, Rule.FAILURE_STRING, fix); 53 | } 54 | }); 55 | } 56 | var _a; 57 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWVudFNwYWNlUnVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jb21tZW50U3BhY2VSdWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDZCQUErQjtBQUMvQiwrQkFBaUM7QUFDakMsK0JBQWlDO0FBRWpDLElBQU0sU0FBUyxHQUFHLGVBQWUsQ0FBQztBQUVsQztJQUEwQixnQ0FBdUI7SUFBakQ7O0lBb0JBLENBQUM7SUFIUSxvQkFBSyxHQUFaLFVBQWEsVUFBeUI7UUFDcEMsTUFBTSxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDbEQsQ0FBQztJQWxCYSxtQkFBYyxHQUFHLGlDQUFpQyxDQUFDO0lBRW5ELGFBQVEsR0FBdUI7UUFDM0MsUUFBUSxFQUFFLFNBQVM7UUFDbkIsTUFBTSxFQUFFLEtBQUs7UUFDYixXQUFXLEVBQUUsaUNBQWlDO1FBQzlDLFNBQVMsRUFBRSxFQUFFO1FBQ2Isa0JBQWtCLEVBQUUsRUFBRTtRQUN0QixPQUFPLEVBQUUsRUFBRTtRQUNYLGNBQWMsRUFBRSxxREFBa0IsWUFDN0IsRUFBUyxrQkFDWCxHQUZjLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxLQUM3QixTQUFTLEdBQ1Y7UUFDSixjQUFjLEVBQUUsS0FBSztRQUNyQixJQUFJLEVBQUUsT0FBTztLQUNkLENBQUM7SUFLSixXQUFDO0NBQUEsQUFwQkQsQ0FBMEIsSUFBSSxDQUFDLEtBQUssQ0FBQyxZQUFZLEdBb0JoRDtBQXBCWSxvQkFBSTtBQXNCakIsY0FBYyxHQUEyQjtJQUN2QyxLQUFLLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsVUFBQyxRQUFRLEVBQUUsRUFBZ0I7WUFBZixjQUFJLEVBQUUsWUFBRyxFQUFFLFlBQUc7UUFDN0QsSUFBSSxLQUFLLEdBQUcsR0FBRyxHQUFHLENBQUMsQ0FBQztRQUNwQixFQUFFLENBQUMsQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyx1QkFBdUI7WUFFaEQsS0FBSyxLQUFLLEdBQUc7WUFFYixRQUFRLENBQUMsS0FBSyxDQUFDLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxVQUFVLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxVQUFDLEdBQUcsSUFBSyxPQUFBLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxFQUFoQyxDQUFnQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzVHLE1BQU0sQ0FBQztRQUNULENBQUM7UUFFRCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLEVBQUUsQ0FBQztZQUMvQixFQUFFLEtBQUssQ0FBQztRQUNWLENBQUM7UUFDRCxFQUFFLENBQUMsQ0FBQyxLQUFLLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQztZQUNsQixNQUFNLENBQUM7UUFDVCxDQUFDO1FBQ0QsSUFBTSxXQUFXLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFFL0MsRUFBRSxDQUFDLENBQUMscUNBQXFDLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM1RCxNQUFNLENBQUM7UUFDVCxDQUFDO1FBRUQsRUFBRSxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDM0IsSUFBSSxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLFVBQVUsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztZQUNwRCxHQUFHLENBQUMsVUFBVSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLGNBQWMsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUN2RCxDQUFDO0lBRUgsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDIn0= -------------------------------------------------------------------------------- /custom-tslint-rules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tslint", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "auto-fix:comment-space":"tslint --config tslint.json --fix ../src/**/*.ts" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "tslint": "^5.10.0", 14 | "typescript": "^2.8.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /custom-tslint-rules/src/commentSpaceRule.ts: -------------------------------------------------------------------------------- 1 | import * as Lint from 'tslint'; 2 | import * as utils from 'tsutils'; 3 | import * as ts from 'typescript'; 4 | 5 | const RULE_NAME = 'comment-space'; 6 | 7 | export class Rule extends Lint.Rules.AbstractRule { 8 | public static FAILURE_STRING = 'comment must start with a space'; 9 | 10 | public static metadata: Lint.IRuleMetadata = { 11 | ruleName: RULE_NAME, 12 | hasFix: false, 13 | description: 'comment must start with a space', 14 | rationale: '', 15 | optionsDescription: '', 16 | options: {}, 17 | optionExamples: [Lint.Utils.dedent` 18 | "${RULE_NAME}": true 19 | `], 20 | typescriptOnly: false, 21 | type: 'style' 22 | }; 23 | 24 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { 25 | return this.applyWithFunction(sourceFile, walk); 26 | } 27 | } 28 | 29 | function walk(ctx: Lint.WalkContext) { 30 | utils.forEachComment(ctx.sourceFile, (fullText, {kind, pos, end}) => { 31 | let start = pos + 2; 32 | if (kind !== ts.SyntaxKind.SingleLineCommentTrivia || 33 | // exclude empty comments 34 | start === end || 35 | // exclude /// 36 | fullText[start] === '/' && ctx.sourceFile.referencedFiles.some((ref) => ref.pos >= pos && ref.end <= end)) { 37 | return; 38 | } 39 | // skip all leading slashes 40 | while (fullText[start] === '/') { 41 | ++start; 42 | } 43 | if (start === end) { 44 | return; 45 | } 46 | const commentText = fullText.slice(start, end); 47 | // whitelist //#region and //#endregion and JetBrains IDEs' "//noinspection ..." 48 | if (/^(?:#(?:end)?region|noinspection\s)/.test(commentText)) { 49 | return; 50 | } 51 | 52 | if (commentText[0] !== ' ') { 53 | let fix = [Lint.Replacement.appendText(start, ' ')]; 54 | ctx.addFailure(start, end, Rule.FAILURE_STRING, fix); 55 | } 56 | 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /custom-tslint-rules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "outDir": "./dist", 7 | "removeComments": true, 8 | "inlineSourceMap": true, 9 | "noImplicitAny": true, 10 | "noImplicitThis": true, 11 | "suppressImplicitAnyIndexErrors": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "stripInternal": true, 16 | "sourceMap": false, 17 | "lib": ["es6"], 18 | "skipLibCheck": true, 19 | "importHelpers": true 20 | }, 21 | "include": [ 22 | "./src/**/*" 23 | ], 24 | "exclude": [ 25 | "./node_modules", 26 | "./dist", 27 | "./test/rules/**/*", 28 | "./tools" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /custom-tslint-rules/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "comment-space": true 4 | }, 5 | "rulesDirectory": "./dist" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic2_wx", 3 | "integrations": { 4 | "cordova": {} 5 | }, 6 | "type": "ionic-angular" 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic2_wx", 3 | "version": "1.0.0", 4 | "author": "Ionic Framework", 5 | "homepage": "http://ionicframework.com/", 6 | "private": true, 7 | "scripts": { 8 | "clean": "ionic-app-scripts clean", 9 | "build": "ionic-app-scripts build", 10 | "lint": "ionic-app-scripts lint", 11 | "ionic:build": "ionic-app-scripts build", 12 | "ionic:serve": "ionic-app-scripts serve", 13 | "fix": "tslint --fix 'src/**/*.ts'", 14 | "build:prod:browser": "ionic build --prod --engine browser && node ./scripts/use-hashname.js" 15 | }, 16 | "dependencies": { 17 | "@angular/common": "4.4.4", 18 | "@angular/compiler": "4.4.4", 19 | "@angular/compiler-cli": "4.4.4", 20 | "@angular/core": "4.4.4", 21 | "@angular/forms": "4.4.4", 22 | "@angular/http": "4.4.4", 23 | "@angular/platform-browser": "4.4.4", 24 | "@angular/platform-browser-dynamic": "4.4.4", 25 | "@ionic/storage": "2.1.3", 26 | "fundebug-javascript": "0.3.3", 27 | "ionic-angular": "3.9.2", 28 | "ionicons": "3.0.0", 29 | "rxjs": "5.5.2", 30 | "sw-toolbox": "3.6.0", 31 | "zone.js": "0.8.18" 32 | }, 33 | "devDependencies": { 34 | "@ionic/app-scripts": "3.1.1", 35 | "tslint-config-alloy": "^0.1.0", 36 | "tslint-eslint-rules": "^5.1.0", 37 | "typescript": "2.4.2", 38 | "ws": "3.3.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/use-hashname.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | /** 5 | * 根据文件内容生成hash值,然后重命名文件名 6 | */ 7 | 8 | 9 | var fs = require('fs'), 10 | path = require('path'), 11 | cheerio = require('cheerio'), 12 | revHash = require('rev-hash'); 13 | 14 | /** 15 | * 16 | * @param string fileName 17 | * @returns string 18 | */ 19 | function hashFile(file) { 20 | 21 | // Get file name 22 | var fileName = file.replace(/\.[^/.]+$/, ""); 23 | // Get file extension 24 | var re = /(?:\.([^.]+))?$/; 25 | var fileExtension = re.exec(file)[1]; 26 | 27 | var filePath = path.join(buildDir, file); 28 | var fileHash = revHash(fs.readFileSync(filePath)); 29 | var fileNewName = `${fileName}.${fileHash}.${fileExtension}`; 30 | var fileNewPath = path.join(buildDir, fileNewName); 31 | var fileNewRelativePath = path.join('build', fileNewName); 32 | //Rename file 33 | console.log("cache-busting.js:hashFile:Renaming " + filePath + " to " + fileNewPath); 34 | fs.renameSync(filePath, fileNewPath); 35 | 36 | return fileNewRelativePath; 37 | } 38 | 39 | 40 | var rootDir = path.resolve(__dirname, '../'); 41 | console.log('rootDir',rootDir); 42 | 43 | var wwwRootDir = path.resolve(rootDir, 'www'); 44 | var buildDir = path.join(wwwRootDir, 'build'); 45 | var indexPath = path.join(wwwRootDir, 'index.html'); 46 | console.log(indexPath); 47 | $ = cheerio.load(fs.readFileSync(indexPath, 'utf-8')); 48 | 49 | $('head link[href="build/main.css"]').attr('href', hashFile('main.css')); 50 | $('body script[src="build/main.js"]').attr('src', hashFile('main.js')); 51 | $('body script[src="build/vendor.js"]').attr('src', hashFile('vendor.js')); 52 | 53 | fs.writeFileSync(indexPath, $.html()); 54 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { Nav, Platform } from 'ionic-angular'; 3 | import { Helper } from '../providers/Helper'; 4 | import { NativeService } from '../providers/NativeService'; 5 | 6 | @Component({ 7 | templateUrl: 'app.html' 8 | }) 9 | export class MyApp { 10 | @ViewChild('myNav') nav: Nav; 11 | 12 | constructor(private platform: Platform, 13 | private helper: Helper, 14 | private nativeService: NativeService) { 15 | this.platform.ready().then(() => { 16 | this.nav.setRoot('IndexPage'); // 设置首页 17 | // 如果不需要微信用户信息,则直接调用this.initWxJsSdk(); 18 | /*if (this.nativeService.isWXBrowser()) { // 判断是否微信浏览器 19 | this.helper.initWxUser(wxUserInfo => { 20 | console.log(wxUserInfo); 21 | this.helper.initWxJsSdk(); 22 | }); 23 | } 24 | if (this.nativeService.isWXBrowser()) { // 判断是否微信浏览器 25 | this.helper.initWxJsSdk(); 26 | }*/ 27 | this.helper.alloyLeverInit(); // 本地"开发者工具" 28 | }); 29 | } 30 | 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { ErrorHandler, NgModule } from '@angular/core'; 3 | import { IonicApp, IonicModule } from 'ionic-angular'; 4 | 5 | import { MyApp } from './app.component'; 6 | import { HttpModule } from '@angular/http'; 7 | import { IonicStorageModule } from '@ionic/storage'; 8 | import { NativeService } from '../providers/NativeService'; 9 | import { FileService } from '../providers/FileService'; 10 | import { HttpService } from '../providers/HttpService'; 11 | import { GlobalData } from '../providers/GlobalData'; 12 | import { Helper } from '../providers/Helper'; 13 | import { Utils } from '../providers/Utils'; 14 | import { FUNDEBUG_API_KEY, IS_DEBUG } from '../providers/Constants'; 15 | import { Logger } from '../providers/Logger'; 16 | 17 | // 参考文档:https://docs.fundebug.com/notifier/javascript/framework/ionic2.html 18 | import * as fundebug from 'fundebug-javascript'; 19 | 20 | fundebug.apikey = FUNDEBUG_API_KEY; 21 | fundebug.releasestage = IS_DEBUG ? 'development' : 'production'; // 应用开发阶段,development:开发;production:生产 22 | fundebug.silent = !IS_DEBUG; // 如果暂时不需要使用Fundebug,将silent属性设为true 23 | 24 | export class FunDebugErrorHandler implements ErrorHandler { 25 | handleError(err: any): void { 26 | fundebug.notifyError(err); 27 | console.error(err); 28 | } 29 | } 30 | 31 | @NgModule({ 32 | declarations: [MyApp], 33 | imports: [ 34 | BrowserModule, 35 | HttpModule, 36 | IonicModule.forRoot(MyApp, { 37 | mode: 'ios', // android是'md' 38 | backButtonText: '' 39 | }), 40 | IonicStorageModule.forRoot() 41 | ], 42 | bootstrap: [IonicApp], 43 | entryComponents: [MyApp], 44 | providers: [ 45 | {provide: ErrorHandler, useClass: FunDebugErrorHandler}, 46 | NativeService, 47 | HttpService, 48 | FileService, 49 | Helper, 50 | Utils, 51 | GlobalData, 52 | Logger 53 | ] 54 | }) 55 | export class AppModule { 56 | } 57 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/theming/ 2 | 3 | 4 | // App Global Sass 5 | // -------------------------------------------------- 6 | // Put style rules here that you want to apply globally. These 7 | // styles are for the entire app and not just one component. 8 | // Additionally, this file can be also used as an entry point 9 | // to import other Sass files to be included in the output CSS. 10 | // 11 | // Shared Sass variables, which can be used to adjust Ionic's 12 | // default Sass variables, belong in "theme/variables.scss". 13 | // 14 | // To declare rules for a specific mode, create a child rule 15 | // for the .md, .ios, or .wp mode classes. The mode class is 16 | // automatically applied to the element in the app. 17 | -------------------------------------------------------------------------------- /src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaosan666/ionic2_wx/57223b357f7e671980e6dfb66860c4dd44e806ac/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /src/assets/img/desktop_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaosan666/ionic2_wx/57223b357f7e671980e6dfb66860c4dd44e806ac/src/assets/img/desktop_logo.jpg -------------------------------------------------------------------------------- /src/assets/lib/alloy-lever.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AlloyLever v1.0.2 By dntzhang 3 | * Github: https://github.com/AlloyTeam/AlloyLever 4 | * MIT Licensed. 5 | */ 6 | ;(function (root, factory) { 7 | if(typeof exports === 'object' && typeof module === 'object') 8 | module.exports = factory() 9 | else if(typeof define === 'function' && define.amd) 10 | define([], factory) 11 | else if(typeof exports === 'object') 12 | exports["AlloyLever"] = factory() 13 | else 14 | root["AlloyLever"] = factory() 15 | })(this, function() { 16 | var AlloyLever = {} 17 | AlloyLever.settings = { 18 | cdn:'http://s.url.cn/qqun/qun/qqweb/m/qun/confession/js/vconsole.min.js', 19 | reportUrl: null, 20 | reportPrefix: '', 21 | reportKey: 'msg', 22 | otherReport: null, 23 | entry: null 24 | } 25 | 26 | AlloyLever.store = [] 27 | 28 | var methodList = ['log', 'info', 'warn', 'debug', 'error']; 29 | methodList.forEach(function(item) { 30 | var method = console[item]; 31 | 32 | console[item] = function() { 33 | AlloyLever.store.push({ 34 | logType: item, 35 | logs: arguments 36 | }); 37 | 38 | method.apply(console, arguments); 39 | } 40 | }); 41 | 42 | AlloyLever.logs = [] 43 | AlloyLever.config = function(config){ 44 | for(var i in config){ 45 | if(config.hasOwnProperty(i)){ 46 | AlloyLever.settings[i] = config[i] 47 | } 48 | } 49 | 50 | if(config.entry){ 51 | window.addEventListener('load', function() { 52 | AlloyLever.entry(config.entry) 53 | }) 54 | } 55 | } 56 | 57 | AlloyLever.vConsole = function(show){ 58 | loadScript(AlloyLever.settings.cdn, function() { 59 | 60 | var i = 0, 61 | len = AlloyLever.store.length 62 | 63 | for (; i < len; i++) { 64 | var item = AlloyLever.store[i] 65 | //console[item.type].apply(console, item.logs) 66 | //prevent twice log 67 | item.noOrigin = true 68 | vConsole.pluginList.default.printLog(item) 69 | } 70 | 71 | if(show) { 72 | try { 73 | vConsole.show() 74 | } catch (e) { 75 | 76 | } 77 | 78 | window.addEventListener('load', function () { 79 | vConsole.show() 80 | }) 81 | } 82 | }) 83 | } 84 | 85 | AlloyLever.entry = function(selector) { 86 | var count = 0, 87 | entry =document.querySelector(selector) 88 | if(entry) { 89 | entry.addEventListener('click', function () { 90 | count++ 91 | if (count > 5) { 92 | count = -10000 93 | AlloyLever.vConsole(true) 94 | } 95 | }) 96 | } 97 | } 98 | 99 | window.onerror = function(msg, url, line, col, error) { 100 | var newMsg = msg 101 | 102 | if (error && error.stack) { 103 | newMsg = processStackMsg(error) 104 | } 105 | 106 | if (isOBJByType(newMsg, "Event")) { 107 | newMsg += newMsg.type ? 108 | ("--" + newMsg.type + "--" + (newMsg.target ? 109 | (newMsg.target.tagName + "::" + newMsg.target.src) : "")) : "" 110 | } 111 | 112 | newMsg = (newMsg + "" || "").substr(0,500) 113 | 114 | AlloyLever.logs.push({ 115 | msg: newMsg, 116 | target: url, 117 | rowNum: line, 118 | colNum: col 119 | }) 120 | 121 | if (msg.toLowerCase().indexOf('script error') > -1) { 122 | console.error('Script Error: See Browser Console for Detail') 123 | } else { 124 | console.error(newMsg) 125 | } 126 | 127 | var ss = AlloyLever.settings 128 | if(ss.reportUrl) { 129 | var src = ss.reportUrl + '?' + ss.reportKey + '='+( ss.reportPrefix?('[' + ss.reportPrefix +']'):'')+ newMsg+'&t='+new Date().getTime() 130 | if(ss.otherReport) { 131 | for (var i in ss.otherReport) { 132 | if (ss.otherReport.hasOwnProperty(i)) { 133 | src += '&' + i + '=' + ss.otherReport[i] 134 | } 135 | } 136 | } 137 | new Image().src = src 138 | } 139 | } 140 | 141 | var parameter = getParameter('vconsole') 142 | 143 | if(parameter) { 144 | if (parameter === 'show') { 145 | AlloyLever.vConsole(true) 146 | } else { 147 | AlloyLever.vConsole(false) 148 | } 149 | } 150 | 151 | function loadScript(src, callback){ 152 | var s, 153 | r, 154 | t 155 | r = false 156 | s = document.createElement('script') 157 | s.type = 'text/javascript' 158 | s.src = src 159 | s.onload = s.onreadystatechange = function() { 160 | //console.log( this.readyState ); //uncomment this line to see which ready states are called. 161 | if ( !r && (!this.readyState || this.readyState == 'complete') ) 162 | { 163 | r = true 164 | callback() 165 | } 166 | } 167 | t = document.getElementsByTagName('script')[0] 168 | t.parentNode.insertBefore(s, t) 169 | } 170 | 171 | function getParameter(n) { 172 | var m = window.location.hash.match(new RegExp('(?:#|&)' + n + '=([^&]*)(&|$)')), 173 | result = !m ? '' : decodeURIComponent(m[1]) 174 | return result ||getParameterByName(n) 175 | } 176 | 177 | function getParameterByName(name, url) { 178 | if (!url) url = window.location.href 179 | name = name.replace(/[\[\]]/g, "\\$&") 180 | var regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)"), 181 | results = regex.exec(url) 182 | if (!results) return null 183 | if (!results[2]) return '' 184 | return decodeURIComponent(results[2].replace(/\+/g, " ")) 185 | } 186 | 187 | function isOBJByType(o, type) { 188 | return Object.prototype.toString.call(o) === "[object " + (type || "Object") + "]" 189 | } 190 | 191 | function processStackMsg (error) { 192 | var stack = error.stack 193 | .replace(/\n/gi, "") 194 | .split(/\bat\b/) 195 | .slice(0, 9) 196 | .join("@") 197 | .replace(/\?[^:]+/gi, "") 198 | var msg = error.toString() 199 | if (stack.indexOf(msg) < 0) { 200 | stack = msg + "@" + stack 201 | } 202 | return stack 203 | } 204 | 205 | function getCookie(name){ 206 | var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)") 207 | 208 | if(arr=document.cookie.match(reg)) 209 | return unescape(arr[2]) 210 | else 211 | return null 212 | } 213 | 214 | AlloyLever.getCookie = getCookie 215 | AlloyLever.getParameter= getParameter 216 | AlloyLever.loadScript = loadScript 217 | 218 | return AlloyLever 219 | }); 220 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ionic微信 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic2_wx", 3 | "short_name": "ionic2_wx", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "assets/img/desktop_logo.jpg", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }], 11 | "background_color": "#4e8ef7", 12 | "theme_color": "#4e8ef7" 13 | } 14 | -------------------------------------------------------------------------------- /src/model/FileObj.d.ts: -------------------------------------------------------------------------------- 1 | export interface FileObj { 2 | id?: string; // 主键 3 | origPath?: string; // 原文件路径 4 | thumbPath?: string; // 缩略文件路径(图片类型文件) 5 | name?: string; // 资源名称 6 | createTime?: string; /// 创建时间 7 | size?: string; // 大小 8 | type?: string; // 类型(jpg, gift, png, xls, doc 9 | status?: string; // 状态(1:正常,0:删除) 10 | token?: string; 11 | base64?: string; // base64字符串 12 | parameter?: string; // 自定义参数,原文返回 13 | } 14 | -------------------------------------------------------------------------------- /src/model/UserInfo.d.ts: -------------------------------------------------------------------------------- 1 | export interface UserInfo { 2 | id?: string; 3 | username?: string; 4 | realname?: string; 5 | mobileNumber?: string; 6 | email?: string; 7 | avatarId?: string; 8 | avatarPath?: string; 9 | departmentId?: string; 10 | departmentName?: string; 11 | roles?: Array; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/model/WxUserInfo.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 微信用户信息 3 | * 如果认证方式为snsapi_base,则只能获取openid和unionId(unionId需要公众号绑定了微信开放平台) 4 | * 如果认证方式为snsapi_userinfo,则可以获取微信用户详细信息 5 | */ 6 | export interface WxUserInfo { 7 | city?: ''; // 广州 8 | country?: ''; // 中国 9 | groupId?: ''; 10 | headImgUrl?: ''; // 头像url http://wx.qlogo.cn/mmopen/vi_32/1FrR5TKmODbdxQ45HQMYd5aOWibKbNicN6FIjriawic2ca45EAgOQEoHq6UcZdSgYWB7qoCI8sQ5r16EaJF0ypFbfQ/0 11 | language?: ''; // 语言 zh_CN 12 | nickname?: ''; // 昵称 小军 13 | openId?: ''; // openId of9yW0uyk0peUE8YWSWw4D1OxjdE 14 | province?: ''; // 广东 15 | remark?: ''; 16 | sex?: ''; // 男 17 | sexId?: ''; // 1 18 | subscribe?: ''; 19 | subscribeTime?: ''; 20 | tagIds?: ''; 21 | unionId?: '' // unionId oZqYowiV9glELVFoaLKosTvOwgQU 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/pages/demo/normal-demo/normal-demo.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 请点击6次 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 点击修改页面title 20 | {{title}} 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/pages/demo/normal-demo/normal-demo.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { NormalDemoPage } from './normal-demo'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | NormalDemoPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(NormalDemoPage), 11 | ], 12 | }) 13 | export class NormalDemoPageModule {} 14 | -------------------------------------------------------------------------------- /src/pages/demo/normal-demo/normal-demo.scss: -------------------------------------------------------------------------------- 1 | page-normal-demo { 2 | .warp > .item { 3 | border-bottom: 1px solid #dfdfdf; 4 | padding: 10px; 5 | } 6 | 7 | .warp > .item:first-child { 8 | border-top: 1px solid #dfdfdf; 9 | } 10 | .warp > .item > .name { 11 | color: #555; 12 | padding: 8px 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/demo/normal-demo/normal-demo.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { App, IonicPage } from 'ionic-angular'; 3 | declare var AlloyLever; 4 | 5 | @IonicPage() 6 | @Component({ 7 | selector: 'page-normal-demo', 8 | templateUrl: 'normal-demo.html', 9 | }) 10 | export class NormalDemoPage { 11 | 12 | title = 'title'; 13 | 14 | constructor(public app: App) { 15 | } 16 | 17 | ionViewDidEnter() { 18 | this.app.setTitle(this.title); 19 | AlloyLever.entry('#entry2'); 20 | 21 | } 22 | 23 | updateTitle() { 24 | this.title = 'title-' + Math.floor(Math.random() * 10000); 25 | this.app.setTitle(this.title); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/demo/wx-jssdk/wx-jssdk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 微信js-sdk demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 点击获取当前坐标 13 | {{location|json}} 14 | 15 | 16 | 在腾讯地图查看位置 17 | 18 | 19 | 点击获取网络类型 20 | {{networkType}} 21 | 22 | 23 | 网络是否连接 24 | {{isConnecting}} 25 | 26 | 27 | 点击扫一扫 28 | {{resultStr}} 29 | 30 | 31 | 选择照片 32 | 33 | 上传图片 34 | 35 | 36 | 拨打电话 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/pages/demo/wx-jssdk/wx-jssdk.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { WxJssdk } from './wx-jssdk'; 4 | import { SelectPicturePageModule } from '../../../shared/select-picture/select-picture.module'; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | WxJssdk, 9 | ], 10 | imports: [ 11 | IonicPageModule.forChild(WxJssdk), SelectPicturePageModule 12 | ] 13 | }) 14 | export class WxJssdkModule { 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/demo/wx-jssdk/wx-jssdk.scss: -------------------------------------------------------------------------------- 1 | page-wx-jssdk { 2 | .warp > .item { 3 | border-bottom: 1px solid #dfdfdf; 4 | padding: 10px; 5 | } 6 | 7 | .warp > .item:first-child { 8 | border-top: 1px solid #dfdfdf; 9 | } 10 | .warp > .item > .name { 11 | color: #555; 12 | padding: 8px 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/demo/wx-jssdk/wx-jssdk.ts: -------------------------------------------------------------------------------- 1 | import { IonicPage } from 'ionic-angular'; 2 | import { Component } from '@angular/core'; 3 | import { NativeService } from '../../../providers/NativeService'; 4 | import { FileObj } from '../../../model/FileObj'; 5 | import { Position } from '../../../../typings/index'; 6 | import { FileService } from '../../../providers/FileService'; 7 | 8 | @IonicPage() 9 | @Component({ 10 | selector: 'page-wx-jssdk', 11 | templateUrl: 'wx-jssdk.html', 12 | }) 13 | export class WxJssdk { 14 | 15 | constructor(private nativeService: NativeService, 16 | private fileService: FileService) { 17 | } 18 | 19 | location: Position; 20 | networkType: string = ''; 21 | isConnecting: boolean; 22 | picList: FileObj[] = []; 23 | resultStr: string = ''; 24 | 25 | getUserLocation() { 26 | this.nativeService.getUserLocation().then(res => { 27 | this.location = res; 28 | }); 29 | } 30 | 31 | openLocation() { 32 | let options = { 33 | latitude: 23.119495, // 纬度,浮点数,范围为90 ~ -90 34 | longitude: 113.350912, // 经度,浮点数,范围为180 ~ -180。 35 | name: '广电科技大厦', // 位置名 36 | }; 37 | this.nativeService.openLocation(options); 38 | } 39 | 40 | getNetworkType() { 41 | this.nativeService.getNetworkType().then(res => { 42 | this.networkType = res; 43 | }); 44 | } 45 | 46 | connectionStatus() { 47 | this.nativeService.getConnectionStatus().then(res => { 48 | this.isConnecting = res; 49 | }); 50 | } 51 | 52 | scanQRCode() { 53 | this.nativeService.scanQRCode().then(res => { 54 | this.resultStr = res; 55 | }); 56 | } 57 | 58 | upload() { 59 | this.fileService.uploadMultiByFilePath(this.picList).subscribe(res => { 60 | console.log(res); 61 | }); 62 | } 63 | 64 | callNumber() { 65 | this.nativeService.callNumber('18688498342'); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/pages/index/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ionic微信 5 | 6 | 7 | 8 | 9 | 10 | 11 | 注意:本项目只提供ionic微信公众号基础开发功能和微信JsSdk调用demo,更多demo请查看ionic2_tabs 13 | 14 | 15 | demo 16 | 17 | 18 | 微信JsSdk Demo 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/pages/index/index.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { IndexPage } from './index'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | IndexPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(IndexPage), 11 | ], 12 | }) 13 | export class IndexPageModule {} 14 | -------------------------------------------------------------------------------- /src/pages/index/index.scss: -------------------------------------------------------------------------------- 1 | page-index { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/index/index.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { IonicPage, NavController } from 'ionic-angular'; 3 | 4 | @IonicPage() 5 | @Component({ 6 | selector: 'page-index', 7 | templateUrl: 'index.html', 8 | }) 9 | export class IndexPage { 10 | 11 | constructor(private navCtrl: NavController) { 12 | } 13 | 14 | demo() { 15 | this.navCtrl.push('NormalDemoPage'); 16 | } 17 | 18 | jsSdkDemo() { 19 | this.navCtrl.push('WxJssdk'); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/providers/Constants.ts: -------------------------------------------------------------------------------- 1 | /*----------------------------------------后台Api地址----------------------------------------*/ 2 | export const api_dev = 'http://88.128.18.144:8109/api/'; 3 | export const api_prod = 'http://xiaojun8109.ngrok.cc/api/'; 4 | 5 | /*----------------------------------------文件服务器地址----------------------------------------*/ 6 | export const file_api_dev = 'http://88.128.18.144:3333'; 7 | export const file_api_prod = 'http://172.16.19.86/kit_file_server'; 8 | 9 | /*----------------------------------------微信认证服务api----------------------------------------*/ 10 | export const wx_api_dev = 'http://88.128.19.209:8102/api/ak/prod_ktxx'; // js安全域名: http://88.128.19.209:8100/?vconsole=show 11 | // export const wx_api_dev = 'http://88.128.19.209:8102/api/ak/yanxiaojun';//js安全域名: http://88.128.19.209:8100/?vconsole=show 12 | // export const wx_api_prod = 'http://172.16.19.204:8102/api/ak/prod_en168'; 13 | export const wx_api_prod = 'http://172.16.19.204:8102/api/ak/prod_ktxx'; 14 | 15 | export const IS_DEBUG = true; // 是否开发(调试)模式 16 | export const APP_SERVE_URL = IS_DEBUG ? api_dev : api_prod; 17 | export const FILE_SERVE_URL = IS_DEBUG ? file_api_dev : file_api_prod; 18 | export const WX_SERVE_URL = IS_DEBUG ? wx_api_dev : wx_api_prod; 19 | 20 | export const REQUEST_TIMEOUT = 20000; // 请求超时时间,单位为毫秒 21 | export const FUNDEBUG_API_KEY = 'acb7e1cd807c04a61a9ce78f6370a6b30e06180097498f00a680269ef85a4851'; // 去 https://fundebug.com/ 申请key 22 | 23 | -------------------------------------------------------------------------------- /src/providers/FileService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun617@163.com on 12-23. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | import { HttpService } from './HttpService'; 6 | import { FILE_SERVE_URL } from './Constants'; 7 | import { FileObj } from '../model/FileObj'; 8 | import { Observable } from 'rxjs/Observable'; 9 | import { NativeService } from './NativeService'; 10 | import 'rxjs/add/observable/of'; 11 | import 'rxjs/add/operator/map'; 12 | 13 | /** 14 | * 上传图片到文件服务器 15 | */ 16 | @Injectable() 17 | export class FileService { 18 | constructor(private httpService: HttpService, 19 | private nativeService: NativeService) { 20 | } 21 | 22 | 23 | /** 24 | * 根据文件id删除文件信息 25 | * @param id 26 | * @returns {FileObj} 27 | */ 28 | deleteById(id: string): Observable { 29 | if (!id) { 30 | return Observable.of({}); 31 | } 32 | return this.httpService.get(FILE_SERVE_URL + '/deleteById', {id}); 33 | } 34 | 35 | /** 36 | * 根据ids(文件数组)获取文件信息 37 | * 先从本地缓存中查找文件,然后再去文件服务器上查找文件 38 | * @param ids 39 | * @returns {FileObj[]} 40 | */ 41 | getFileInfoByIds(ids: string[]): Observable { 42 | if (!ids || ids.length == 0) { 43 | return Observable.of([]); 44 | } 45 | return this.httpService.get(FILE_SERVE_URL + '/getByIds', {ids}).map(result => { 46 | if (!result.success) { 47 | this.nativeService.alert(result.msg); 48 | return []; 49 | } else { 50 | for (let fileObj of result.data) { 51 | fileObj.origPath = FILE_SERVE_URL + fileObj.origPath; 52 | fileObj.thumbPath = FILE_SERVE_URL + fileObj.thumbPath; 53 | } 54 | return result.data; 55 | } 56 | }); 57 | } 58 | 59 | 60 | /** 61 | * 根据文件id获取文件信息 62 | * @param id 63 | * @returns {FileObj} 64 | */ 65 | getFileInfoById(id: string): Observable { 66 | if (!id) { 67 | return Observable.of({}); 68 | } 69 | return this.getFileInfoByIds([id]).map(res => { 70 | return res[0] || {}; 71 | }); 72 | } 73 | 74 | /** 75 | * 根据base64(字符串)批量上传图片 76 | * @param fileObjList 数组中的对象必须包含bse64属性 77 | * @returns {FileObj[]} 78 | */ 79 | uploadMultiByBase64(fileObjList: FileObj[]): Observable { 80 | if (!fileObjList || fileObjList.length == 0) { 81 | return Observable.of([]); 82 | } 83 | return this.httpService.post(FILE_SERVE_URL + '/appUpload?directory=ionic2_wx', fileObjList).map(result => { 84 | if (!result.success) { 85 | this.nativeService.alert(result.msg); 86 | return []; 87 | } else { 88 | for (let fileObj of result.data) { 89 | fileObj.origPath = FILE_SERVE_URL + fileObj.origPath; 90 | fileObj.thumbPath = FILE_SERVE_URL + fileObj.thumbPath; 91 | } 92 | return result.data; 93 | } 94 | }); 95 | } 96 | 97 | /** 98 | * 根据base64(字符串)上传单张图片 99 | * @param fileObj 对象必须包含origPath属性 100 | * @returns {FileObj} 101 | */ 102 | uploadByBase64(fileObj: FileObj): Observable { 103 | if (!fileObj.base64) { 104 | return Observable.of({}); 105 | } 106 | return this.uploadMultiByBase64([fileObj]).map(res => { 107 | return res[0] || {}; 108 | }); 109 | } 110 | 111 | /** 112 | * 根据filePath(文件路径)批量上传图片 113 | * @param fileObjList 数组中的对象必须包含origPath属性 114 | * @returns {FileObj[]} 115 | */ 116 | uploadMultiByFilePath(fileObjList: FileObj[]): Observable { 117 | if (fileObjList.length == 0) { 118 | return Observable.of([]); 119 | } 120 | return Observable.create((observer) => { 121 | this.nativeService.showLoading(); 122 | let fileObjs = []; 123 | for (let fileObj of fileObjList) { 124 | this.nativeService.localIdToBase64(fileObj.origPath).then(data => { 125 | fileObjs.push({ 126 | 'base64': data.base64, 127 | 'parameter': fileObj.parameter 128 | }); 129 | if (fileObjs.length === fileObjList.length) { 130 | this.uploadMultiByBase64(fileObjs).subscribe(res => { 131 | this.nativeService.hideLoading(); 132 | observer.next(res); 133 | }); 134 | } 135 | }); 136 | } 137 | }); 138 | } 139 | 140 | /** 141 | * 根据filePath(文件路径)上传单张图片 142 | * @param fileObj 对象必须包含origPath属性 143 | * @returns {FileObj} 144 | */ 145 | uploadByFilePath(fileObj: FileObj): Observable { 146 | if (!fileObj.origPath) { 147 | return Observable.of({}); 148 | } 149 | return this.uploadMultiByFilePath([fileObj]).map(res => { 150 | return res[0] || {}; 151 | }); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/providers/GlobalData.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun on 2017/4/13. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | 6 | @Injectable() 7 | export class GlobalData { 8 | 9 | private _userId: string; // 用户id 10 | private _username: string; // 用户名 11 | private _user; // 用户详细信息 12 | 13 | private _token: string; // token 14 | 15 | // 设置http请求是否显示loading,注意:设置为true,接下来的请求会不显示loading,请求执行完成会自动设置为false 16 | private _showLoading: boolean = true; 17 | 18 | 19 | get userId(): string { 20 | return this._userId; 21 | } 22 | 23 | set userId(value: string) { 24 | this._userId = value; 25 | } 26 | 27 | get username(): string { 28 | return this._username; 29 | } 30 | 31 | set username(value: string) { 32 | this._username = value; 33 | } 34 | 35 | get user() { 36 | return this._user; 37 | } 38 | 39 | set user(value) { 40 | this._user = value; 41 | } 42 | 43 | get token(): string { 44 | return this._token; 45 | } 46 | 47 | set token(value: string) { 48 | this._token = value; 49 | } 50 | 51 | get showLoading(): boolean { 52 | return this._showLoading; 53 | } 54 | 55 | set showLoading(value: boolean) { 56 | this._showLoading = value; 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/providers/Helper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun617@163.com on 12-27. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | import { Storage } from '@ionic/storage'; 6 | import { HttpService } from './HttpService'; 7 | import { WX_SERVE_URL } from './Constants'; 8 | import { Utils } from './Utils'; 9 | import { WxUserInfo } from '../model/WxUserInfo'; 10 | 11 | declare var AlloyLever; 12 | declare var wx; 13 | 14 | /** 15 | * Helper类存放和业务有关的公共方法 16 | * @description 17 | */ 18 | @Injectable() 19 | export class Helper { 20 | 21 | constructor(private httpService: HttpService, private storage: Storage) { 22 | } 23 | 24 | /** 25 | * AlloyLever,一款本地"开发者工具" 26 | * 文档: https://github.com/AlloyTeam/AlloyLever 27 | * http://localhost:8100/#/index?vconsole=show 28 | * http://localhost:8100/#/index?vconsole=hide 29 | */ 30 | alloyLeverInit() { 31 | AlloyLever.config({ 32 | cdn: '//s.url.cn/qqun/qun/qqweb/m/qun/confession/js/vconsole.min.js', // vconsole的CDN地址 33 | /*reportUrl: "// a.qq.com", // 错误上报地址 34 | reportPrefix: 'qun', // 错误上报msg前缀,一般用于标识业务类型 35 | reportKey: 'msg', // 错误上报msg前缀的key,用户上报系统接收存储msg 36 | otherReport: { // 需要上报的其他信息 37 | uin: 491862102 38 | },*/ 39 | entry: '#entry' // 请点击这个DOM元素6次召唤vConsole。// 你可以通过AlloyLever.entry('#entry2')设置多个机关入口召唤神龙 40 | }); 41 | 42 | } 43 | 44 | /** 45 | * 获取微信用户信息 46 | * 生成微信认证url并在微信客户端跳转到该url > 微信服务器认证完成并返回一个code > 根据code获取微信用户信息(openId或用户信息) 47 | */ 48 | initWxUser(callBackFun) { 49 | // 从缓存中获取微信用户信息 50 | this.storage.get('wxUserInfo').then((wxUserInfo: WxUserInfo) => { 51 | if (wxUserInfo) { 52 | callBackFun(wxUserInfo); 53 | } else { 54 | // 获取地址栏code参数值,如果没有code则请求code 55 | let code = Utils.getQueryString('code'); 56 | if (code) { 57 | // 通过code获取用户信息 58 | this.httpService.get(WX_SERVE_URL + '/v1/info/' + code).subscribe((wxUserInfo: WxUserInfo) => { 59 | this.storage.set('wxUserInfo', wxUserInfo); 60 | callBackFun(wxUserInfo); 61 | }); 62 | } else { 63 | // 请求授权地址,跳转该地址浏览器地址栏会生成一个code参数,根据code参数值获取用户信息 64 | // code有两种类型 snsapi_base 和 snsapi_userinfo 默认为snsapi_base,通过该code可以得到用户的openId和unionId 65 | // 当设置scope=snsapi_userinfo 会返回更详细的用户信息,如用户头像,昵称等;注意,设置该类型需要用户同意,会先跳转到一个让用户确认授权的界面 66 | // 'http://mytest.com/workmap/' 67 | debugger; 68 | this.httpService.post(WX_SERVE_URL + '/v1/auth?scope=snsapi_base', window.location.href).subscribe(url => { 69 | window.location.href = url; // 跳转到微信授权地址,会返回一个授权code 70 | }); 71 | } 72 | } 73 | }); 74 | } 75 | 76 | /** 77 | * 初始化微信JS-SDK 78 | * 参数为"js安全域名" 79 | * 官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 80 | */ 81 | initWxJsSdk() { 82 | this.httpService.post(WX_SERVE_URL + '/v1/jsConfig', window.location.href).subscribe(res => { 83 | wx.config({ 84 | debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 85 | appId: res.appId, 86 | nonceStr: res.nonceStr, 87 | signature: res.signature, 88 | timestamp: res.timestamp, 89 | jsApiList: ['chooseImage', 'previewImage', 'getNetworkType', 'uploadImage', 'downloadImage', 'getLocalImgData', 'openLocation', 'getLocation', 'scanQRCode', 'chooseWXPay', 'closeWindow'] // 必填,需要使用的JS接口列表 90 | }); 91 | wx.ready(() => { 92 | console.log('初始化js-sdk成功'); 93 | }); 94 | wx.error(res => { 95 | console.log('初始化js-sdk失败' + res.errMsg); 96 | }); 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/providers/HttpService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun617@163.com on 12-27. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | import { 6 | Headers, 7 | Http, 8 | RequestMethod, 9 | RequestOptions, 10 | RequestOptionsArgs, 11 | Response, 12 | URLSearchParams 13 | } from '@angular/http'; 14 | import { Observable } from 'rxjs/Observable'; 15 | import { TimeoutError } from 'rxjs/util/TimeoutError'; 16 | import 'rxjs/add/operator/timeout'; 17 | import { Utils } from './Utils'; 18 | import { GlobalData } from './GlobalData'; 19 | import { NativeService } from './NativeService'; 20 | import { APP_SERVE_URL, IS_DEBUG, REQUEST_TIMEOUT } from './Constants'; 21 | import { Logger } from './Logger'; 22 | import { Events } from 'ionic-angular'; 23 | 24 | @Injectable() 25 | export class HttpService { 26 | 27 | constructor(public http: Http, 28 | public globalData: GlobalData, 29 | public logger: Logger, 30 | private events: Events, 31 | public nativeService: NativeService) { 32 | } 33 | 34 | public get(url: string, paramMap: any = null, useDefaultApi = true): Observable { 35 | const options = new RequestOptions({ 36 | method: RequestMethod.Get, 37 | search: HttpService.buildURLSearchParams(paramMap) 38 | }); 39 | return useDefaultApi ? this.defaultRequest(url, options) : this.request(url, options); 40 | } 41 | 42 | public post(url: string, body: any = {}, useDefaultApi = true): Observable { 43 | const options = new RequestOptions({ 44 | method: RequestMethod.Post, 45 | body, 46 | headers: new Headers({ 47 | 'Content-Type': 'application/json; charset=UTF-8' 48 | }) 49 | }); 50 | return useDefaultApi ? this.defaultRequest(url, options) : this.request(url, options); 51 | } 52 | 53 | public postFormData(url: string, paramMap: any = null, useDefaultApi = true): Observable { 54 | const options = new RequestOptions({ 55 | method: RequestMethod.Post, 56 | body: HttpService.buildURLSearchParams(paramMap).toString(), 57 | headers: new Headers({ 58 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 59 | }) 60 | }); 61 | return useDefaultApi ? this.defaultRequest(url, options) : this.request(url, options); 62 | } 63 | 64 | public delete(url: string, paramMap: any = null, useDefaultApi = true): Observable { 65 | const options = new RequestOptions({ 66 | method: RequestMethod.Delete, 67 | search: HttpService.buildURLSearchParams(paramMap) 68 | }); 69 | return useDefaultApi ? this.defaultRequest(url, options) : this.request(url, options); 70 | } 71 | 72 | /** 73 | * 一个app可能有多个后台接口服务(api),针对主api添加业务处理,非主api请调用request方法 74 | */ 75 | public defaultRequest(url: string, options: RequestOptionsArgs): Observable { 76 | // 使用默认API:APP_SERVE_URL 77 | url = Utils.formatUrl(url.startsWith('http') ? url : APP_SERVE_URL + url); // tslint:disable-line 78 | // 添加请求头 79 | options.headers = options.headers || new Headers(); 80 | options.headers.append('Authorization', 'Bearer ' + this.globalData.token); 81 | 82 | return Observable.create(observer => { 83 | this.request(url, options).subscribe(res => { 84 | // 后台api返回统一数据,res.code===1表示业务处理成功,否则表示发生异常或业务处理失败 85 | if (res.code === 1) { 86 | observer.next(res.data); 87 | } else { 88 | IS_DEBUG && console.log('%c 请求处理失败 %c', 'color:red', '', 'url', url, 'options', options, 'err', res); 89 | // 401 token无效或过期需要重新登录 90 | if (res.code == 401) { 91 | this.nativeService.showToast('密码已过期,请重新登录'); 92 | this.events.publish('user:reLogin'); // 跳转到登录页面 93 | } else { 94 | this.nativeService.alert(res.msg || '请求失败,请稍后再试!'); 95 | } 96 | observer.error(res.data); 97 | } 98 | }); 99 | }); 100 | } 101 | 102 | public request(url: string, options: RequestOptionsArgs): Observable { 103 | IS_DEBUG && console.log('%c 请求发送前 %c', 'color:blue', '', 'url', url, 'options', options); 104 | this.showLoading(); 105 | return Observable.create(observer => { 106 | this.http.request(url, options).timeout(REQUEST_TIMEOUT).subscribe(res => { 107 | try { 108 | observer.next(res.json()); 109 | } catch (e) { 110 | observer.next(res); 111 | } 112 | IS_DEBUG && console.log('%c 请求发送成功 %c', 'color:green', '', 'url', url, 'options', options, 'res', res); 113 | this.hideLoading(); 114 | }, err => { 115 | observer.error(this.requestFailedHandle(url, options, err)); 116 | this.hideLoading(); 117 | IS_DEBUG && console.log('%c 请求发送失败 %c', 'color:red', '', 'url', url, 'options', options, 'err', err); 118 | }); 119 | }); 120 | } 121 | 122 | /** 123 | * 处理请求失败事件 124 | */ 125 | private requestFailedHandle(url: string, options: RequestOptionsArgs, err: Response) { 126 | if (err instanceof TimeoutError) { 127 | this.nativeService.alert('请求超时,请稍后再试!'); 128 | } else { 129 | const status = err.status; 130 | let msg = '请求发生异常'; 131 | if (status === 0) { 132 | msg = '请求失败,请求响应出错'; 133 | } else if (status === 404) { 134 | msg = '请求失败,未找到请求地址'; 135 | } else if (status === 500) { 136 | msg = '请求失败,服务器出错,请稍后再试'; 137 | } 138 | this.nativeService.alert(msg); 139 | this.logger.httpLog(err, msg, { 140 | url, 141 | status 142 | }); 143 | } 144 | return err; 145 | } 146 | 147 | /** 148 | * 将对象转为查询参数 149 | */ 150 | private static buildURLSearchParams(paramMap): URLSearchParams { 151 | const params = new URLSearchParams(); 152 | if (!paramMap) { 153 | return params; 154 | } 155 | Object.keys(paramMap).forEach(key => { 156 | let val = paramMap[key]; 157 | if (val instanceof Date) { 158 | val = Utils.dateFormat(val, 'yyyy-MM-dd hh:mm:ss'); 159 | } 160 | params.set(key, val); 161 | }); 162 | return params; 163 | } 164 | 165 | private count = 0; // 记录未完成的请求数量,当请求数为0关闭loading,当不为0显示loading 166 | 167 | private showLoading() { 168 | if (++this.count > 0) {// 一旦有请求就弹出loading 169 | this.globalData.showLoading && this.nativeService.showLoading(); 170 | } 171 | } 172 | 173 | private hideLoading() { 174 | if (this.globalData.showLoading) { 175 | // 延迟处理可以避免嵌套请求关闭了第一个loading,突然后弹出第二个loading情况(结合nativeService.showLoading()) 176 | setTimeout(() => { 177 | if (--this.count === 0) {// 当正在请求数为0,关闭loading 178 | this.nativeService.hideLoading(); 179 | } 180 | }, 200); 181 | } else { 182 | this.globalData.showLoading = true; 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/providers/Logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun617@163.com on 07-25. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | import { GlobalData } from './GlobalData'; 6 | import * as fundebug from 'fundebug-javascript'; 7 | 8 | /** 9 | * Utils类存放和业务无关的公共方法 10 | * @description 11 | */ 12 | @Injectable() 13 | export class Logger { 14 | constructor(private globalData: GlobalData) { 15 | } 16 | 17 | log(err: any, action: string, other = null): void { 18 | console.log('Logger.log:action-' + action); 19 | other && console.log(other); 20 | console.log(err); 21 | fundebug.notifyError(err, 22 | { 23 | metaData: { 24 | action, // 操作名称 25 | other, // 其他数据信息 26 | user: {id: this.globalData.userId, name: this.globalData.username} 27 | } 28 | }); 29 | } 30 | 31 | httpLog(err: any, msg: string, other = {}): void { 32 | console.log('Logger.httpLog:msg-' + msg); 33 | fundebug.notifyHttpError(err, 34 | { 35 | metaData: { 36 | action: msg, // 操作名称 37 | other, // 其他数据信息 38 | user: {id: this.globalData.userId, name: this.globalData.username} 39 | } 40 | }); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/NativeService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun617@163.com on 12-27. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | import { ToastController, LoadingController, Loading, AlertController } from 'ionic-angular'; 6 | import { Position } from '../../typings/index'; 7 | import { REQUEST_TIMEOUT } from './Constants'; 8 | import { GlobalData } from './GlobalData'; 9 | 10 | declare var wx; 11 | 12 | @Injectable() 13 | export class NativeService { 14 | private loading: Loading; 15 | private loadingIsOpen: boolean = false; 16 | 17 | constructor(private toastCtrl: ToastController, 18 | private alertCtrl: AlertController, 19 | private globalData: GlobalData, 20 | private loadingCtrl: LoadingController) { 21 | } 22 | 23 | 24 | /** 25 | * 是否微信浏览器 26 | */ 27 | isWXBrowser = (() => { 28 | let isWx = null; 29 | return () => { 30 | if (isWx === null) { 31 | isWx = navigator.userAgent.toLowerCase().indexOf('micromessenger') !== -1; 32 | } 33 | return isWx; 34 | }; 35 | })(); 36 | 37 | /** 38 | * 是否ios浏览器 39 | */ 40 | isIosBrowser = (() => { 41 | let isIos = null; 42 | return () => { 43 | if (isIos === null) { 44 | isIos = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); 45 | } 46 | return isIos; 47 | }; 48 | })(); 49 | 50 | /** 51 | * 只有一个确定按钮的弹出框. 52 | * 如果已经打开则不再打开 53 | */ 54 | alert = (() => { 55 | let isExist = false; 56 | return (title: string, subTitle: string = '', message: string = '', callBackFun = null): void => { 57 | if (!isExist) { 58 | isExist = true; 59 | this.alertCtrl.create({ 60 | title, 61 | subTitle, 62 | message, 63 | buttons: [{ 64 | text: '确定', handler: () => { 65 | isExist = false; 66 | callBackFun && callBackFun(); 67 | } 68 | }], 69 | enableBackdropDismiss: false 70 | }).present(); 71 | } 72 | }; 73 | })(); 74 | 75 | /** 76 | * 统一调用此方法显示提示信息 77 | * @param message 信息内容 78 | * @param duration 显示时长 79 | */ 80 | showToast(message: string = '操作完成', duration: number = 2000): void { 81 | this.toastCtrl.create({ 82 | message, 83 | duration, 84 | position: 'middle', 85 | showCloseButton: false 86 | }).present(); 87 | } 88 | 89 | /** 90 | * 通过浏览器打开url 91 | */ 92 | openUrlByBrowser(url: string): void { 93 | window.location.href = url; 94 | } 95 | 96 | /** 97 | * 统一调用此方法显示loading 98 | * @param content 显示的内容 99 | */ 100 | showLoading(content: string = ''): void { 101 | if (!this.globalData.showLoading) { 102 | return; 103 | } 104 | if (!this.loadingIsOpen) { 105 | this.loadingIsOpen = true; 106 | this.loading = this.loadingCtrl.create({ 107 | content 108 | }); 109 | this.loading.present(); 110 | setTimeout(() => { 111 | this.dismissLoading(); 112 | }, REQUEST_TIMEOUT); 113 | } 114 | } 115 | 116 | /** 117 | * 关闭loading 118 | */ 119 | hideLoading(): void { 120 | if (!this.globalData.showLoading) { 121 | this.globalData.showLoading = true; 122 | } 123 | setTimeout(() => { 124 | this.dismissLoading(); 125 | }, 200); 126 | } 127 | 128 | private dismissLoading() { 129 | if (this.loadingIsOpen) { 130 | this.loadingIsOpen = false; 131 | this.loading.dismiss(); 132 | } 133 | } 134 | 135 | /** 136 | * 判断是否有网络 137 | */ 138 | getConnectionStatus(): Promise { 139 | return new Promise((resolve) => { 140 | this.getNetworkType().then(res => { 141 | resolve(res !== 'none'); 142 | }); 143 | }); 144 | } 145 | 146 | /** 147 | * 获取网络类型 148 | * @returns {Promise} 149 | */ 150 | getNetworkType(): Promise { 151 | return new Promise((resolve) => { 152 | wx.getNetworkType({ 153 | success (res) { 154 | let networkType = res.networkType; // 返回网络类型2g,3g,4g,wifi 155 | resolve(networkType); 156 | }, 157 | fail () { 158 | resolve('none'); 159 | } 160 | }); 161 | }); 162 | } 163 | 164 | /** 165 | * 选择照片或拍照 166 | */ 167 | chooseImage(options = {}): Promise { 168 | let ops = { 169 | count: 9, // 图片数量 170 | sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有 171 | sourceType: ['album', 'camera'], ...options}; 172 | return new Promise((resolve) => { 173 | wx.chooseImage({ 174 | success (res) { 175 | let localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片 176 | resolve(localIds); 177 | }, ...ops}); 178 | }); 179 | } 180 | 181 | 182 | /** 183 | * 根据图片路径把图片转化为base64字符串 184 | */ 185 | localIdToBase64(localId: string): Promise { 186 | let that = this; 187 | return new Promise((resolve) => { 188 | wx.getLocalImgData({ 189 | localId, 190 | success (res) { 191 | let localData = res.localData; 192 | let base64 = that.isIosBrowser() ? localData : 'data:image/jpg;base64,' + localData; 193 | resolve({'localId': localId, 'base64': base64}); 194 | } 195 | }); 196 | }); 197 | } 198 | 199 | /** 200 | * 根据图片路径数组把图片转化为base64字符串 201 | */ 202 | localIdsToBase64(localIds: Array): Promise> { 203 | let result = []; 204 | return new Promise((resolve) => { 205 | for (let localId of localIds) { 206 | this.localIdToBase64(localId).then(data => { 207 | result.push(data); 208 | if (result.length === localIds.length) { 209 | resolve(result); 210 | } 211 | }); 212 | } 213 | }); 214 | } 215 | 216 | /** 217 | * 图片预览 218 | * @param current 219 | * @param urls 220 | */ 221 | previewImage(current = '', urls = []) { 222 | wx.previewImage({ 223 | current, // 当前显示图片的http链接 224 | urls // 需要预览的图片http链接列表 225 | }); 226 | } 227 | 228 | /** 229 | * 调起微信扫一扫接口 230 | * @return {Promise} 231 | */ 232 | scanQRCode(needResult = '1'): Promise { 233 | return new Promise((resolve) => { 234 | wx.scanQRCode({ 235 | needResult, // 0扫描结果由微信处理,1则直接返回扫描结果, 236 | scanType: ['qrCode', 'barCode'], // 可以指定扫二维码还是一维码,默认二者都有 237 | success (res) { 238 | resolve(res.resultStr); 239 | } 240 | }); 241 | }); 242 | } 243 | 244 | /** 245 | * 拨打电话 246 | * @param number 247 | */ 248 | callNumber(number: string): void { 249 | if (this.isIosBrowser) { 250 | let a = document.createElement('a'); 251 | a.href = 'tel:' + number; 252 | a.style.visibility = 'hidden'; 253 | document.body.appendChild(a); 254 | a.click(); 255 | } else { 256 | window.location.href = 'tel:' + number; 257 | } 258 | } 259 | 260 | /** 261 | * 获得用户当前坐标 262 | */ 263 | getUserLocation(type = 'gcj02'): Promise { 264 | return new Promise((resolve) => { 265 | wx.getLocation({ 266 | type, // 关于坐标系https://www.jianshu.com/p/d3dd4149bb0b 267 | success (res) { 268 | resolve({'longitude': res.longitude, 'latitude': res.latitude}); 269 | } 270 | }); 271 | }); 272 | } 273 | 274 | /** 275 | * 在腾讯地图上显示坐标详细 276 | * 可以查看位置,导航 277 | */ 278 | openLocation(options) { 279 | let ops = { 280 | latitude: 0, // 纬度,浮点数,范围为90 ~ -90 281 | longitude: 0, // 经度,浮点数,范围为180 ~ -180。 282 | name: '', // 位置名 283 | address: '', // 地址详情说明 284 | scale: this.isIosBrowser() ? 15 : 18, // 地图缩放级别,整形值,范围从1~28。默认为最大 285 | infoUrl: '', ...options}; 286 | wx.openLocation(ops); 287 | } 288 | 289 | } 290 | -------------------------------------------------------------------------------- /src/providers/Utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun617@163.com on 12-27. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | 6 | declare let hex_md5; 7 | 8 | /** 9 | * Utils类存放和业务无关的公共方法 10 | * @description 11 | */ 12 | @Injectable() 13 | export class Utils { 14 | 15 | static isEmpty(value): boolean { 16 | return value == null || typeof value === 'string' && value.length === 0; 17 | } 18 | 19 | static isNotEmpty(value): boolean { 20 | return !Utils.isEmpty(value); 21 | } 22 | 23 | /** 24 | * 格式“是”or“否” 25 | * @param value 26 | * @returns {string|string} 27 | */ 28 | static formatYesOrNo(value: number | string): string { 29 | return value == 1 ? '是' : (value == '0' ? '否' : null); 30 | } 31 | 32 | 33 | /** 34 | * 日期对象转为日期字符串 35 | * @param date 需要格式化的日期对象 36 | * @param sFormat 输出格式,默认为yyyy-MM-dd 年:y,月:M,日:d,时:h,分:m,秒:s 37 | * @example dateFormat(new Date()) "2017-02-28" 38 | * @example dateFormat(new Date(),'yyyy-MM-dd') "2017-02-28" 39 | * @example dateFormat(new Date(),'yyyy-MM-dd HH:mm:ss') "2017-02-28 13:24:00" ps:HH:24小时制 40 | * @example dateFormat(new Date(),'yyyy-MM-dd hh:mm:ss') "2017-02-28 01:24:00" ps:hh:12小时制 41 | * @example dateFormat(new Date(),'hh:mm') "09:24" 42 | * @example dateFormat(new Date(),'yyyy-MM-ddTHH:mm:ss+08:00') "2017-02-28T13:24:00+08:00" 43 | * @example dateFormat(new Date('2017-02-28 13:24:00'),'yyyy-MM-ddTHH:mm:ss+08:00') "2017-02-28T13:24:00+08:00" 44 | * @returns {string} 45 | */ 46 | static dateFormat(date: Date, sFormat: String = 'yyyy-MM-dd'): string { 47 | const time = { 48 | Year: 0, 49 | TYear: '0', 50 | Month: 0, 51 | TMonth: '0', 52 | Day: 0, 53 | TDay: '0', 54 | Hour: 0, 55 | THour: '0', 56 | hour: 0, 57 | Thour: '0', 58 | Minute: 0, 59 | TMinute: '0', 60 | Second: 0, 61 | TSecond: '0', 62 | Millisecond: 0 63 | }; 64 | time.Year = date.getFullYear(); 65 | time.TYear = String(time.Year).substr(2); 66 | time.Month = date.getMonth() + 1; 67 | time.TMonth = time.Month < 10 ? '0' + time.Month : String(time.Month); 68 | time.Day = date.getDate(); 69 | time.TDay = time.Day < 10 ? '0' + time.Day : String(time.Day); 70 | time.Hour = date.getHours(); 71 | time.THour = time.Hour < 10 ? '0' + time.Hour : String(time.Hour); 72 | time.hour = time.Hour < 13 ? time.Hour : time.Hour - 12; 73 | time.Thour = time.hour < 10 ? '0' + time.hour : String(time.hour); 74 | time.Minute = date.getMinutes(); 75 | time.TMinute = time.Minute < 10 ? '0' + time.Minute : String(time.Minute); 76 | time.Second = date.getSeconds(); 77 | time.TSecond = time.Second < 10 ? '0' + time.Second : String(time.Second); 78 | time.Millisecond = date.getMilliseconds(); 79 | 80 | return sFormat.replace(/yyyy/ig, String(time.Year)) 81 | .replace(/yyy/ig, String(time.Year)) 82 | .replace(/yy/ig, time.TYear) 83 | .replace(/y/ig, time.TYear) 84 | .replace(/MM/g, time.TMonth) 85 | .replace(/M/g, String(time.Month)) 86 | .replace(/dd/ig, time.TDay) 87 | .replace(/d/ig, String(time.Day)) 88 | .replace(/HH/g, time.THour) 89 | .replace(/H/g, String(time.Hour)) 90 | .replace(/hh/g, time.Thour) 91 | .replace(/h/g, String(time.hour)) 92 | .replace(/mm/g, time.TMinute) 93 | .replace(/m/g, String(time.Minute)) 94 | .replace(/ss/ig, time.TSecond) 95 | .replace(/s/ig, String(time.Second)) 96 | .replace(/fff/ig, String(time.Millisecond)); 97 | } 98 | 99 | /** 100 | * 每次调用sequence加1 101 | * @type {()=>number} 102 | */ 103 | static getSequence = (() => { 104 | let sequence = 1; 105 | return () => { 106 | return ++sequence; 107 | }; 108 | })(); 109 | 110 | /** 111 | * 返回字符串长度,中文计数为2 112 | * @param str 113 | * @returns {number} 114 | */ 115 | static strLength(str: string): number { 116 | let len = 0; 117 | for (let i = 0, length = str.length; i < length; i++) { 118 | str.charCodeAt(i) > 255 ? len += 2 : len++; 119 | } 120 | return len; 121 | } 122 | 123 | /** 124 | * 把url中的双斜杠替换为单斜杠 125 | * 如:http://localhost:8080//api//demo.替换后http://localhost:8080/api/demo 126 | * @param url 127 | * @returns {string} 128 | */ 129 | static formatUrl(url = ''): string { 130 | let index = 0; 131 | if (url.startsWith('http')) { 132 | index = 7; 133 | } 134 | return url.substring(0, index) + url.substring(index).replace(/\/\/*/g, '/'); 135 | } 136 | 137 | 138 | static sessionStorageGetItem(key: string) { 139 | const jsonString = sessionStorage.getItem(key); 140 | if (jsonString) { 141 | return JSON.parse(jsonString); 142 | } 143 | return null; 144 | } 145 | 146 | static sessionStorageSetItem(key: string, value: any) { 147 | sessionStorage.setItem(key, JSON.stringify(value)); 148 | } 149 | 150 | static sessionStorageRemoveItem(key: string) { 151 | sessionStorage.removeItem(key); 152 | } 153 | 154 | static sessionStorageClear() { 155 | sessionStorage.clear(); 156 | } 157 | 158 | /** 159 | * 字符串加密 160 | * @param str 161 | * @returns {any} 162 | */ 163 | static hex_md5(str: string) { 164 | return hex_md5(str); 165 | } 166 | 167 | /** 产生一个随机的32位长度字符串 */ 168 | static uuid() { 169 | let text = ''; 170 | const possible = 'abcdef0123456789'; 171 | for (let i = 0; i < 19; i++) { 172 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 173 | } 174 | return text + new Date().getTime(); 175 | } 176 | 177 | /** 178 | * 获取地址栏参数 179 | * @param name 180 | * @returns {any} 181 | */ 182 | static getQueryString(name): string { 183 | let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); 184 | let r = window.location.search.substr(1).match(reg); 185 | return r != null ? r[2] : ''; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/providers/Validators.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yanxiaojun617@163.com on 3-12. 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | import { AbstractControl, Validators as angularValidators } from '@angular/forms'; 6 | 7 | @Injectable() 8 | export class Validators extends angularValidators { 9 | 10 | /*E-mail*/ 11 | static email = (control: AbstractControl) => { 12 | return Validators.validatorsByPattern('email', control, '([a-z0-9]*[-_]?[a-z0-9]+)*@([a-z0-9]*[-_]?[a-z0-9]+)+[\.][a-z]{2,3}([\.][a-z]{2})?'); 13 | } 14 | 15 | /*手机号码*/ 16 | static phone = (control: AbstractControl) => { 17 | return Validators.validatorsByPattern('phone', control, '1[0-9]{10,10}'); 18 | } 19 | 20 | /*中文*/ 21 | static chinese = (control: AbstractControl) => { 22 | return Validators.validatorsByPattern('chinese', control, '[(\u4e00-\u9fa5)]+'); 23 | } 24 | 25 | /*英文、数字包括下划线*/ 26 | static legallyNamed = (control: AbstractControl) => { 27 | return Validators.validatorsByPattern('legallyNamed', control, '[A-Za-z0-9_]+'); 28 | } 29 | 30 | private static validatorsByPattern = (name: string, control: AbstractControl, pattern: string) => { 31 | const validatorFn = Validators.pattern(pattern)(control); 32 | if (validatorFn != null) { 33 | validatorFn[name] = validatorFn['pattern']; 34 | } 35 | return validatorFn; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check out https://googlechromelabs.github.io/sw-toolbox/ for 3 | * more info on how to use sw-toolbox to custom configure your service worker. 4 | */ 5 | 6 | 7 | 'use strict'; 8 | importScripts('./build/sw-toolbox.js'); 9 | 10 | self.toolbox.options.cache = { 11 | name: 'ionic-cache' 12 | }; 13 | 14 | // pre-cache our key assets 15 | self.toolbox.precache( 16 | [ 17 | './build/main.js', 18 | './build/vendor.js', 19 | './build/main.css', 20 | './build/polyfills.js', 21 | 'index.html', 22 | 'manifest.json' 23 | ] 24 | ); 25 | 26 | // dynamically cache any other local assets 27 | self.toolbox.router.any('/*', self.toolbox.fastest); 28 | 29 | // for any other requests go to the network, cache, 30 | // and then only use that cached resource if your user goes offline 31 | self.toolbox.router.default = self.toolbox.networkFirst; 32 | -------------------------------------------------------------------------------- /src/shared/select-picture/select-picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | × 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/shared/select-picture/select-picture.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { SelectPicturePage } from './select-picture'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | SelectPicturePage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(SelectPicturePage) 11 | ], 12 | exports: [ 13 | SelectPicturePage 14 | ] 15 | }) 16 | export class SelectPicturePageModule { 17 | } 18 | -------------------------------------------------------------------------------- /src/shared/select-picture/select-picture.scss: -------------------------------------------------------------------------------- 1 | page-select-picture { 2 | .pictures { 3 | position: relative; 4 | float: left; 5 | width: 50px; 6 | height: 50px; 7 | margin: 3px; 8 | background-color: #f1f1f1; 9 | } 10 | .pictures img { 11 | width: 100%; 12 | height: 100%; 13 | object-fit: cover; 14 | border-radius: 6px; 15 | } 16 | 17 | .add { 18 | font-size: 25px; 19 | color: #5c5c5c; 20 | text-align: center; 21 | border: solid 1px #e0dfe4; 22 | line-height: 54px; 23 | vertical-align: middle; 24 | border-radius: 6px; 25 | } 26 | 27 | .remove { 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | 32 | position: absolute; 33 | right: -5px; 34 | top: -5px; 35 | width: 18px; 36 | height: 18px; 37 | border-radius: 18px; 38 | font-size: 16px; 39 | color: #fff; 40 | background-color: red; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/shared/select-picture/select-picture.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | import { IonicPage, AlertController } from 'ionic-angular'; 3 | import { FileObj } from '../../model/FileObj'; 4 | import { NativeService } from '../../providers/NativeService'; 5 | import { FileService } from '../../providers/FileService'; 6 | import { GlobalData } from '../../providers/GlobalData'; 7 | import { DomSanitizer } from '@angular/platform-browser'; 8 | 9 | /** 10 | * 自定义添加/预览图片组件 11 | * @description 12 | * @example 13 | * @example 14 | */ 15 | @IonicPage() 16 | @Component({ 17 | selector: 'page-select-picture', 18 | templateUrl: 'select-picture.html', 19 | }) 20 | export class SelectPicturePage { 21 | @Input() max: number = 4; // 最多可选择多少张图片,默认为4张 22 | 23 | @Input() allowAdd: boolean = true; // 是否允许新增 24 | 25 | @Input() allowDelete: boolean = true; // 是否允许删除 26 | 27 | @Input() fileObjList: FileObj[] = []; // 图片列表,与fileObjListChange形成双向数据绑定 28 | @Output() fileObjListChange = new EventEmitter(); 29 | 30 | constructor(private alertCtrl: AlertController, 31 | private fileService: FileService, 32 | private globalData: GlobalData, 33 | private nativeService: NativeService, 34 | public sanitizer: DomSanitizer) { 35 | } 36 | 37 | addPicture() {// 新增照片 38 | this.nativeService.chooseImage({ 39 | count: (this.max - this.fileObjList.length) 40 | }).then(localIds => { 41 | // 由于ios显示直接图片路径有bug,所以缩略图使用base64字符串用于显示 42 | if (this.nativeService.isIosBrowser()) { 43 | this.nativeService.localIdsToBase64(localIds).then(res => { 44 | for (let data of res) { 45 | let fileObj = {'origPath': data.localId, 'thumbPath': data.base64}; 46 | this.fileObjList.push(fileObj); 47 | } 48 | this.fileObjListChange.emit(this.fileObjList); 49 | }); 50 | } else { 51 | for (let localId of localIds) { 52 | let fileObj = {'origPath': localId, 'thumbPath': localId}; 53 | this.fileObjList.push(fileObj); 54 | } 55 | this.fileObjListChange.emit(this.fileObjList); 56 | } 57 | }); 58 | } 59 | 60 | deletePicture(i) {// 删除照片 61 | if (!this.allowDelete) { 62 | return; 63 | } 64 | this.alertCtrl.create({ 65 | title: '确认删除?', 66 | buttons: [{text: '取消'}, 67 | { 68 | text: '确定', 69 | handler: () => { 70 | let delArr = this.fileObjList.splice(i, 1); 71 | this.fileObjListChange.emit(this.fileObjList); 72 | let delId = delArr[0].id; 73 | if (delId) { 74 | this.globalData.showLoading = false; 75 | this.fileService.deleteById(delId); 76 | } 77 | } 78 | } 79 | ] 80 | }).present(); 81 | } 82 | 83 | viewerPicture(index) {// 照片预览 84 | let urls = []; 85 | let current = ''; 86 | for (let i = 0, len = this.fileObjList.length; i < len; i++) { 87 | let origPath = this.fileObjList[i].origPath; 88 | if (i == index) { 89 | current = origPath; 90 | } 91 | urls.push(origPath); 92 | } 93 | this.nativeService.previewImage(current, urls); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/v2/theming/ 3 | $font-path: "../assets/fonts"; 4 | 5 | @import "ionic.globals"; 6 | 7 | 8 | // Shared Variables 9 | // -------------------------------------------------- 10 | // To customize the look and feel of this app, you can override 11 | // the Sass variables found in Ionic's source scss files. 12 | // To view all the possible Ionic variables, see: 13 | // http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/ 14 | 15 | 16 | 17 | 18 | // Named Color Variables 19 | // -------------------------------------------------- 20 | // Named colors makes it easy to reuse colors on various components. 21 | // It's highly recommended to change the default colors 22 | // to match your app's branding. Ionic uses a Sass map of 23 | // colors so you can add, rename and remove colors as needed. 24 | // The "primary" color is the only required color in the map. 25 | 26 | $colors: ( 27 | primary: #488aff, 28 | secondary: #32db64, 29 | danger: #f53d3d, 30 | light: #f4f4f4, 31 | dark: #222 32 | ); 33 | 34 | 35 | // App iOS Variables 36 | // -------------------------------------------------- 37 | // iOS only Sass variables can go here 38 | 39 | 40 | 41 | 42 | // App Material Design Variables 43 | // -------------------------------------------------- 44 | // Material Design only Sass variables can go here 45 | 46 | 47 | 48 | 49 | // App Windows Variables 50 | // -------------------------------------------------- 51 | // Windows only Sass variables can go here 52 | 53 | 54 | 55 | 56 | // App Theme 57 | // -------------------------------------------------- 58 | // Ionic apps can have different themes applied, which can 59 | // then be future customized. This import comes last 60 | // so that the above variables are used and Ionic's 61 | // default are overridden. 62 | 63 | 64 | $toolbar-background: color($colors, primary); 65 | $tabs-ios-background: #f8f8f8; 66 | $tabs-md-background: #f8f8f8; 67 | $searchbar-ios-input-height: 33px; 68 | $searchbar-ios-background-color: color($colors, primary); 69 | 70 | $hairlines-width: 1px; 71 | $list-border-color: #e0dfe4; 72 | 73 | @import "ionic.theme.default"; 74 | 75 | // start 自定义全局css,具体使用例子,请全局搜索class名 76 | .validation-failed{ 77 | float: right; 78 | padding-right: 10px; 79 | color: #ff0000; 80 | font-size: 1.2rem; 81 | letter-spacing: 1px; 82 | } 83 | 84 | .clear-fix:before, 85 | .clear-fix:after { 86 | display: table; 87 | content: " "; 88 | } 89 | .clear-fix:after{ 90 | clear: both; 91 | } 92 | // end 自定义全局css 93 | 94 | 95 | // Ionicons 96 | // -------------------------------------------------- 97 | // The premium icon font for Ionic. For more info, please see: 98 | // http://ionicframework.com/docs/v2/ionicons/ 99 | 100 | @import "ionic.ionicons"; 101 | 102 | 103 | // Fonts 104 | // -------------------------------------------------- 105 | 106 | @import "roboto"; 107 | @import "noto-sans"; 108 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "sourceMap": true, 14 | "target": "es5", 15 | "typeRoots": [ 16 | "../node_modules/@types" 17 | ] 18 | }, 19 | "include": [ 20 | "src/**/*.ts" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "src/**/*.spec.ts", 25 | "src/**/__tests__/*.ts" 26 | ], 27 | "compileOnSave": false, 28 | "atom": { 29 | "rewriteTsconfig": false 30 | } 31 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-alloy", 3 | "rules": { 4 | "no-parameter-properties":false, 5 | "no-redundant-jsdoc":false, 6 | "triple-equals":false, 7 | "no-debugger": false, 8 | "member-ordering":false, 9 | "no-this-assignment": [true, {"allowed-names": ["^self$","^that$"], "allow-destructuring": true}], 10 | "no-unused-expression": [true, "allow-fast-null-checks", "allow-new"], 11 | "comment-space": true // 自定义的rule,单行注释必须以空格开头,可以自动修复 12 | }, 13 | "rulesDirectory": [ 14 | "node_modules/tslint-eslint-rules/dist/rules", 15 | "custom-tslint-rules/dist" 16 | ] 17 | } 18 | 19 | 20 | // https://github.com/palantir/tslint 21 | // https://github.com/buzinas/tslint-eslint-rules 22 | // https://github.com/AlloyTeam/tslint-config-alloy 23 | 24 | // 自动修复命令 25 | // tslint --fix 'src/**/*.ts' 26 | 27 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Position { 2 | longitude: string, 3 | latitude: string 4 | } 5 | --------------------------------------------------------------------------------