├── .eslintignore
├── .stylelintignore
├── src
├── assets
│ ├── styles
│ │ ├── .gitkeep
│ │ ├── base
│ │ │ ├── _reset.scss
│ │ │ └── _base.scss
│ │ ├── abstracts
│ │ │ └── _variables.scss
│ │ └── main.scss
│ └── images
│ │ └── AngularJS-large.png
├── common
│ ├── components
│ │ └── index.js
│ ├── directives
│ │ ├── index.js
│ │ └── auto-focus.directive.js
│ ├── services
│ │ ├── index.js
│ │ └── http.service.js
│ ├── common.module.js
│ └── common.config.js
├── views
│ ├── list
│ │ ├── list.scss
│ │ ├── components
│ │ │ └── news-panel
│ │ │ │ ├── news-panel.html
│ │ │ │ ├── news-panel.component.js
│ │ │ │ └── news-panel.scss
│ │ ├── list.module.js
│ │ ├── list.html
│ │ ├── list.component.js
│ │ └── list.service.js
│ └── index
│ │ ├── index.html
│ │ ├── components
│ │ └── hello-angular
│ │ │ ├── hello-angular.scss
│ │ │ ├── hello-angular.html
│ │ │ └── hello-angular.component.js
│ │ ├── index.component.js
│ │ ├── index.module.js
│ │ └── index.scss
├── main.js
└── router
│ ├── router.module.js
│ ├── router.transitions.js
│ ├── router.config.js
│ └── routes.js
├── .browserslistrc
├── public
├── favicon.ico
└── index.html
├── .gitignore
├── environments
├── production.js
└── development.js
├── jsconfig.json
├── babel.config.js
├── .stylelintrc.js
├── .eslintrc.js
├── package.json
├── mock
└── db.json
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/src/assets/styles/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/components/index.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_reset.scss:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlymisaky/AngularJS-ES6/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/styles/abstracts/_variables.scss:
--------------------------------------------------------------------------------
1 | $distance-list: 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50;
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.log
3 | .vscode
4 | dist
5 | node_modules
6 | package-lock.json
7 | .stylelintcache
8 |
--------------------------------------------------------------------------------
/src/assets/styles/main.scss:
--------------------------------------------------------------------------------
1 | @import 'abstracts/variables';
2 |
3 | @import 'base/reset';
4 |
5 | @import 'base/base';
6 |
--------------------------------------------------------------------------------
/src/common/directives/index.js:
--------------------------------------------------------------------------------
1 | import { autoFocus } from './auto-focus.directive';
2 |
3 | export default { autoFocus };
4 |
--------------------------------------------------------------------------------
/src/common/services/index.js:
--------------------------------------------------------------------------------
1 | import { HttpService } from './http.service';
2 |
3 | export default {
4 | HttpService,
5 | };
6 |
--------------------------------------------------------------------------------
/src/assets/images/AngularJS-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlymisaky/AngularJS-ES6/HEAD/src/assets/images/AngularJS-large.png
--------------------------------------------------------------------------------
/environments/production.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // NODE_ENV: "production",
3 | NAME: '线上环境',
4 | DOMAIN: 'http://news-at.zhihu.com/',
5 | };
6 |
--------------------------------------------------------------------------------
/environments/development.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // NODE_ENV: "development",
3 | NAME: '开发环境',
4 | DOMAIN: 'http://news-at.zhihu.com/',
5 | };
6 |
--------------------------------------------------------------------------------
/src/views/list/list.scss:
--------------------------------------------------------------------------------
1 | #view-list {
2 | background-color: #f9f9f9;
3 |
4 | .list {
5 | display: flex;
6 | flex-wrap: wrap;
7 | justify-content: center;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": [
6 | "./src/*"
7 | ]
8 | }
9 | },
10 | "include": [
11 | "src"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/src/views/index/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 看看日报
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/list/components/news-panel/news-panel.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/common/directives/auto-focus.directive.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @returns {import('angular').IDirective}
3 | */
4 | export function autoFocus() {
5 | return {
6 | restrict: 'A',
7 | link($scope, $ele) {
8 | $ele[0].focus();
9 | },
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/views/index/components/hello-angular/hello-angular.scss:
--------------------------------------------------------------------------------
1 | #hello-angular {
2 | margin-top: 80px;
3 | text-align: center;
4 |
5 | input[type="text"] {
6 | width: 290px;
7 | height: 20px;
8 | padding: 5px;
9 | font-size: 16px;
10 | outline-style: none;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/views/index/components/hello-angular/hello-angular.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
Hello {{$ctrl.value}}!
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/views/index/index.component.js:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 | import template from './index.html';
3 |
4 | /** @type {import('angular').IComponentOptions} */
5 | export const index = {
6 | template,
7 | controller: class {
8 | constructor() {
9 | this.listState = 'list';
10 | }
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/src/views/index/index.module.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 |
3 | import { index } from './index.component';
4 | import { helloAngular } from './components/hello-angular/hello-angular.component';
5 |
6 | export default angular
7 | .module('app.views.index', [])
8 | .component({ index, helloAngular })
9 | .name;
10 |
--------------------------------------------------------------------------------
/src/views/index/components/hello-angular/hello-angular.component.js:
--------------------------------------------------------------------------------
1 | import './hello-angular.scss';
2 | import template from './hello-angular.html';
3 |
4 | /** @type {angular.IComponentOptions} */
5 | export const helloAngular = {
6 | template,
7 | controller: class {
8 | constructor() {
9 | this.value = 'AngularJS';
10 | }
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/src/views/list/list.module.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 |
3 | import { list } from './list.component';
4 | import { newsPanel } from './components/news-panel/news-panel.component';
5 | import { ListService } from './list.service';
6 |
7 | export default angular
8 | .module('app.views.list', [])
9 | .component({ list, newsPanel })
10 | .service({ ListService })
11 | .name;
12 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 |
3 | import { commonModule } from '@/common/common.module';
4 | import { routerModule } from '@/router/router.module';
5 |
6 | import '@/assets/styles/main.scss';
7 |
8 | angular.module('app', [
9 | commonModule,
10 | routerModule,
11 | ]);
12 |
13 | angular.element(document).ready(() => {
14 | angular.bootstrap(document, ['app']);
15 | });
16 |
--------------------------------------------------------------------------------
/src/views/list/components/news-panel/news-panel.component.js:
--------------------------------------------------------------------------------
1 | import './news-panel.scss';
2 | import template from './news-panel.html';
3 |
4 | /** @type {import('angular').IComponentOptions} */
5 | export const newsPanel = {
6 | template,
7 | bindings: {
8 | news: '<',
9 | },
10 | controller: class {
11 | constructor() {
12 | this.url = 'http://daily.zhihu.com/story/';
13 | }
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/common/common.module.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import components from './components';
3 | import directives from './directives';
4 | import services from './services';
5 | import { commonConfig } from './common.config';
6 |
7 | export const commonModule = angular
8 | .module('app.common', [])
9 | .component(components)
10 | .directive(directives)
11 | .service(services)
12 | .config(commonConfig)
13 | .name;
14 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@babel/core').TransformOptions} */
2 | const transformOptions = {
3 | presets: [
4 | [
5 | '@babel/preset-env',
6 | {
7 | modules: false,
8 | },
9 | ],
10 | ],
11 | plugins: [
12 | '@babel/plugin-transform-runtime',
13 | '@babel/plugin-syntax-dynamic-import',
14 | '@babel/plugin-proposal-class-properties',
15 | ],
16 | };
17 |
18 | module.exports = transformOptions;
19 |
--------------------------------------------------------------------------------
/src/router/router.module.js:
--------------------------------------------------------------------------------
1 | import angular from 'angular';
2 | import * as uiRouter from '@uirouter/angularjs';
3 | import oclazyload from 'oclazyload';
4 | import { routerConfig } from './router.config';
5 | import { routeChange } from './router.transitions';
6 |
7 | export const routerModule = angular
8 | .module('app.router', [
9 | uiRouter.default,
10 | oclazyload,
11 | ])
12 | .config(routerConfig)
13 | .run(routeChange)
14 | .name;
15 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | AngularJS-ES6
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/views/list/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 正在加载
4 |
5 |
6 |
7 | 没有数据
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/views/index/index.scss:
--------------------------------------------------------------------------------
1 | #view-index {
2 | a {
3 | display: inline-block;
4 | min-width: 120px;
5 | color: #fff;
6 | background-color: #1e88e5;
7 | text-decoration: none;
8 | border-radius: 2px;
9 | padding: 10px 16px;
10 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 2px rgba(0, 0, 0, 0.24);
11 | transition: all 0.3s;
12 |
13 | &:hover {
14 | box-shadow: 0 0 12px rgba(0, 0, 0, 0.12), 0 6px 6px rgba(0, 0, 0, 0.24);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/views/list/components/news-panel/news-panel.scss:
--------------------------------------------------------------------------------
1 | #news-panel {
2 | width: 260px;
3 | height: 325px;
4 | background-color: #fff;
5 | padding: 20px;
6 | border-radius: 2px;
7 |
8 | &:hover {
9 | cursor: pointer;
10 | }
11 |
12 | a {
13 | display: block;
14 | color: #428bca;
15 | text-decoration: none;
16 |
17 | img {
18 | display: block;
19 | max-width: 100%;
20 | height: 260px;
21 | }
22 |
23 | span {
24 | font-size: 16px;
25 | color: #000;
26 | line-height: 30px;
27 | display: block;
28 | background: #fff;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/router/router.transitions.js:
--------------------------------------------------------------------------------
1 | import NProgress from 'nprogress';
2 | import 'nprogress/nprogress.css';
3 |
4 | /**
5 | * @param {import('@uirouter/angularjs').Transition} $transitions
6 | */
7 | export function routeChange($transitions) {
8 | $transitions.onStart({}, () => {
9 | NProgress.start();
10 | });
11 |
12 | $transitions.onFinish({}, () => {
13 | NProgress.done();
14 | });
15 | }
16 |
17 | routeChange.$inject = ['$transitions'];
18 |
19 | // https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-change-events
20 | // $stateChangeStart $stateChangeCancel $stateChangeSuccess $stateChangeError $stateNotFound
21 |
--------------------------------------------------------------------------------
/src/common/common.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {import('angular').IQService} $q
3 | * @returns {import('angular').IHttpInterceptor}
4 | */
5 | function httpInterceptorFactory($q) {
6 | return {
7 |
8 | request(config) {
9 | return config;
10 | },
11 |
12 | requestError(rejection) {
13 | return rejection;
14 | },
15 |
16 | response(resp) {
17 | return $q.resolve(resp);
18 | },
19 |
20 | responseError(rejection) {
21 | return rejection;
22 | },
23 | };
24 | }
25 |
26 | httpInterceptorFactory.$inject = ['$q'];
27 |
28 | /**
29 | * @param {import('angular').IHttpProvider} $httpProvider
30 | */
31 | export function commonConfig($httpProvider) {
32 | $httpProvider.interceptors.push(httpInterceptorFactory);
33 | }
34 |
35 | commonConfig.$inject = [
36 | '$httpProvider',
37 | ];
38 |
--------------------------------------------------------------------------------
/src/router/router.config.js:
--------------------------------------------------------------------------------
1 | import { routes } from './routes';
2 |
3 | /**
4 | * @param {import('angular').ILocationProvider} $locationProvider
5 | * @param {import('@uirouter/angularjs').UrlRouterProvider} $urlRouterProvider
6 | * @param {import('@uirouter/angularjs').StateProvider} $stateProvider
7 | */
8 | export function routerConfig($locationProvider, $urlRouterProvider, $stateProvider) {
9 | $locationProvider
10 | .hashPrefix('') // remove !
11 | .html5Mode({
12 | // history Api
13 | enabled: !(process.env.NODE_ENV === 'production'),
14 | requireBase: false,
15 | });
16 |
17 | $urlRouterProvider.otherwise('/index');
18 |
19 | routes.forEach((route) => $stateProvider.state(route));
20 | }
21 |
22 | routerConfig.$inject = [
23 | '$locationProvider',
24 | '$urlRouterProvider',
25 | '$stateProvider',
26 | ];
27 |
--------------------------------------------------------------------------------
/src/views/list/list.component.js:
--------------------------------------------------------------------------------
1 | import './list.scss';
2 | import template from './list.html';
3 |
4 | /** @type {import('angular').IComponentOptions} */
5 | export const list = {
6 | template,
7 | controller: class {
8 | static $inject = ['$scope', '$injector', 'ListService'];
9 |
10 | /**
11 | * @param {import('angular').IScope} $scope
12 | * @param {import('angular').auto.IInjectorService} $injector
13 | * @param {import('./list.service')} ListService
14 | */
15 | constructor($scope, $injector, ListService) {
16 | this.$scope = $scope;
17 | this.ListService = ListService;
18 | this.newsList = [];
19 | }
20 |
21 | async $onInit() {
22 | this.ListService
23 | .getNewsList()
24 | .then((news) => {
25 | this.newsList = news;
26 | });
27 | }
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/src/router/routes.js:
--------------------------------------------------------------------------------
1 | /** @type {Array} */
2 | export const routes = [
3 | {
4 | name: 'index',
5 | url: '/index',
6 | component: 'index',
7 | lazyLoad(transition) {
8 | /** @type {import('oclazyload').ILazyLoad} */
9 | const $ocLazyLoad = transition.injector().get('$ocLazyLoad');
10 | return import('@/views/index/index.module.js').then((ngModule) => $ocLazyLoad.load({ name: ngModule.default }));
11 | },
12 | },
13 | {
14 | name: 'list',
15 | url: '/list',
16 | component: 'list',
17 | lazyLoad(transition) {
18 | /** @type {import('oclazyload').ILazyLoad} */
19 | const $ocLazyLoad = transition.injector().get('$ocLazyLoad');
20 | return import('@/views/list/list.module.js').then((ngModule) => $ocLazyLoad.load({ name: ngModule.default }));
21 | },
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import('stylelint').Configuration} */
2 | const styleLintConfig = {
3 | processors: [],
4 | extends: 'stylelint-config-standard',
5 | plugins: ['stylelint-order', 'stylelint-scss'],
6 | rules: {
7 | 'at-rule-empty-line-before': 'always',
8 | 'at-rule-name-case': 'lower',
9 | 'block-no-empty': true,
10 | // scss 语法提示
11 | // 参考 https://github.com/stylelint/stylelint/issues/3190
12 | 'at-rule-no-unknown': null,
13 | 'scss/at-rule-no-unknown': true,
14 | // css书写顺序
15 | 'order/order': [
16 | 'declarations',
17 | 'custom-properties',
18 | 'dollar-variables',
19 | 'rules',
20 | 'at-rules',
21 | ],
22 | 'order/properties-order': [
23 | 'position',
24 | 'z-index',
25 | // 其他样式的顺序
26 | ],
27 | // 其他规则
28 | 'no-empty-source': null,
29 | },
30 | };
31 |
32 | module.exports = styleLintConfig;
33 |
--------------------------------------------------------------------------------
/src/views/list/list.service.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | export class ListService {
3 | static $inject = ['HttpService'];
4 |
5 | /**
6 | *
7 | * @param {import('@/common/services/http.service')} HttpService
8 | */
9 | constructor(HttpService) {
10 | this.HttpService = HttpService;
11 | }
12 |
13 | getNewsList() {
14 | return this.HttpService
15 | .get('/4/news/latest')
16 | .then((response) => {
17 | if (response.status === 200) {
18 | return this.formatNewsList(response.data);
19 | }
20 | return this.formatNewsList(require('../../../mock/db.json'));
21 | })
22 | .catch(() => this.formatNewsList(require('../../../mock/db.json')));
23 | }
24 |
25 | /**
26 | * @private
27 | * @param {Array} data
28 | */
29 | formatNewsList(data) {
30 | const idList = [];
31 | const list = [];
32 | [...data.top_stories, ...data.stories].forEach((item) => {
33 | if (!idList.includes(item.id)) {
34 | idList.push(item.id);
35 | // eslint-disable-next-line no-param-reassign
36 | item.image = item.image || item.images[0];
37 | list.push(item);
38 | }
39 | });
40 | return list;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import('eslint').Linter.Config} */
2 | const eslintConfig = {
3 | root: true,
4 | env: {
5 | browser: true,
6 | node: true,
7 | },
8 | parser: 'babel-eslint',
9 | parserOptions: {
10 | ecmaVersion: 2020,
11 | sourceType: 'module',
12 | ecmaFeatures: {
13 | tsx: true,
14 | jsx: true,
15 | },
16 | },
17 | extends: ['airbnb-base'],
18 | settings: {
19 | 'import/resolver': {
20 | alias: {
21 | map: [
22 | ['@', './src'],
23 | ],
24 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
25 | },
26 | },
27 | 'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
28 | },
29 | rules: {
30 | 'import/extensions': ['error', 'always', {
31 | js: 'never',
32 | jsx: 'never',
33 | ts: 'never',
34 | tsx: 'never',
35 | }],
36 | 'import/no-absolute-path': 'off',
37 | 'import/prefer-default-export': 'off',
38 | 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
39 | 'no-param-reassign': ['error', {
40 | props: true,
41 | ignorePropertyModificationsFor: [
42 | 'Target', // 装饰器
43 | 'e', // for e.returnvalue
44 | ],
45 | }],
46 | 'max-len': 'off',
47 | 'no-console': 'warn',
48 | 'no-debugger': 'error',
49 | 'no-restricted-syntax': 'off',
50 | 'guard-for-in': 'off',
51 | 'arrow-body-style': 'off',
52 | 'class-methods-use-this': 'off',
53 | },
54 | };
55 |
56 | module.exports = eslintConfig;
57 |
--------------------------------------------------------------------------------
/src/common/services/http.service.js:
--------------------------------------------------------------------------------
1 | export class HttpService {
2 | baseUrl = process.env.NODE_ENV === 'production' ? '' : 'api';
3 |
4 | static $inject = ['$http'];
5 |
6 | /**
7 | * @param {import('angular').IHttpService} $http
8 | */
9 | constructor($http) {
10 | this.$http = $http;
11 | }
12 |
13 | /**
14 | * @param {string} url
15 | * @param {import('angular').IRequestShortcutConfig} config
16 | */
17 | get(url, config = {}) {
18 | return this.$http.get(this.baseUrl + url, config);
19 | }
20 |
21 | /**
22 | * @param {string} url
23 | * @param {any} data
24 | * @param {import('angular').IRequestShortcutConfig} config
25 | * @returns
26 | */
27 | post(url, data, config = {}) {
28 | return this.$http.post(this.baseUrl + url, data, config);
29 | }
30 |
31 | /**
32 | * @param {string} url
33 | * @param {import('angular').IRequestShortcutConfig} config
34 | */
35 | delete(url, config = {}) {
36 | return this.$http.delete(this.baseUrl + url, config);
37 | }
38 |
39 | /**
40 | * @param {string} url
41 | * @param {any} data
42 | * @param {import('angular').IRequestShortcutConfig} config
43 | */
44 | put(url, data, config = {}) {
45 | return this.$http.put(this.baseUrl + url, data, config);
46 | }
47 |
48 | /**
49 | * @param {string} url
50 | * @param {any} data
51 | * @param {import('angular').IRequestShortcutConfig} config
52 | */
53 | patch(url, data, config = {}) {
54 | return this.$http.patch(this.baseUrl + url, data, config);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_base.scss:
--------------------------------------------------------------------------------
1 | .fl {
2 | float: left;
3 | }
4 |
5 | .fr {
6 | float: right;
7 | }
8 |
9 | .clearfix::after {
10 | content: "";
11 | display: block;
12 | visibility: hidden;
13 | height: 0;
14 | clear: both;
15 | }
16 |
17 | .clearfix {
18 | zoom: 1;
19 | }
20 |
21 | .text-center {
22 | text-align: center;
23 | }
24 |
25 | @each $distance in $distance-list {
26 | .m-#{$distance} {
27 | margin: $distance + px !important;
28 | }
29 | .m-y-#{$distance} {
30 | margin-top: $distance + px !important;
31 | margin-bottom: $distance + px !important;
32 | }
33 | .m-x-#{$distance} {
34 | margin-left: $distance + px !important;
35 | margin-right: $distance + px !important;
36 | }
37 | .m-t-#{$distance} {
38 | margin-top: $distance + px !important;
39 | }
40 | .m-r-#{$distance} {
41 | margin-right: $distance + px !important;
42 | }
43 | .m-b-#{$distance} {
44 | margin-bottom: $distance + px !important;
45 | }
46 | .m-l-#{$distance} {
47 | margin-left: $distance + px !important;
48 | }
49 | .p-#{$distance} {
50 | padding: $distance + px !important;
51 | }
52 | .p-y-#{$distance} {
53 | padding-top: $distance + px !important;
54 | padding-bottom: $distance + px !important;
55 | }
56 | .p-x-#{$distance} {
57 | padding-left: $distance + px !important;
58 | padding-right: $distance + px !important;
59 | }
60 | .p-t-#{$distance} {
61 | padding-top: $distance + px !important;
62 | }
63 | .p-r-#{$distance} {
64 | padding-right: $distance + px !important;
65 | }
66 | .p-b-#{$distance} {
67 | padding-bottom: $distance + px !important;
68 | }
69 | .p-l-#{$distance} {
70 | padding-left: $distance + px !important;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angularjs-es6",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "onlymisaky",
6 | "license": "ISC",
7 | "keywords": [
8 | "angularjs",
9 | "ES6",
10 | "webpack"
11 | ],
12 | "scripts": {
13 | "dev": "webpack serve --open --mode development --progress --color --config build/webpack.dev.js",
14 | "build": "node build/build.js",
15 | "lint-js": "eslint --fix --ext .js --ext .ts src/",
16 | "lint-css": "stylelint **/*.{html,css,sass,scss,less} --fix --cache"
17 | },
18 | "dependencies": {
19 | "@babel/runtime": "^7.12.5",
20 | "@uirouter/angularjs": "^1.0.29",
21 | "angular": "^1.8.2",
22 | "nprogress": "^0.2.0",
23 | "oclazyload": "^1.1.0"
24 | },
25 | "devDependencies": {
26 | "@babel/core": "^7.12.10",
27 | "@babel/plugin-proposal-class-properties": "^7.12.1",
28 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
29 | "@babel/plugin-transform-runtime": "^7.12.10",
30 | "@babel/preset-env": "^7.12.11",
31 | "@types/angular": "^1.8.0",
32 | "autoprefixer": "^10.2.1",
33 | "babel-eslint": "^10.1.0",
34 | "babel-loader": "^8.2.2",
35 | "clean-webpack-plugin": "^3.0.0",
36 | "copy-webpack-plugin": "^7.0.0",
37 | "css-loader": "^5.0.1",
38 | "eslint": "^7.18.0",
39 | "eslint-config-airbnb-base": "^14.2.1",
40 | "eslint-import-resolver-alias": "^1.1.2",
41 | "eslint-plugin-import": "^2.22.1",
42 | "eslint-webpack-plugin": "^2.4.1",
43 | "file-loader": "^6.2.0",
44 | "friendly-errors-webpack-plugin": "^1.7.0",
45 | "html-loader": "^1.3.2",
46 | "html-webpack-plugin": "^4.5.1",
47 | "less": "^4.1.0",
48 | "less-loader": "^7.2.1",
49 | "mini-css-extract-plugin": "^1.3.4",
50 | "optimize-css-assets-webpack-plugin": "^5.0.4",
51 | "ora": "^5.2.0",
52 | "portfinder": "^1.0.28",
53 | "postcss-loader": "^4.1.0",
54 | "sass": "^1.32.4",
55 | "sass-loader": "^10.1.1",
56 | "style-loader": "^2.0.0",
57 | "stylelint": "^13.8.0",
58 | "stylelint-config-standard": "^20.0.0",
59 | "stylelint-order": "^4.1.0",
60 | "stylelint-scss": "^3.18.0",
61 | "stylelint-webpack-plugin": "^2.1.1",
62 | "url-loader": "^4.1.1",
63 | "webpack": "^5.15.0",
64 | "webpack-bundle-analyzer": "^4.3.0",
65 | "webpack-cli": "^4.3.1",
66 | "webpack-dev-server": "^3.11.2",
67 | "webpack-merge": "^5.7.3"
68 | },
69 | "postcss": {
70 | "plugins": {
71 | "autoprefixer": {}
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/mock/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "date": "20180403",
3 | "stories": [
4 | {
5 | "images": [
6 | "https:\/\/pic3.zhimg.com\/v2-b2d8efa97811f51b6bf808545fb04c12.jpg"
7 | ],
8 | "type": 0,
9 | "id": 9676858,
10 | "ga_prefix": "040315",
11 | "title": "算不上游戏迷,我也觉得电影《头号玩家》还不错"
12 | },
13 | {
14 | "images": [
15 | "https:\/\/pic1.zhimg.com\/v2-bb23542fdf5645229a42d5bf1028e214.jpg"
16 | ],
17 | "type": 0,
18 | "id": 9677138,
19 | "ga_prefix": "040313",
20 | "title": "比起星巴克致癌,更可怕的是活跃在朋友圈里谣言号"
21 | },
22 | {
23 | "images": [
24 | "https:\/\/pic1.zhimg.com\/v2-0aec038b6c1076e06af8d35af8408d98.jpg"
25 | ],
26 | "type": 0,
27 | "id": 9676927,
28 | "ga_prefix": "040312",
29 | "title": "大误 · 《头号玩家》里的隐藏细节"
30 | },
31 | {
32 | "images": [
33 | "https:\/\/pic2.zhimg.com\/v2-f4334b59b0922bfb4c9f6a93faa0c4ed.jpg"
34 | ],
35 | "type": 0,
36 | "id": 9677020,
37 | "ga_prefix": "040310",
38 | "title": "为什么人到中年,很少有身材苗条的?"
39 | },
40 | {
41 | "images": [
42 | "https:\/\/pic3.zhimg.com\/v2-fa6123c0c3e2f261a99e8367e7681182.jpg"
43 | ],
44 | "type": 0,
45 | "id": 9674718,
46 | "ga_prefix": "040309",
47 | "title": "春天一到,怎么有的树先开花再长叶,而有的却先长叶再开花?"
48 | },
49 | {
50 | "images": [
51 | "https:\/\/pic4.zhimg.com\/v2-f1c084ca341d396f69cb0c31535332bb.jpg"
52 | ],
53 | "type": 0,
54 | "id": 9675761,
55 | "ga_prefix": "040308",
56 | "title": "用数据告诉你,什么样的数据分析师才是企业需要的"
57 | },
58 | {
59 | "images": [
60 | "https:\/\/pic4.zhimg.com\/v2-db46ed6ffca222f8cea56d9681aceb8f.jpg"
61 | ],
62 | "type": 0,
63 | "id": 9676962,
64 | "ga_prefix": "040307",
65 | "title": "明明在国内,为什么 B 站、爱奇艺这些公司还要赴美 IPO?"
66 | },
67 | {
68 | "images": [
69 | "https:\/\/pic2.zhimg.com\/v2-53cab5878c3c23fcc1c169c2743a3695.jpg"
70 | ],
71 | "type": 0,
72 | "id": 9677039,
73 | "ga_prefix": "040307",
74 | "title": "「白银案」凶手和他的同学们"
75 | },
76 | {
77 | "images": [
78 | "https:\/\/pic3.zhimg.com\/v2-40b35835a2780eb2291d2fb79d6a4f76.jpg"
79 | ],
80 | "type": 0,
81 | "id": 9676818,
82 | "ga_prefix": "040306",
83 | "title": "瞎扯 · 如何正确地吐槽"
84 | }
85 | ],
86 | "top_stories": [
87 | {
88 | "image": "https:\/\/pic4.zhimg.com\/v2-498ce8f4b9ce9886cc0066abcaf44687.jpg",
89 | "type": 0,
90 | "id": 9676858,
91 | "ga_prefix": "040315",
92 | "title": "算不上游戏迷,我也觉得电影《头号玩家》还不错"
93 | },
94 | {
95 | "image": "https:\/\/pic2.zhimg.com\/v2-0d9f5f97429089f20ee1ce224cd79c1d.jpg",
96 | "type": 0,
97 | "id": 9677138,
98 | "ga_prefix": "040313",
99 | "title": "比起星巴克致癌,更可怕的是活跃在朋友圈里谣言号"
100 | },
101 | {
102 | "image": "https:\/\/pic3.zhimg.com\/v2-dd0b4daf5f529b84572b89b197191ff2.jpg",
103 | "type": 0,
104 | "id": 9677039,
105 | "ga_prefix": "040307",
106 | "title": "「白银案」凶手和他的同学们"
107 | },
108 | {
109 | "image": "https:\/\/pic1.zhimg.com\/v2-ff4d7de6674f827c905004d255236520.jpg",
110 | "type": 0,
111 | "id": 9676962,
112 | "ga_prefix": "040307",
113 | "title": "明明在国内,为什么 B 站、爱奇艺这些公司还要赴美 IPO?"
114 | },
115 | {
116 | "image": "https:\/\/pic2.zhimg.com\/v2-768623fde965fe862190301cad11e88d.jpg",
117 | "type": 0,
118 | "id": 9677020,
119 | "ga_prefix": "040310",
120 | "title": "为什么人到中年,很少有身材苗条的?"
121 | }
122 | ]
123 | }
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 2021-01-18: 升级了项目依赖,添加了 eslint 和 stylelint 。因为发现还有小伙伴在fork本项目,由于本项目距今已经有两年多的时间,只剩下参考意义了,如果确实对 Angularjs 非常感兴趣,建议学习 Angular ,再不济也看一下 用 TypeScript 写 Angularjs 的方式吧。
2 |
3 | ---
4 |
5 | 一个用 `AngularJS` + `es6/es7` + `webpack` 构建的种子项目。
6 |
7 | 该项目只有两个页面,目的是为了展示如何使用主流的方式开发 `AngularJS` 应用。
8 |
9 | 在线地址
10 |
11 | ## 截图
12 |
13 | 
14 |
15 | 
16 |
17 | ## 用法
18 |
19 | 1. 下载到本地
20 | ```bash
21 | git clone https://github.com/onlymisaky/AngularJS-ES6.git
22 | ```
23 |
24 | 2. 切换到项目目录
25 | ```bash
26 | cd AngularJS-ES6
27 | ```
28 |
29 | 3. 安装依赖,这一步很慢,耐心等待即可,如长时间没有安装好,请尝试用 [cnpm](https://npm.taobao.org/) 安装
30 | ```bash
31 | npm i --registry=http://r.cnpmjs.org/
32 | ```
33 | 4. 启动服务
34 | ```bash
35 | npm run dev
36 | ```
37 | 5. 打包
38 | ```bash
39 | npm run build
40 | ```
41 | 6. 如果在执行 **第4** 或 **第5** 步的过程中提示 *node-sass* 错误,请使用下面的命令重新 build node-sass ,build 完成后再次使用 **第4** 或 **第5** 的命令即可
42 | ```bash
43 | npm rebuild node-sass
44 | ```
45 |
46 | **提示**
47 |
48 | 原则上,不建议用 [cnpm](https://npm.taobao.org/) 安装依赖,因为用 cnpm 安装依赖后,再用 vscode 打开项目会导致 cpu 狂飙至 100% ,这个锅应该是 cnpm 的。 详情可以查看这两个 issues :
49 |
50 | [When using cnpm/pnpm, rg uses lots of CPU #35659](https://github.com/Microsoft/vscode/issues/35659)
51 |
52 | [Use search.followSymlinks for all searches #37000](https://github.com/Microsoft/vscode/issues/37000)
53 |
54 | 如果由于网络问题,不得不使用 cnpm ,恰巧你的开发工具可是 vscode ,那么请用下面两种方式解决:
55 |
56 | 第一种方式 : 在 `cnpm i` 完成后,打开 vscode 将**设置**中的 **search.followSymlinks** 修改为 **false** 。
57 |
58 | 第二种方式 : 在 cnpm 命令后面添加参数,即使用 `cnpm i --by=npm` 来安装。
59 |
60 | 另外也不推荐用 taobao 的源安装,他的速度还没 cnpm 的快。
61 |
62 | ## 开发日记
63 |
64 | ### 1、目录结构
65 |
66 | 项目的目录结构决定了整个项目的风格,下面是整体的目录结构图:
67 |
68 | 
69 |
70 | 这个结构是在参考的了 Angular 项目结构的基础上结合个人的经验构建出来的,现在你可能不是很明白为什么要这样设计,不要着急,你只要先总体的看一下它的结构,对它有一个大概的了解即可,继续往下读,你会明白为什么这样设计。
71 |
72 | ### 2、模块设计
73 |
74 | 这里采用了多模块设计,angularjs 的 module 一直饱受病诟,它希望开发者通过 angular.module 来对项目进行更合理的划分,但是这个 module 实在是太弱了。首先它是脱离了文件系统的,而 es6 的 module 则是完全基于文件来设计的,一个 .js 文件就是一个 esModule ,所以当这两个结合到一块的时候,还是有些怪异的。其次 angular.module 顶多算是一个不可检测的命名空间,何为不可检测?就是当你拿到这个 module 的时候,你不知道这个 module 下面到底有哪些东西,另外当你想在 moduleA 下面注册 MyService 的时候,可能 moduleA 依赖了 moduleB ,而 moduleB 可能已经注册了一个功能不一样的 MyService ,这些你明白什么叫不可检测了吧。造成这个原因主要是因为在 angularjs 诞生的时候,还有 esModule 的概念。
75 |
76 | 既然 angular.module 这么弱,为什么还要用它呢,干脆在整个项目中只用一个 module,这样不就可以最小化上面的那些问题了吗。的确可以这样,但是这样一来,我们打包出的文件体积就太大了,也许你想到了用按需加载的方式来切分代码,但是在 angularjs 中,只有 module 可以动态加载,而且 component 等等都是在项目启动的时候就已经创建好了,如果你想动态的加载一个 component ,你只能去动态的加载注册这个 component 的 module 。所以这个 module 虽然不好用,但是又不得不用。
77 |
78 | 在整个项目中,首先定义了个三个 module :
79 |
80 | 1. 根模块:`angular.module('app')`
81 | 2. 通用模块:`angular.module('app.common')` 用于注册一些通用的 service、component等
82 | 3. 路由模块:`angular.module('app.router')` 配置路由和路由钩子
83 |
84 | 这三个 module 是在首次启动的时候就创建好了,所以在这个三个 module 中注册的 service、component等都是可以在整个项目中直接使用的,其次是每一个页面可以定义为一个 module ,这里建议将每一个顶级的路由对应的所有代码都放在一个 module 中,然在 router module 中根据需求直接 require 这个 module 或者使用 oclazyload 动态加载。如果项目加大,可以具体到每一个组路由定义为一个 module 。
85 |
86 | ### 3、干掉filter
87 |
88 | `filter` 也是一个饱受争议的设计,虽然它很有用也很好用,但是极为消耗性能的,在 `$digest` 过程中,filter会执行很至少两次,在很多关于angularJS的讨论中,都不推荐使用它。
89 |
90 | 其实 filter 的作用就是转换数据,当你需要处理数据的时候,你可以写一些专门处理数据的函数,然后在 controller 或者 service 中调用它,而视图只负责显示数据。
91 |
92 | ### 4、新的写法
93 |
94 | #### service
95 |
96 | 在 angularjs 中,注册一个自定义服务有三种写法:`provider`、`factory`、`service` ,我们都知道 factory 和 service 都是基于 provider 封装的。而 service 的写法是最适合用 es6 的 class 来写:
97 |
98 | ```javascript
99 | export class UserService {
100 |
101 | static $inject = ['$http'];
102 |
103 | constructor($http) {
104 | this.$http = $http;
105 | }
106 |
107 | getUser(userId) {
108 | return this.$http
109 | .get(`/user/${userId}`)
110 | .then(response => {
111 | if (response.status === 200) {
112 | return response.data;
113 | }
114 | });
115 | }
116 | }
117 | ```
118 |
119 | #### controller
120 |
121 | AngularJS 1.2 版本开始提供了一个 `controllerAs` 语法,让 `controller` 成为了一个纯净的 `ViewModel` ,而且 AngularJS 是通过 `new` 关键字把它当成构造函数来调用的,所以 controller 的写法和 service 完全一样:
122 |
123 | ```javascript
124 | class UserController {
125 |
126 | static $inject = ['$stateParams', 'UserService'];
127 |
128 | user;
129 |
130 | constructor($stateParams, UserService) {
131 | this.UserService = UserService;
132 | this.userId = $stateParams.id;
133 | }
134 |
135 | $onInit() {
136 | this.UserService
137 | .getUser(this.userId)
138 | .then(user => this.user = user);
139 | }
140 | }
141 | ```
142 |
143 | #### 更安全的注册
144 |
145 | 什么叫更安全的注册呢,我们以上面的 UserService 为例,虽然我们我们创建的类名为 UserService ,但在将 UserService 导入到 module 的是时候,我们可以注册为 XxxService :
146 |
147 | ```javascript
148 | import { UserService } from './user.service';
149 | angular
150 | .module('user')
151 | .service('XxxService', UserService);
152 | ```
153 |
154 | 这样一来就全乱了套了,那样才能避免这样的情况呢?angularjs 的 .service 、 .component、 .directive 等方法除了可以用 `userModule.service('UserService', UserService)` 的写法注册,还可以用对象的方式注册 `userModule.service({UserService: UserService})` 。利用对象方式的写法,我们可以 es6 的属性的简洁表示法来注册 `userModule.service({UserService})` ,通过这种学法来注册 service、component、directive 这样就可以避免上述情况发生了。
155 |
156 | ### 5、router
157 |
158 | [ui-router](https://ui-router.github.io/ng1/) 绝对是 AngularJS 的标配了,它最大的优势就是解决了路由的嵌套。
159 |
160 | `ui-router` 还支持 [Route to component](https://ui-router.github.io/ng1/tutorial/hellosolarsystem) ,所以项目里所有的页面都是组件。
161 |
162 | ### 6、按需加载
163 |
164 | 前面提到,在 angularjs 中按需加载的最小单位是 ngModule ,所以如果需要按需加载的话,将按需加载的代码用一个新的 angular.module 包装一下,然后通过 [oclazyload](git://github.com/ocombe/ocLazyLoad.git) 来加载 ngModule 。这里建议在路由层面做按需加载,具体代码可以查看 [routes.js](https://github.com/onlymisaky/AngularJS-ES6/blob/master/src/router/routes.js) 。
165 |
166 | ### 7、未解决的问题
167 |
168 | #### css Module
169 | 其实我已经有相应的解决方案了,我们可以在控制器中引入样式,然后将样式挂在 `vm` 上,然后在 `view` 中使用,具体的写法如下
170 | ```javascript
171 | import styles from './a.css';
172 | class User {
173 | constructor() {
174 | this.styles = styles;
175 | }
176 | }
177 | ```
178 | ```html
179 |
180 | ```
181 | ```css
182 | .text-center {
183 | text-align: center;
184 | }
185 | ```
186 | 在我看来这种实现方式是在是太惨不忍睹了,所以暂时用 **id选择器** 方式解决。
187 |
188 | ## 参考
189 | - [AngularJS styleguide (ES2015)](https://github.com/toddmotto/angularjs-styleguide)
190 | - [Angular 1.x和ES6的结合](https://github.com/xufei/blog/issues/29)
191 | - [Angular1.x + es6 开发风格指南](https://github.com/kuitos/kuitos.github.io/issues/34)
192 | - [Angular沉思录(三)Angular中的模块机制](https://github.com/xufei/blog/issues/17)
193 | - [基于ui-router的非侵入式angular按需加载方案](https://github.com/kuitos/kuitos.github.io/issues/31)
194 | - [Lazy load AngularJS with Webpack](https://michalzalecki.com/lazy-load-angularjs-with-webpack/)
195 | - [angular-1-5-components-app](https://github.com/toddmotto/angular-1-5-components-app)
196 |
--------------------------------------------------------------------------------