├── .gitignore
├── doc
├── theme
│ ├── face
│ │ └── components
│ │ │ └── icons
│ │ │ ├── font
│ │ │ ├── icon.eot
│ │ │ ├── icon.ttf
│ │ │ └── icon.woff
│ │ │ ├── fonts
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ └── fontawesome-webfont.woff2
│ │ │ └── index.html
│ ├── template
│ │ ├── post.ejs
│ │ ├── home.ejs
│ │ └── default.ejs
│ ├── head.html
│ ├── media
│ │ ├── index.html
│ │ ├── logo.svg
│ │ ├── filter.svg
│ │ ├── user.svg
│ │ ├── browser-5.svg
│ │ ├── keyword.svg
│ │ ├── smartphone.svg
│ │ ├── loupe.svg
│ │ ├── online-shop.svg
│ │ ├── browser-4.svg
│ │ ├── bar-chart.svg
│ │ ├── online-shop-1.svg
│ │ ├── mailing.svg
│ │ ├── analytics-1.svg
│ │ ├── promotion.svg
│ │ ├── browser-3.svg
│ │ ├── line-chart.svg
│ │ ├── browsers-1.svg
│ │ ├── search.svg
│ │ ├── growth.svg
│ │ ├── rating.svg
│ │ ├── share.svg
│ │ ├── update.svg
│ │ ├── aim.svg
│ │ ├── browser-1.svg
│ │ ├── browser.svg
│ │ ├── stats.svg
│ │ ├── optimization.svg
│ │ ├── laptop.svg
│ │ ├── speedometer.svg
│ │ ├── ads.svg
│ │ ├── blog.svg
│ │ ├── browsers-2.svg
│ │ ├── activity.svg
│ │ └── browser-2.svg
│ ├── foot.html
│ └── polyfill
│ │ └── es6-promise.auto.js
├── intro.md
├── README.md
├── sidebar.html
├── jquery.demo.js
└── API.md
├── compile
├── open.js
├── webpack.online.version.config.js
├── getConfig.js
├── webpack.karma.config.js
├── livereload.js
├── deploy-gh.js
├── server.js
├── webpack.online.config.js
├── karma.ci.js
├── karma.conf.js
└── webpack.config.js
├── test
└── basic.test.js
├── developers-to-read.md
├── lib
└── index.js
├── README.md
├── compile.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | output
3 | yarn.lock
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/font/icon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/font/icon.eot
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/font/icon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/font/icon.ttf
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/font/icon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/font/icon.woff
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onface/page/HEAD/doc/theme/face/components/icons/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/compile/open.js:
--------------------------------------------------------------------------------
1 | var open = require("open")
2 | var config = require('./getConfig')()
3 | setTimeout(function () {
4 | open("http://127.0.0.1:" + config.serverPort + '?若不显示则刷新页面')
5 | }, 500)
6 |
--------------------------------------------------------------------------------
/compile/webpack.online.version.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var iPackage = require('../package.json')
3 | var webpackConfig = require('./webpack.online.config.js')
4 | webpackConfig.output.path = path.join(__dirname, '../output', iPackage.version)
5 | module.exports = webpackConfig
6 |
--------------------------------------------------------------------------------
/compile/getConfig.js:
--------------------------------------------------------------------------------
1 | var hashPort = require('hash-to-port')
2 | var iPackgae = require('../package.json')
3 | module.exports = function () {
4 | return {
5 | serverPort: hashPort('server' + iPackgae.name),
6 | livereloadServerPort: hashPort('livereloadServer' + iPackgae.name)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/compile/webpack.karma.config.js:
--------------------------------------------------------------------------------
1 | var webpackConfig = require('./webpack.config.js')
2 | webpackConfig = {
3 | module: webpackConfig.module,
4 | devtool: '#inline-source-map',
5 | externals: {
6 | 'react/lib/ExecutionEnvironment': true,
7 | 'react/lib/ReactContext': true
8 | }
9 | }
10 | module.exports = webpackConfig
11 |
--------------------------------------------------------------------------------
/compile/livereload.js:
--------------------------------------------------------------------------------
1 | var livereload = require('livereload')
2 | var config = require('./getConfig')()
3 | var path = require('path')
4 | var lrserver = livereload.createServer({
5 | port: config.livereloadServerPort,
6 | delay: 100,
7 | exclusions: [/\\.git\//, /\\.svn\//, /\\.hg\//, /\.js/]
8 | })
9 | var watchPath = path.join(__dirname, '../output')
10 | lrserver.watch(watchPath)
11 | console.log('Livereload: http://127.0.0.1:' + config.livereloadServerPort + '\n\t' + watchPath)
12 |
--------------------------------------------------------------------------------
/compile/deploy-gh.js:
--------------------------------------------------------------------------------
1 | var ghpages = require('gh-pages')
2 | var path = require('path')
3 | var fs = require('fs')
4 | ghpages.publish(path.join(__dirname, '../output'), {
5 | message: 'onface/module auto-generated commit',
6 | logger: function(message) {
7 | console.log(message)
8 | },
9 | add: true
10 | }, function(err) {
11 | console.log(err)
12 | if (err) {throw err}
13 | console.log('push success')
14 | });
15 | console.log('git branch gh-pages pushing...')
16 |
--------------------------------------------------------------------------------
/compile/server.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var compiler = webpack(require('./webpack.config.js'))
3 | var express = require('express')
4 | var path = require('path')
5 | var app = express();
6 | require('./livereload.js')
7 | app.use(express.static(path.join(__dirname, '../output')))
8 | app.use(require("webpack-dev-middleware")(compiler, {
9 | publicPath: '/',
10 | }));
11 | app.use(require("webpack-hot-middleware")(compiler))
12 | var config = require('./getConfig')()
13 | app.listen(config.serverPort)
14 |
--------------------------------------------------------------------------------
/test/basic.test.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 | Enzyme.configure({ adapter: new Adapter() });
4 | import { expect } from 'chai';
5 | import { mount } from 'enzyme';
6 |
7 | import Button from "../lib/index"
8 | import React, { Component } from "react"
9 |
10 | describe('', () => {
11 | it('has className', () => {
12 | expect(
13 | mount().find('.face-btn').length
14 | ).to.equal(1)
15 | });
16 | })
17 |
--------------------------------------------------------------------------------
/developers-to-read.md:
--------------------------------------------------------------------------------
1 | ## Automated Builds
2 |
3 | You can use travis & saucelabs test your code.
4 |
5 | ### Travis environment Variables
6 |
7 | [travis](https://travis-ci.org/)
8 |
9 | 
10 |
11 | ## SAUCE_USERNAME & SAUCE_ACCESS_KEY
12 |
13 | [Saucelabs signup OSS](https://saucelabs.com/beta/signup/OSS/None)
14 |
15 | 
16 |
--------------------------------------------------------------------------------
/doc/intro.md:
--------------------------------------------------------------------------------
1 | # 指引
2 |
3 | > 使用一个组件或模块之前,应当知道它能解决的问题是什么,不能解决的问题是什么。使用的最佳实践是什么?
4 |
5 |
6 | 项目中最常见的需求就是AJAX列表,不同项目的列表千变万化。很难找到两个项目的列表的界面和操作方式相同。
7 |
8 | 通过分析列表的的业务需求和代码逻辑,`face-page` 封装出列表的生命周期和配置参数 。让开发人员可以快速的基于 `face-page` 开发出一个AJAX列表。
9 |
10 | 你只需要编写列表的视觉代码和配置生命周期,所有的逻辑细节都会由 `face-page` 处理。
11 |
12 | > 可能你会想直接对着设计稿把AJAX列表给的业务逻辑代码写出来就好了,非常简单。
13 |
14 | > 但是请你考虑: 一个项目可能会有几十个列表,每个列表的界面和功能都不完全是一样的。
15 |
16 | > 唯一相同的只是他们会展示数据,用户进行操作后数据会变动。 并且还可能需要加入一些诸如“清空搜索结果” “搜索结果为空的” 的复杂的功能。
17 |
18 | > 直接编码还要解决[数据片段条件问题](./doc/README.md#数据片段条件)
19 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 | # 示例
2 |
3 | ## jQuery
4 |
5 | ````html
6 |
10 | loading...
11 |
12 |
13 | ````
14 |
15 | ````code
16 | {
17 | title: 'jQuery',
18 | desc: '生命周期: `getQuery` `willFetch` `fetch` `willFetch` `render` 触发:`query`',
19 | html: '',
20 | js: './jquery.demo.js',
21 | source: './jquery.demo.js',
22 | open: true
23 | }
24 | ````
25 |
--------------------------------------------------------------------------------
/doc/theme/template/post.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | <%- title %>
11 |
12 |
13 |
14 |
15 | <%- content %>
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/compile/webpack.online.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var iPackage = require('../package.json')
3 | var webpack = require('webpack')
4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
5 | var webpackConfig = require('./webpack.config.js')
6 | Object.keys(webpackConfig.entry).forEach(function (key, index) {
7 | webpackConfig.entry[key] = webpackConfig.entry[key].filter(function (item) {
8 | return item !== 'webpack-hot-middleware/client'
9 | })
10 | })
11 |
12 | webpackConfig.plugins = [
13 | new webpack.NamedModulesPlugin(),
14 | new webpack.NoEmitOnErrorsPlugin(),
15 | new webpack.DefinePlugin({
16 | 'process.env': {
17 | NODE_ENV: JSON.stringify('production')
18 | }
19 | })
20 | ]
21 | webpackConfig.output.publicPath = '/' + iPackage.$repository + '/' + iPackage.version
22 | module.exports = webpackConfig
23 |
--------------------------------------------------------------------------------
/doc/theme/template/home.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | <%- title %>
11 |
12 |
13 |
14 |
15 | <%- content %>
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/doc/sidebar.html:
--------------------------------------------------------------------------------
1 |
4 |
7 |
8 |
9 |
10 | 文档
11 |
12 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/doc/theme/head.html:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/doc/theme/template/default.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | <%- title %>
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
27 | <%- content %>
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import spare from "sparejs"
2 | import extend from "extend"
3 | class Page {
4 | constructor(options) {
5 | const self = this
6 | self.options = options
7 | }
8 | }
9 | const _query = function (data, callback) {
10 | const self = this
11 | if (typeof callback !== 'function') {
12 | callback = () => {}
13 | }
14 | data = spare(data, {})
15 | let queryData = self.options.getQuery()
16 | let fetchQuery = extend(true, {}, queryData, data)
17 | let rendered = false
18 | new Promise(self.options.willFetch).then(function () {
19 | self.options.fetch(
20 | fetchQuery,
21 | function render(...arg) {
22 | rendered = true
23 | self.options.render.apply(undefined , arg)
24 | },
25 | function didFetch() {
26 | // 给 rendered 留出时间赋值
27 | setTimeout(function () {
28 | self.options.didFetch()
29 | callback({end: 'didFetch', rendered})
30 | }, 0)
31 | }
32 | )
33 | }).catch(function () {
34 | callback({
35 | end: 'willFetch',
36 | rendered
37 | })
38 | })
39 |
40 | }
41 |
42 | Page.prototype.query = function (data, callback) {
43 | const self = this
44 | // 增加延迟是因为 react 框架的 setState 是异步的。
45 | // 有时会出现 setState 已经调用但是 getQuery 获取的值还是老的
46 | setTimeout(function () {
47 | _query.bind(self)(data, callback)
48 | }, 0)
49 | }
50 | export default Page
51 | module.exports = Page
52 |
--------------------------------------------------------------------------------
/compile/karma.ci.js:
--------------------------------------------------------------------------------
1 | var karmaConf = require('./karma.conf.js')
2 | var iPackage = require('../package.json')
3 | var userConfig = require('../compile.js')
4 | var extend = require('extend')
5 | module.exports = function(config) {
6 | if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {
7 | var sauceUserErrorMsg = 'Make sure the SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables are set.'
8 | console.error('---------------')
9 | console.error(sauceUserErrorMsg)
10 | console.error('---------------')
11 | throw new Error(sauceUserErrorMsg)
12 | }
13 | var sauceLabsConfig = {
14 | frameworks: ['jasmine'],
15 | reporters: ['progress', 'saucelabs'],
16 | sauceLabs: {
17 | testName: iPackage.name + ' unit tests',
18 | tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,
19 | recordVideo: false,
20 | testName: iPackage.name,
21 | recordScreenshots: false,
22 | connectOptions: {
23 | 'no-ssl-bump-domains': 'all', // Ignore SSL error on Android emulator
24 | port: 5757,
25 | logfile: 'sauce_connect.log'
26 | },
27 | public: 'public',
28 | build: process.env.CIRCLE_BUILD_NUM || process.env.SAUCE_BUILD_ID || Date.now()
29 | },
30 | captureTimeout: (1000*60)*5,
31 | browserNoActivityTimeout: (1000*60)*5,
32 | customLaunchers: userConfig.test.launchers,
33 | browsers: Object.keys(userConfig.test.launchers),
34 | singleRun: true
35 | }
36 | config.plugins.push('karma-sauce-launcher')
37 | config.set(extend(true, {}, karmaConf, sauceLabsConfig))
38 | }
39 |
--------------------------------------------------------------------------------
/compile/karma.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var iPackage = require('../package.json')
3 | var hashToPort = require('hash-to-port')
4 | var compileConfig = require('../compile.js')
5 | var testServerPort = hashToPort(iPackage.name + 'onface/test:server')
6 | var webpackConfig = require('./webpack.karma.config')
7 | var karmaConf = function () {
8 | return {
9 | basePath: '../',
10 | frameworks: ['jasmine'],
11 | files: [
12 | require.resolve('../doc/theme/polyfill/lt-ie10.js'),
13 | require.resolve('../doc/theme/polyfill/lte-ie11.js'),
14 | require.resolve('../doc/theme/polyfill/es6-promise.auto.js'),
15 | ].concat(compileConfig.test.files),
16 | preprocessors: {
17 | // add webpack as preprocessor
18 | 'lib/**/*.js': ['webpack', 'sourcemap'],
19 | 'lib/**/*.vue': ['webpack', 'sourcemap'],
20 | 'lib/**/*.css': ['webpack', 'sourcemap'],
21 | 'test/**/*.js': ['webpack', 'sourcemap']
22 | },
23 | webpack: webpackConfig,
24 | webpackServer: {
25 | noInfo: true
26 | },
27 | plugins: [
28 | 'karma-webpack',
29 | 'karma-jasmine',
30 | 'karma-sourcemap-loader',
31 | 'karma-chrome-launcher'
32 | ],
33 | babelPreprocessor: {
34 | options: compileConfig.babel
35 | },
36 | reporters: ['progress'],
37 | port: testServerPort,
38 | colors: true,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | processKillTimeout: 1200000
43 | }
44 | }
45 | module.exports = function(config) {
46 | config.set(karmaConf(config));
47 | };
48 |
--------------------------------------------------------------------------------
/doc/theme/media/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
19 |
20 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/doc/theme/media/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
32 |
--------------------------------------------------------------------------------
/doc/theme/media/filter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # face-page
2 |
3 |
4 |
5 |
8 |
9 |
10 |
face-page
11 |
12 | 封装 搜索/列表/分页 逻辑代码
13 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 
28 |
29 | 定制开发
30 | 提供底层接口便于二次开发
31 |
32 | |
33 |
34 |
35 | 
36 |
37 | 场景丰富
38 | 涵盖绝大多数业务场景,提供业务样例代码。
39 |
40 | |
41 |
42 |
43 | 
44 |
45 | 逻辑封装
46 | 提供业务逻辑封装,快速开发
47 |
48 | |
49 |
50 |
51 |
52 |
53 |
54 | Creator
55 |
56 |
69 |
--------------------------------------------------------------------------------
/doc/theme/media/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/compile/webpack.config.js:
--------------------------------------------------------------------------------
1 | var glob = require("glob")
2 | var webpack = require('webpack')
3 | var path = require('path')
4 | var compileConfig = require('../compile')
5 | var entryMap = {}
6 | var entryFiles = glob.sync('**/**demo.js')
7 | var iPackage = require('../package.json')
8 | entryFiles.forEach(function (filePath) {
9 | if (/^(output|node_modules)/.test(filePath)) { return }
10 | var name = filePath
11 | entryMap[name] = [
12 | './' + filePath,
13 | 'webpack-hot-middleware/client'
14 | ]
15 | })
16 | module.exports = {
17 | entry: entryMap,
18 | output: {
19 | publicPath: '/',
20 | path: path.join(__dirname, '../output'),
21 | filename: '[name]',
22 | chunkFilename: '__chunk/[id]-[hash]-chunk.js'
23 | },
24 | resolve: {
25 | alias: {
26 | 'vue': 'vue/dist/vue.js'
27 | }
28 | },
29 | devServer: {
30 | hot: true
31 | },
32 | plugins: [
33 | new webpack.HotModuleReplacementPlugin(),
34 | new webpack.NamedModulesPlugin(),
35 | new webpack.NoEmitOnErrorsPlugin(),
36 | ],
37 | module: {
38 | rules: [
39 | {
40 | test: /.(less|css)$/,
41 | use: [
42 | {
43 | loader: 'style-loader'
44 | },
45 | {
46 | loader: 'css-loader'
47 | },
48 | {
49 | loader: 'less-loader'
50 | }
51 | ]
52 | },
53 | {
54 | test: /\.vue$/,
55 | exclude: /node_modules/,
56 | use: [
57 | 'vue-loader'
58 | ]
59 | },
60 | {
61 | test: /\.js$/,
62 | exclude: /node_modules/,
63 | use: [
64 | {
65 | loader: 'babel-loader',
66 | options: compileConfig.babel
67 | }
68 | ]
69 | },
70 | { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=1&minetype=application/font-woff&name=__media/[path][name]-[hash:6].[ext]' },
71 | { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=1&minetype=application/font-woff&name=__media/[path][name]-[hash:6].[ext]' },
72 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=1&minetype=application/octet-stream&name=__media/[path][name]-[hash:6].[ext]' },
73 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
74 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=1&minetype=image/svg+xml&name=__media/[path][name]-[hash:6].[ext]' },
75 | { test: /\.(png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/i, loader: 'url-loader?limit=1&name=__media/[path][name]-[hash:6].[ext]'},
76 | { test: /\.json$/, loader: 'json-loader' }
77 | ]
78 | },
79 | resolve: {
80 | alias: (function () {
81 | var alias = compileConfig.alias
82 | alias[iPackage.name] = path.resolve(__dirname, '../')
83 | return alias
84 | })()
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/doc/jquery.demo.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery')
2 | var Page = require('face-page')
3 | var message = require('face-message')
4 | window.list = null
5 | $(function () {
6 | var $search = $('#search')
7 | var $list = $('#list')
8 | var $page = $('#page')
9 | var $loading = $('#loading')
10 | var page = new Page({
11 | getQuery: function () {
12 | var serializeArray = $search.serializeArray()
13 | var data = {}
14 | serializeArray.forEach(function (item) {
15 | data[item.name] = item.value
16 | })
17 | return data
18 | },
19 | willFetch: function (resolve, reject) {
20 | if ($loading.is(":visible")) {
21 | message.info('防止重复提交')
22 | reject()
23 | return
24 | }
25 | $loading.show()
26 | resolve()
27 | },
28 | didFetch: function () {
29 | $loading.hide()
30 | },
31 | fetch: function (queryData, render, didFetch) {
32 | queryData.page = queryData.page || 1
33 | $.ajax({
34 | url: 'http://echo.onface.live/onface/echo/mock/list?$delay=300',
35 | type: 'get',
36 | data: queryData,
37 | dataType: 'json'
38 | }).done(function (res) {
39 | render(res, queryData)
40 | }).fail(function () {
41 | message.error('网络出错,请刷新重试')
42 | })
43 | .always(function () {
44 | didFetch()
45 | })
46 | },
47 | render: function (res, queryData) {
48 | // render list
49 | var html = res.data.map(function (item) {
50 | return '¥' + item.cash + '-' + item.name + ''
51 | })
52 | if (res.data.length === 0) {
53 | html = '暂无数据 '
54 | }
55 | $list.html(html)
56 |
57 | // render paging
58 | var paging = []
59 | res.page = Number(res.page)
60 | if (res.pageCount != 1) {
61 | if (res.page != 1) {
62 | paging.push('')
63 | }
64 | paging.push(res.page + '/' + res.pageCount)
65 | if (res.page != res.pageCount) {
66 | paging.push('')
67 | }
68 | }
69 | $page.html(paging.join(''))
70 |
71 | }
72 | })
73 | $('#search').on('submit', function (e) {
74 | e.preventDefault()
75 | page.query({}, function (info) {
76 | console.log('query callback', info)
77 | })
78 | })
79 | $('body').on('click', '#clearSearch', function () {
80 | $('#search').get(0).reset()
81 | page.query()
82 | })
83 | $('#page').on('click', 'button', function (e) {
84 | page.query({
85 | page: $(this).data('page')
86 | })
87 | })
88 | page.query()
89 | })
90 |
--------------------------------------------------------------------------------
/doc/theme/media/browser-5.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/keyword.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/smartphone.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/loupe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/online-shop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/compile.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | module.exports = {
3 | less: {
4 | plugins: []
5 | },
6 | babel: {
7 | presets: [
8 | "es2015"
9 | ],
10 | plugins: [
11 | [
12 | "transform-react-jsx",
13 | {"pragma": "require(\"react\").createElement"}
14 | ],
15 | "transform-flow-strip-types",
16 | "syntax-flow",
17 | "syntax-jsx",
18 | "transform-react-display-name",
19 | "transform-decorators-legacy",
20 | "transform-class-properties"
21 | ]
22 | },
23 | test: {
24 | files: [
25 | (function () {
26 | if (process.env.files) {
27 | return process.env.files
28 | }
29 | else {
30 | return 'test/**/*.test.js'
31 | }
32 | })()
33 | ],
34 | launchers: {
35 | // ie family
36 | sl_ie_9: {
37 | base: 'SauceLabs',
38 | browserName: 'internet explorer',
39 | platform: 'Windows 7',
40 | version: '9'
41 | },
42 | sl_ie_10: {
43 | base: 'SauceLabs',
44 | browserName: 'internet explorer',
45 | platform: 'Windows 8',
46 | version: '10'
47 | },
48 | sl_ie_11: {
49 | base: 'SauceLabs',
50 | browserName: 'internet explorer',
51 | platform: 'Windows 8.1',
52 | version: '11'
53 | },
54 | sl_edge: {
55 | base: 'SauceLabs',
56 | browserName: 'MicrosoftEdge',
57 | platform: 'Windows 10'
58 | },
59 | // the cool kids
60 | sl_chrome: {
61 | base: 'SauceLabs',
62 | browserName: 'chrome',
63 | platform: 'Windows 7'
64 | },
65 | sl_firefox: {
66 | base: 'SauceLabs',
67 | browserName: 'firefox',
68 | platform: 'Windows 7'
69 | },
70 | sl_mac_safari: {
71 | base: 'SauceLabs',
72 | browserName: 'safari',
73 | platform: 'Windows 7'
74 | },
75 | sl_mac_safari: {
76 | base: 'SauceLabs',
77 | browserName: 'safari',
78 | platform: 'OS X 10.10'
79 | }, // mobile
80 | sl_ios_safari_8: {
81 | base: 'SauceLabs',
82 | browserName: 'iphone',
83 | version: '8.4'
84 | },
85 | sl_ios_safari_9: {
86 | base: 'SauceLabs',
87 | browserName: 'iphone',
88 | version: '9.3'
89 | },
90 | sl_android_4_4: {
91 | base: 'SauceLabs',
92 | browserName: 'android',
93 | version: '4.4'
94 | },
95 | sl_android_5_1: {
96 | base: 'SauceLabs',
97 | browserName: 'android',
98 | version: '5.1'
99 | }
100 | }
101 | },
102 | fis: function (fis) {
103 |
104 | },
105 | alias: {
106 | 'vue': 'vue/dist/vue.js',
107 | [path.resolve('./lib/index.css')]: path.resolve('./lib/index.less')
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/doc/theme/media/browser-4.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "face-page",
3 | "main": "lib/index.js",
4 | "version": "0.1.0",
5 | "description": "Encapsulate list logic code | 封装列表逻辑代码",
6 | "keywords": "",
7 | "scripts": {
8 | "dev": "rm -rf output && fis3 release -w -d ./output -r ./ -f compile/fis-conf.js",
9 | "s": "node ./compile/open && node ./compile/server",
10 | "build:1": "rm -rf ./output && fis3 release build -d ./output -r ./ -f compile/fis-conf.js && fis3 release buildversion -d ./output -r ./ -f compile/fis-conf.js",
11 | "build:2": "webpack --progress --config ./compile/webpack.online.config.js && webpack --progress --config ./compile/webpack.online.version.config.js",
12 | "npm": "rm -rf ./output && fis3 release npm -d ./output -r ./ -f compile/fis-conf.js",
13 | "doc": "npm run build:1 && npm run build:2",
14 | "dp:gh": "node ./compile/deploy-gh",
15 | "test": "./node_modules/karma/bin/karma start ./compile/karma.conf.js --browsers Chrome",
16 | "test:ci": "./node_modules/karma/bin/karma start ./compile/karma.ci.js"
17 | },
18 | "$username": "onface",
19 | "$repository": "page",
20 | "dependencies": {
21 | "calling": "^0.3.1",
22 | "extend": "^3.0.1",
23 | "jquery": "^3.3.1",
24 | "safe-extend": "^3.0.4",
25 | "sparejs": "^0.4.0"
26 | },
27 | "devDependencies": {
28 | "axios": "^0.18.0",
29 | "babel": "^6.23.0",
30 | "babel-core": "^6.26.0",
31 | "babel-loader": "^7.1.2",
32 | "babel-plugin-syntax-flow": "^6.18.0",
33 | "babel-plugin-syntax-jsx": "^6.18.0",
34 | "babel-plugin-transform-class-properties": "^6.24.1",
35 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
36 | "babel-plugin-transform-flow-strip-types": "^6.22.0",
37 | "babel-plugin-transform-react-display-name": "^6.25.0",
38 | "babel-plugin-transform-react-jsx": "^6.24.1",
39 | "babel-plugin-transform-react-jsx-source": "^6.22.0",
40 | "babel-polyfill": "^6.26.0",
41 | "babel-preset-es2015": "^6.24.1",
42 | "button.react": "^0.3.6",
43 | "chai": "^4.1.2",
44 | "css-loader": "^0.26.1",
45 | "ejs": "^2.5.7",
46 | "enzyme": "^3.3.0",
47 | "enzyme-adapter-react-16": "^1.1.1",
48 | "express": "^4.16.2",
49 | "face-message": "^0.1.1",
50 | "file-loader": "^1.1.6",
51 | "fis-parser-less-2.x": "^0.1.4",
52 | "fis3-hook-relative": "^2.0.1",
53 | "fis3-parser-vue-component": "^5.5.1",
54 | "gh-pages": "^1.1.0",
55 | "glob": "^7.1.2",
56 | "hash-to-port": "^1.0.0",
57 | "is-absolute-url": "^2.1.0",
58 | "jasmine-core": "^2.8.0",
59 | "json-loader": "^0.5.7",
60 | "json5": "^0.5.1",
61 | "karma": "^2.0.0",
62 | "karma-babel-preprocessor": "^7.0.0",
63 | "karma-chrome-launcher": "^2.2.0",
64 | "karma-jasmine": "^1.1.1",
65 | "karma-sauce-launcher": "^1.2.0",
66 | "karma-sourcemap-loader": "^0.3.7",
67 | "karma-webpack": "^2.0.9",
68 | "less": "^2.7.3",
69 | "less-loader": "^2.2.3",
70 | "less-plugin-autoprefix": "^1.5.1",
71 | "less-plugin-functions": "^1.0.0",
72 | "livereload": "^0.6.3",
73 | "markrun": "^0.23.0",
74 | "open": "^0.0.5",
75 | "query": "^0.2.0",
76 | "react": "^16.2.0",
77 | "react-dom": "^16.2.0",
78 | "react-hot-loader": "^4.0.0-beta.6",
79 | "style-loader": "^0.13.1",
80 | "table.react": "^0.3.2",
81 | "url-loader": "^0.6.2",
82 | "vue": "^2.5.13",
83 | "vue-loader": "^13.6.1",
84 | "vue-template-compiler": "^2.5.13",
85 | "webpack": "^3.10.0",
86 | "webpack-dev-middleware": "^2.0.3",
87 | "webpack-hot-middleware": "^2.21.0"
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/doc/theme/face/components/icons/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
32 |
33 |
34 |
35 | 图标来源 ant-design http://iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=790
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | <span class="fi fi-${name}" ></span>
45 | <span class="fi fi-${name}-${status}" ></span>
46 |
47 | <span class="fi fi-up ></span>
48 | <span class="fi fi-up-o ></span>
49 | <span class="fi fi-up-f ></span>
50 | <span class="fi fi-up-of ></span>
51 | <span class="fi fi-up-s ></span>
52 | <span class="fi fi-up-sf ></span>
53 |
54 |
55 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/doc/theme/media/bar-chart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/online-shop-1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/foot.html:
--------------------------------------------------------------------------------
1 |
43 |
92 |
--------------------------------------------------------------------------------
/doc/theme/media/mailing.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/analytics-1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/promotion.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/browser-3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/line-chart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/browsers-1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/API.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | ## new Page()
4 |
5 | ```js
6 | var Page = require('face-page').default
7 | // import Page from "lsit-logic"
8 | var list = new Page(options)
9 | ```
10 |
11 | ## 生命周期
12 |
13 | > options 主要配置用于列表的生命周期触发时所需要获取的一些数据,因为不同列表获取数据的方式是不同的。
14 |
15 | ```
16 | list.query()
17 | 1. getQuery
18 | 3. willFetch
19 | 4. fetch
20 | 5. render & didFetch
21 | ```
22 |
23 | ### getQuery
24 |
25 | ```js
26 | new Page({
27 | // 获取查询条件
28 | getQuery: function () {
29 | return {
30 | search_keyword: $('#search_keyword').val()
31 | }
32 | }
33 | })
34 | ```
35 |
36 | ### willFetch
37 |
38 | 即将获取数据时触发执行,可通过 `next()` 终止后续操作。
39 |
40 | 建议用来防止重复搜索和控制 loading 状态。
41 |
42 | > list.query() 调用后会执行 willFetch 若调用 next 则执行 fetch。
43 | > 若要防止重复搜索,不调用 next 则不会执行 fetch
44 |
45 | ```js
46 | new Page({
47 | willFetch: function (next) {
48 | if ($('#tip').hasClass('loading')) {
49 | return
50 | }
51 | $('#tip').addClass('loading')
52 | next()
53 | }
54 | })
55 | ```
56 |
57 | ### fetch
58 |
59 | 获取数据时触发执行,将 `query` 作为参数查询数据。获取到数据后调用 `didFetch` 进入 `didFetch` 阶段。数据获取成功调用 `render` 调用 `options.render`。(有时服务器会拒绝用户访问数据,比如登录过期了。)
60 |
61 | > options.willFetch 中调用 `next()` 后执行
62 |
63 | ```js
64 | new Page({
65 | fetch: function (queryData, render, didFetch) {
66 | $.ajax({
67 | url: '/some',
68 | data: queryData,
69 | dataType: 'json'
70 | }).done(function (res) {
71 | if (res.error) {
72 | aler(res.error)
73 | }
74 | else {
75 | render(res, queryData)
76 | }
77 | }).always(function () {
78 | didFetch()
79 | })
80 | }
81 | })
82 | ```
83 |
84 | ### didFetch
85 |
86 | 获取数据完成后触发执行。
87 |
88 | 建议用来控制 loading 状态。
89 |
90 | > 在 `options.fetch` 中调用 didFetch() 会执行
91 |
92 |
93 | ```js
94 | new Page({
95 | didFetch: function (next) {
96 | $('#tip').removeClass('loading')
97 | }
98 | })
99 | ```
100 |
101 | ### render
102 |
103 | 成功获取数据后触发执行,用于渲染页面数据。
104 |
105 | > 在 `options.fetch` 中调用 `render(res, query)` 会执行
106 |
107 | ```js
108 | new Page({
109 | render: function (res, query) {
110 | var html = '- ' + res.data.join('
') + '
'
111 | // 渲染列表
112 | $('#list').html(html)
113 | // 设置页码 (因为用户可能点击第10页,但是第10页的内容已经被服务器删除了,服务器只会返回第9页)
114 | $('#page').val(query.page)
115 | // 同样设置搜索关键字是因为用户可能在加载过程中修改了搜索关键字,设置后搜索条件和结果能保持一致。
116 | // 或用户调用了 clearQuery
117 | $('#search_keyword').val(query.search_keyword)
118 | }
119 | })
120 | ```
121 |
122 | ## query()
123 |
124 | 搜索或翻页时调用
125 |
126 | ```js
127 | $('#search').on('submit', function () {
128 | // 根据 options.getQuery() 返回值进行查询
129 | list.query()
130 | })
131 |
132 | $('#chanegPage').on('submit', function () {
133 | // 获取 options.getQuery() 返回值,与 {page: 2} 合并后进行查询
134 | list.query({page: 2})
135 | })
136 | ```
137 |
138 |
139 | ### 数据片段条件
140 |
141 | > 不求完全理解,知道大概概念即可
142 |
143 | 注意,修改页码时需要传入 `{page: 2}` 而搜索时不需要传任何值。因为搜索条件分为两种:
144 |
145 | 1. 查询条件
146 | 2. 数据片段条件
147 |
148 |
149 | 区分查询条件和数据片段条件是用于解决类似 [github commits](https://github.com/onface/react/commits/master?after=e7be4bc86ef218f9654ad43398e363962f3127e3+34) 分页的复杂问题。这种列表没有页码,只有 `after` `before` 参数。(打开页面拉到下方点击 [Newer](https://github.com/onface/react/commits/master?before=e7be4bc86ef218f9654ad43398e363962f3127e3+35) 和 [Older](https://github.com/onface/react/commits/master?after=e7be4bc86ef218f9654ad43398e363962f3127e3+69))
150 |
151 | 查询条件一般有 `用户名` `手机号` `开始日期` `按金额正序排列` `按金额倒序排列` `每页显示多少条件数据`
152 |
153 | 数据片段条件一般有 `页码`
154 |
155 | **他们的区别是:在多次查询中。`数据片段条件变化`时查询条件不变,`查询条件变化`时数据片段条件被设为为默认值。**(一般情况默认值是 `""` 或 `1`)
156 |
157 | 从用户的角度来看就是:
158 |
159 | ```shell
160 | 1次搜索: user=nimo&page=1 // 输入 nimo 点击搜索按钮
161 | 2次搜索: user=nimo&page=2 // 点击下一页 => page 改变,但 user 不变
162 | 3次搜索: user=code&page=1 // 输入 code 点击搜索按钮 => user 改变 page 改为默认值 1
163 | ```
164 |
165 | 这里查询条件是 `user`,数据片段条件是 `page`。
166 |
167 | ---
168 |
169 | 有时也会定义成
170 |
171 | `https://github.com/onface/react/commits/master?after=e7be4bc86ef218f9654ad43398e363962f3127e3+34`
172 |
173 | 中的 `after`
174 |
175 | 什么条件是数据片段条件是由开发者根据项目实际情况去定义的。
176 |
177 | ### 复杂的情况
178 |
179 | > 不求完全理解,知道大概概念即可
180 |
181 | 用户多次查询时,如果用户搜索了 `user=nimo&age=13&page=2`,接着修改了 `` 的值为 `abc` 但没有点击搜索按钮,直接点击了下一页 。 请求参数会变成 `user=abc&age=13&page=2`。
182 |
183 | 有时根据产品需求请求参数需要是 `user=nimo&age=13&page=3` (用户没有点击搜索按钮,查询条件不变化,即使页面中的 input 变化了。)
184 |
185 | 这时就需要点击搜索按钮时将当前值保存起来,然后通过 `options.getQuery()` 获取的值是刚才保存的值。
186 |
187 | 比如用 jQuery 可以这样做
188 |
189 | ```js
190 | var list = new Page({
191 | getQuery: function () {
192 | return $('#search').data('query')
193 | }
194 | })
195 | function setQuery (this) {
196 | var query = {}
197 | var $this = $(this)
198 | $this.serializeArray().forEach(function (item) {
199 | query[item.name] = item.value
200 | })
201 | $this.data('query', query)
202 | }
203 | $('#search').on('submit', function () {
204 | setQuery(this)
205 | list.query()
206 | })
207 | setQuery(
208 | $('#search')
209 | )
210 |
211 | ```
212 |
213 | React Vue Angular 等声明式框架 则将 `setQuery` 改为将搜索条件命名为 `lastTimeQuery` 保存到 `state` 中
214 |
--------------------------------------------------------------------------------
/doc/theme/media/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/growth.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/rating.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/share.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/update.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/aim.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/browser-1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/browser.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/stats.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/optimization.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/laptop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/speedometer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/ads.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/blog.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/polyfill/es6-promise.auto.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.ES6Promise=e()}(this,function(){"use strict";function t(t){return"function"==typeof t||"object"==typeof t&&null!==t}function e(t){return"function"==typeof t}function n(t){I=t}function r(t){J=t}function o(){return function(){return process.nextTick(a)}}function i(){return"undefined"!=typeof H?function(){H(a)}:c()}function s(){var t=0,e=new V(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){return t.port2.postMessage(0)}}function c(){var t=setTimeout;return function(){return t(a,1)}}function a(){for(var t=0;t
--------------------------------------------------------------------------------
/doc/theme/media/activity.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/theme/media/browser-2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------