├── README.md
├── angular_programming.jpg
├── backend
├── .gitignore
├── _db.json
├── app.js
└── package.json
└── frontend
├── .angular-cli.json
├── .editorconfig
├── .gitignore
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── about
│ │ ├── about-routing.module.ts
│ │ ├── about.component.css
│ │ ├── about.component.html
│ │ ├── about.component.spec.ts
│ │ ├── about.component.ts
│ │ └── about.module.ts
│ ├── admin
│ │ ├── admin-routing.module.ts
│ │ ├── admin.component.html
│ │ ├── admin.component.ts
│ │ ├── admin.module.ts
│ │ ├── center
│ │ │ ├── center.component.css
│ │ │ ├── center.component.html
│ │ │ ├── center.component.ts
│ │ │ ├── index.ts
│ │ │ └── shared
│ │ │ │ ├── center-shared.module.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── questionnaire-controls
│ │ │ │ ├── index.ts
│ │ │ │ ├── questionnaire-controls.component.css
│ │ │ │ ├── questionnaire-controls.component.html
│ │ │ │ └── questionnaire-controls.component.ts
│ │ │ │ ├── questionnaire-detail
│ │ │ │ ├── index.ts
│ │ │ │ ├── questionnaire-detail.component.html
│ │ │ │ └── questionnaire-detail.component.ts
│ │ │ │ └── questionnaire-item
│ │ │ │ ├── index.ts
│ │ │ │ ├── questionnaire-item.component.html
│ │ │ │ └── questionnaire-item.component.ts
│ │ └── edit
│ │ │ ├── edit.component.css
│ │ │ ├── edit.component.html
│ │ │ ├── edit.component.ts
│ │ │ ├── index.ts
│ │ │ └── shared
│ │ │ ├── edit-shared.module.ts
│ │ │ ├── index.ts
│ │ │ ├── question-select
│ │ │ ├── index.ts
│ │ │ ├── question-select.component.css
│ │ │ ├── question-select.component.html
│ │ │ └── question-select.component.ts
│ │ │ └── questionnaire-outline
│ │ │ ├── index.ts
│ │ │ ├── questionnaire-outline.component.css
│ │ │ ├── questionnaire-outline.component.html
│ │ │ └── questionnaire-outline.component.ts
│ ├── app-routing.module.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── core
│ │ ├── core.module.ts
│ │ └── services
│ │ │ ├── auth-guard.service.ts
│ │ │ ├── index.ts
│ │ │ ├── login.service.ts
│ │ │ ├── questionnaire-old.service.ts
│ │ │ ├── questionnaire.service.ts
│ │ │ ├── register.service.ts
│ │ │ └── user.service.ts
│ ├── home
│ │ ├── home-routing.module.ts
│ │ ├── home.component.css
│ │ ├── home.component.html
│ │ ├── home.component.ts
│ │ └── home.module.ts
│ ├── published
│ │ ├── published-routing.module.ts
│ │ ├── published.component.css
│ │ ├── published.component.html
│ │ ├── published.component.ts
│ │ └── published.module.ts
│ ├── shared
│ │ ├── config
│ │ │ └── env.config.ts
│ │ ├── index.ts
│ │ ├── models
│ │ │ ├── index.ts
│ │ │ ├── question.model.ts
│ │ │ ├── questionnaire.model.ts
│ │ │ └── user.model.ts
│ │ ├── navbar
│ │ │ ├── index.ts
│ │ │ ├── navbar.component.css
│ │ │ ├── navbar.component.html
│ │ │ └── navbar.component.ts
│ │ ├── question
│ │ │ ├── index.ts
│ │ │ ├── question.component.ts
│ │ │ ├── question.module.ts
│ │ │ └── shared
│ │ │ │ ├── index.ts
│ │ │ │ ├── question-checkbox
│ │ │ │ ├── index.ts
│ │ │ │ ├── question-checkbox.component.html
│ │ │ │ └── question-checkbox.component.ts
│ │ │ │ ├── question-radio
│ │ │ │ ├── index.ts
│ │ │ │ ├── question-radio.component.html
│ │ │ │ └── question-radio.component.ts
│ │ │ │ ├── question-score
│ │ │ │ ├── index.ts
│ │ │ │ ├── question-score.component.html
│ │ │ │ └── question-score.component.ts
│ │ │ │ ├── question-shared.module.ts
│ │ │ │ └── question-text
│ │ │ │ ├── index.ts
│ │ │ │ ├── question-text.component.html
│ │ │ │ └── question-text.component.ts
│ │ ├── questionnaire
│ │ │ ├── index.ts
│ │ │ ├── questionnaire.component.css
│ │ │ ├── questionnaire.component.html
│ │ │ ├── questionnaire.component.ts
│ │ │ └── questionnaire.module.ts
│ │ └── shared.module.ts
│ └── user
│ │ ├── index.ts
│ │ ├── shared
│ │ ├── field
│ │ │ ├── field-base.ts
│ │ │ ├── field-radio.ts
│ │ │ ├── field-select.ts
│ │ │ ├── field-text.ts
│ │ │ ├── field-validators.ts
│ │ │ ├── field.component.css
│ │ │ ├── field.component.html
│ │ │ ├── field.component.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── login
│ │ │ ├── index.ts
│ │ │ ├── login.component.css
│ │ │ ├── login.component.html
│ │ │ └── login.component.ts
│ │ ├── register
│ │ │ ├── index.ts
│ │ │ ├── register.component.css
│ │ │ ├── register.component.html
│ │ │ └── register.component.ts
│ │ └── user-shared.module.ts
│ │ ├── user-routing.module.ts
│ │ └── user.module.ts
├── assets
│ ├── .gitkeep
│ ├── img
│ │ ├── about
│ │ │ ├── create.jpg
│ │ │ ├── faq.jpg
│ │ │ └── my.jpg
│ │ ├── edit
│ │ │ ├── checkbox.png
│ │ │ ├── radio.png
│ │ │ ├── star.png
│ │ │ └── text.png
│ │ ├── home
│ │ │ ├── banner_01.jpg
│ │ │ ├── banner_02.jpg
│ │ │ └── banner_03.jpg
│ │ └── notfound.png
│ └── svg
│ │ └── more.svg
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
└── typings.d.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/README.md:
--------------------------------------------------------------------------------
1 | # Angular 调查问卷示例项目
2 |
3 | ### 这是由广发证券互联网金融技术团队出品的原创书籍《揭秘 Angular》的第三部分项目实战的源代码。
4 |
5 | 
6 |
7 | 这个示例项目包含以下特点:
8 |
9 | * 遵循官方最佳实践的目录布局
10 | * 代码难易程度适中,方便学习
11 | * 功能丰富的脚手架,易于扩展使用
12 | * 简洁化的后端服务,聚焦前端框架学习
13 |
14 |
15 | ## 如何上手
16 |
17 | 调查问卷项目包括前端 frontend 目录以及后端 backend 目录。我们可以先运行后端服务,方便前端的注册与登录用户以及提供问卷相关的服务。安装过 Node.js 之后(确保你的 Node.js 版本为 6.x 及以上),在终端运行以下命令:
18 |
19 | ```bash
20 | cd backend
21 | npm install
22 | node app
23 | ```
24 |
25 | 接下来,将终端目录定位 frontend 之中,再运行以下命令:
26 |
27 | ```bash
28 | npm install
29 | npm start
30 | ```
31 |
32 | 以上前后台的命令都执行完后,即成功启动整个项目应用。
33 |
--------------------------------------------------------------------------------
/angular_programming.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angular-programming/angular-questionnaire/1c0b80face5bf6180d2807732d4389830bfca336/angular_programming.jpg
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-*
3 |
--------------------------------------------------------------------------------
/backend/_db.json:
--------------------------------------------------------------------------------
1 | {
2 | "user": [
3 | {
4 | "username": "admin",
5 | "password": "dc483e80a7a0bd9ef71d8cf973673924",
6 | "createDate": "2016-12-08"
7 | },
8 | {
9 | "username": "test",
10 | "password": "e10adc3949ba59abbe56e057f20f883e",
11 | "createDate": "5/2/2017"
12 | }
13 | ],
14 | "questionnaires": [
15 | {
16 | "title": "关于 Angular 2 在 2016 的发展现状调查",
17 | "starter": "关于在 2016 年 9 月中旬,Google 官方正式发布了 Angular 2 版本后的一些问卷调查",
18 | "ending": "",
19 | "state": 1,
20 | "questionList": [
21 | {
22 | "title": "如何评价 Angular 2.0 Final Release 的发布?",
23 | "type": 0,
24 | "answer": ""
25 | },
26 | {
27 | "title": "你会在下一个项目中考虑使用 Angular 2 吗?",
28 | "type": 1,
29 | "options": [
30 | {
31 | "key": 0,
32 | "value": "会"
33 | },
34 | {
35 | "key": 1,
36 | "value": "不会"
37 | }
38 | ],
39 | "answer": ""
40 | },
41 | {
42 | "title": "Angular 2 中数据状态管理方案有哪些?",
43 | "type": 0,
44 | "answer": ""
45 | },
46 | {
47 | "title": "Angular 2.0与 Angular1.x 版本相比,有哪些方面的改进呢?",
48 | "type": 3,
49 | "answer": ""
50 | },
51 | {
52 | "title": "Angular 2 能兼容到 IE 的什么版本?",
53 | "type": 2,
54 | "options": [
55 | {
56 | "key": 0,
57 | "value": "IE8 及以下版本"
58 | },
59 | {
60 | "key": 1,
61 | "value": "IE9"
62 | },
63 | {
64 | "key": 2,
65 | "value": "IE10"
66 | },
67 | {
68 | "key": 3,
69 | "value": "IE11"
70 | }
71 | ],
72 | "answer": {
73 | "selected": []
74 | }
75 | },
76 | {
77 | "title": "TypeScript 会不会借着 Angular,成为主流编程语言?",
78 | "type": 1,
79 | "options": [
80 | {
81 | "key": 0,
82 | "value": "会"
83 | },
84 | {
85 | "key": 1,
86 | "value": "不会"
87 | }
88 | ],
89 | "answer": ""
90 | },
91 | {
92 | "title": "关于如何正确的学习 Angular 2 的思考,您有哪些建议 ?",
93 | "type": 0,
94 | "answer": ""
95 | }
96 | ],
97 | "id": "eb988080-bdb1-11e6-9040-db60cb4931b1",
98 | "createDate": "2016-12-01"
99 | },
100 | {
101 | "title": "关于 2016 谷歌开发者大会在北京和上海召开的相关问卷调查",
102 | "starter": "",
103 | "ending": "",
104 | "state": 1,
105 | "questionList": [],
106 | "id": "b0b4d4b0-bdb5-11e6-8153-af1293d9eace",
107 | "createDate": "2016-12-08"
108 | },
109 | {
110 | "title": "Angular 2 对于 TypeScript 的生态建设影响的相关调查",
111 | "starter": "",
112 | "ending": "",
113 | "state": 0,
114 | "questionList": [],
115 | "id": "19655e30-bdb6-11e6-8153-af1293d9eace",
116 | "createDate": "2016-12-09"
117 | },
118 | {
119 | "title": "fdsg",
120 | "starter": "efwdsg",
121 | "ending": "",
122 | "state": 0,
123 | "questionList": [
124 | {
125 | "title": "问题标题fewgewfefewgew",
126 | "type": 0,
127 | "answer": ""
128 | }
129 | ],
130 | "id": "691dd290-3e99-11e7-95ac-21877dadc395",
131 | "createDate": "5/22/2017"
132 | }
133 | ]
134 | }
--------------------------------------------------------------------------------
/backend/app.js:
--------------------------------------------------------------------------------
1 | const jsonServer = require('json-server');
2 | const uuid = require('node-uuid');
3 | const crypto = require('crypto');
4 | const bodyParser = require('body-parser');
5 | const low = require('lowdb');
6 | const storage = require('lowdb/file-async');
7 |
8 | // import jsonServer from 'json-server';
9 | // import uuid from 'node-uuid';
10 | // import bodyParser from 'body-parser';
11 |
12 | // import low from 'lowdb';
13 | // import storage from 'lowdb/file-async';
14 |
15 |
16 | //创建一个Express服务器
17 | const server = jsonServer.create();
18 |
19 | //使用json-server默认选择的中间件(logger,static, cors和no-cache)
20 | server.use(jsonServer.defaults());
21 |
22 | //使用body-parser中间件
23 | server.use(bodyParser.json());
24 |
25 |
26 | //数据文件
27 | const dbfile = process.env.prod === '1' ? 'db.json' : '_db.json';
28 |
29 | //创建一个lowdb实例
30 | const db = low(dbfile, {storage});
31 |
32 |
33 | const md5 = str => crypto
34 | .createHash('md5')
35 | .update(str.toString())
36 | .digest('hex');
37 |
38 | //添加新问卷
39 | server.post('/questionnaire/add', (req, res) => {
40 | const item = req.body;
41 | item.id = uuid.v1();
42 | item.createDate = new Date().toLocaleDateString();
43 | db('questionnaires').push(item).then(() => {
44 | res.json({'success':true, data:item});
45 | });
46 | });
47 |
48 | //删除已有问卷
49 | server.get('/questionnaire/delete/:id', (req, res)=>{
50 | db('questionnaires').remove({id: req.params.id}).then(()=>{
51 | res.json({'success': true});
52 | });
53 | });
54 |
55 | //获取所有问卷
56 | server.get('/questionnaires', (req, res) => {
57 | const questionnaires = db('questionnaires');
58 | res.json({'success':true, data:questionnaires});
59 | });
60 |
61 | //根据id获取问卷数据
62 | server.get('/questionnaire/:id', (req, res) => {
63 | const questionnaire = db('questionnaires').find({id: req.params.id});
64 | res.json({'success':true, data:questionnaire});
65 | });
66 |
67 | //更新已有问卷
68 | server.post('/questionnaire/update', (req, res) => {
69 | const item = req.body;
70 | db('questionnaires').chain().find({id:item.id}).assign(item).value();
71 | res.json({'success':true, data:item});
72 | });
73 |
74 | //发布问卷
75 | server.post('/questionnaire/updateState', (req, res)=>{
76 | const params = req.body;
77 | const item = db('questionnaires').chain().find({id:params.id});
78 | item.assign({state:params.state}).value();
79 | res.json({'success':true, data:item});
80 | });
81 |
82 | // get userinfo
83 | server.get('/user/:username', (req, res) => {
84 | const user = db('user')
85 | .find({
86 | username: req.params.username
87 | });
88 |
89 | res.json({
90 | success: true,
91 | data: {
92 | username: user.username,
93 | createDate: user.createDate
94 | }
95 | });
96 | });
97 |
98 | // register
99 | server.post('/user/add', (req, res) => {
100 | const item = req.body;
101 | const user = db('user')
102 | .find({
103 | username: item.username
104 | });
105 | if (user) {
106 | res.json({
107 | success: false,
108 | message: `"${item.username}" is exists`
109 | })
110 | } else {
111 | item.password = md5(item.password);
112 | item.createDate = new Date().toLocaleDateString();
113 | db('user')
114 | .push(item)
115 | .then(() => {
116 | res.json({
117 | success: true
118 | });
119 | });
120 | }
121 | });
122 |
123 | // login
124 | server.post('/login', (req, res) => {
125 | const data = req.body || {};
126 | const username = data.username;
127 | const user = db('user')
128 | .find({
129 | username
130 | });
131 |
132 | if (user && user.password === md5(data.password)) {
133 | // todo reset session
134 | res.json({
135 | success: true
136 | });
137 | } else {
138 | res.json({
139 | success: false,
140 | message: 'username or password error'
141 | });
142 | }
143 | });
144 |
145 | //路由配置
146 | const router = jsonServer.router(dbfile);
147 | server.use('/api', router);
148 |
149 | //启动服务,并监听5000端口
150 | server.listen(5000, () => {
151 | console.log('server is running at ', 5000, dbfile);
152 | });
153 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "body-parser": "^1.14.2",
13 | "json-server": "^0.8.7",
14 | "lowdb": "^0.12.2",
15 | "node-uuid": "^1.4.7"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "angular-questionnaire"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "app",
21 | "styles": [
22 | "../node_modules/bootstrap/dist/css/bootstrap.min.css",
23 | "styles.css"
24 | ],
25 | "scripts": [],
26 | "environmentSource": "environments/environment.ts",
27 | "environments": {
28 | "dev": "environments/environment.ts",
29 | "prod": "environments/environment.prod.ts"
30 | }
31 | }
32 | ],
33 | "e2e": {
34 | "protractor": {
35 | "config": "./protractor.conf.js"
36 | }
37 | },
38 | "lint": [
39 | {
40 | "project": "src/tsconfig.app.json",
41 | "exclude": "**/node_modules/**"
42 | },
43 | {
44 | "project": "src/tsconfig.spec.json",
45 | "exclude": "**/node_modules/**"
46 | },
47 | {
48 | "project": "e2e/tsconfig.e2e.json",
49 | "exclude": "**/node_modules/**"
50 | }
51 | ],
52 | "test": {
53 | "karma": {
54 | "config": "./karma.conf.js"
55 | }
56 | },
57 | "defaults": {
58 | "styleExt": "css",
59 | "component": {}
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .vscode
3 |
--------------------------------------------------------------------------------
/frontend/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Ng2demoCliPage } from './app.po';
2 |
3 | describe('ng2demo-cli App', () => {
4 | let page: Ng2demoCliPage;
5 |
6 | beforeEach(() => {
7 | page = new Ng2demoCliPage();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('app works!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/frontend/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class Ng2demoCliPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016"
10 | ],
11 | "outDir": "../dist/out-tsc-e2e",
12 | "module": "commonjs",
13 | "target": "es6",
14 | "types":[
15 | "jasmine",
16 | "node"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | files: [
19 | { pattern: './src/test.ts', watched: false }
20 | ],
21 | preprocessors: {
22 | './src/test.ts': ['@angular/cli']
23 | },
24 | mime: {
25 | 'text/x-typescript': ['ts','tsx']
26 | },
27 | coverageIstanbulReporter: {
28 | reports: [ 'html', 'lcovonly' ],
29 | fixWebpackSourcePaths: true
30 | },
31 | angularCli: {
32 | environment: 'dev'
33 | },
34 | reporters: config.angularCli && config.angularCli.codeCoverage
35 | ? ['progress', 'coverage-istanbul']
36 | : ['progress', 'kjhtml'],
37 | port: 9876,
38 | colors: true,
39 | logLevel: config.LOG_INFO,
40 | autoWatch: true,
41 | browsers: ['Chrome'],
42 | singleRun: false
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-questionnaire",
3 | "version": "1.0.1",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/animations": "5.0.0",
16 | "@angular/common": "5.0.0",
17 | "@angular/compiler": "5.0.0",
18 | "@angular/core": "5.0.0",
19 | "@angular/forms": "5.0.0",
20 | "@angular/http": "5.0.0",
21 | "@angular/platform-browser": "5.0.0",
22 | "@angular/platform-browser-dynamic": "5.0.0",
23 | "@angular/router": "5.0.0",
24 | "core-js": "2.4.1",
25 | "rxjs": "5.5.2",
26 | "zone.js": "0.8.14",
27 | "bootstrap": "3.3.7",
28 | "moment": "2.18.1",
29 | "ngx-bootstrap": "1.8.0"
30 | },
31 | "devDependencies": {
32 | "@angular/cli": "1.5.0",
33 | "@angular/compiler-cli": "5.0.0",
34 | "@angular/language-service": "5.0.0",
35 | "@types/jasmine": "~2.5.53",
36 | "@types/jasminewd2": "~2.0.2",
37 | "@types/node": "~6.0.60",
38 | "codelyzer": "~3.2.0",
39 | "jasmine-core": "~2.6.2",
40 | "jasmine-spec-reporter": "~4.1.0",
41 | "karma": "~1.7.0",
42 | "karma-chrome-launcher": "~2.1.1",
43 | "karma-cli": "~1.0.1",
44 | "karma-coverage-istanbul-reporter": "1.2.1",
45 | "karma-jasmine": "~1.1.0",
46 | "karma-jasmine-html-reporter": "0.2.2",
47 | "protractor": "~5.1.2",
48 | "ts-node": "~3.2.0",
49 | "tslint": "~5.7.0",
50 | "typescript": "~2.4.2"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | beforeLaunch: function() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | },
27 | onPrepare() {
28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { AboutComponent } from './about.component';
4 | const routes: Routes = [
5 | { path: 'about', component: AboutComponent }
6 | ];
7 | @NgModule({
8 | imports: [
9 | RouterModule.forChild(routes)
10 | ],
11 | exports: [
12 | RouterModule
13 | ]
14 | })
15 | export class AboutRoutingModule { }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | padding: 16px;
4 | }
5 |
6 | accordion-group .col-lg-3 {
7 | text-align: center;
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
![{{item.text}}]()
8 |
9 |
10 | {{ item?.desc }}
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { By } from '@angular/platform-browser';
3 |
4 | import { DebugElement } from '@angular/core';
5 |
6 | import { AboutComponent } from './about.component';
7 | import { AccordionModule } from 'ngx-bootstrap';
8 | import { SharedModule } from '../shared/shared.module';
9 |
10 | export function main() {
11 | describe('About component', () => {
12 |
13 | let comp: AboutComponent;
14 | let fixture: ComponentFixture;
15 | let aboutEl: DebugElement;
16 |
17 | // setting module for testing
18 | // compile template and css
19 | beforeEach( async(() => {
20 | TestBed.configureTestingModule({
21 | imports: [SharedModule, AccordionModule],
22 | declarations: [AboutComponent],
23 | })
24 | .compileComponents();
25 | }));
26 |
27 | // synchronous beforeEach
28 | beforeEach(() => {
29 | fixture = TestBed.createComponent(AboutComponent);
30 | comp = fixture.componentInstance;
31 | aboutEl = fixture.debugElement;
32 |
33 | fixture.detectChanges(); // trigger initial data binding
34 | });
35 |
36 | it('should render accordion', ()=>{
37 |
38 | const de = aboutEl.queryAll(By.css('accordion'));
39 | expect(de.length).toBe(1);
40 | });
41 |
42 | it('should render correct accordion text', ()=>{
43 |
44 | // const de = aboutEl.queryAll(By.css('accordion'));
45 | expect(aboutEl.nativeElement.textContent).toContain('常见FAQ');
46 | });
47 | });
48 | }
49 |
50 | ////// Test Host Component //////
51 | import { Component } from '@angular/core';
52 |
53 | @Component({
54 | selector: 'test-cmp',
55 | template: ''
56 | })
57 | class TestComponent {}
58 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'sd-help',
5 | templateUrl: 'about.component.html',
6 | styleUrls: ['about.component.css']
7 | })
8 | export class AboutComponent {
9 |
10 | private items: any[];
11 |
12 | constructor() {
13 | this.items = [
14 | {
15 | icon:'./assets/img/about/faq.jpg',
16 | text:'常见FAQ',
17 | desc:'使用问卷收集数据的基本流程是:创建问卷、编辑问卷、设置问卷、预览问卷、回收问卷、数据分析等几个步骤。问卷提供三种创建问卷方式,创建空白问卷、选择问卷模板、文本编辑器。可以灵活设置问卷的显示、回收条件,在线实时统计回收结果,并可以导出Excel和SPSS数据。详细操作指引请参照帮助中心”操作指引“部分。',
18 | open: true
19 | },
20 | {
21 | icon:'./assets/img/about/create.jpg',
22 | text:'创建问卷',
23 | desc:'问卷,共有三种创建问卷的方式让您来选择使用:创建空白问卷、选择问卷模板、文本编辑器,可根据自身的使用习惯,选择最合适的方式来快速创建一份问卷。',
24 | open: false
25 | },
26 | {
27 | icon:'./assets/img/about/my.jpg',
28 | text:'我的问卷',
29 | desc:'问卷编辑、预览完成后,一份在线问卷即完成,此时可以通过对问卷的复制、编辑、删除、发布、统计、导出、分享等功能,实现对问卷的全面管理',
30 | open: false
31 | }
32 | ];
33 |
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { AboutComponent } from './about.component';
5 | import { AccordionModule } from 'ngx-bootstrap';
6 | import { AboutRoutingModule } from './about-routing.module';
7 |
8 | @NgModule({
9 | imports: [CommonModule, AccordionModule.forRoot(), AboutRoutingModule],
10 | declarations: [AboutComponent]
11 | })
12 | export class AboutModule { }
13 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/admin-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { AdminComponent } from './admin.component';
5 | import { CenterComponent } from './center/center.component';
6 | import { EditComponent } from './edit/edit.component';
7 |
8 | import { AuthGuard } from '../core/services/auth-guard.service';
9 |
10 | const routes: Routes = [
11 | {
12 | path: 'admin',
13 | component: AdminComponent,
14 | canActivate: [AuthGuard],
15 | children: [
16 | {
17 | path: '',
18 | children: [
19 | { path: 'center', component: CenterComponent },
20 | { path: 'edit/:id', component: EditComponent }
21 | ]
22 | }
23 | ]
24 | }
25 | ];
26 |
27 | @NgModule({
28 | imports: [RouterModule.forChild(routes)],
29 | exports: [RouterModule]
30 | })
31 | export class AdminRoutingModule { }
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/admin.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/admin.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'sd-admin',
5 | templateUrl: 'admin.component.html',
6 | })
7 | export class AdminComponent { }
8 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/admin.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { AdminComponent } from './admin.component';
5 | import { CenterComponent } from './center';
6 | import { EditComponent } from './edit';
7 |
8 | import { AdminRoutingModule } from './admin-routing.module';
9 | import { SharedModule } from '../shared/shared.module';
10 | import { QuestionnaireModule } from '../shared/questionnaire/questionnaire.module';
11 | import { CenterSharedModule } from './center/shared/center-shared.module';
12 | import { EditSharedModule } from './edit/shared/edit-shared.module';
13 |
14 | import { TabsModule } from 'ngx-bootstrap';
15 |
16 | @NgModule({
17 | imports: [CommonModule, AdminRoutingModule, SharedModule, TabsModule.forRoot(), QuestionnaireModule, CenterSharedModule, EditSharedModule],
18 | declarations: [
19 | AdminComponent,
20 | CenterComponent,
21 | EditComponent
22 | ]
23 | })
24 | export class AdminModule { }
25 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/center.component.css:
--------------------------------------------------------------------------------
1 | .center-container{
2 | padding:20px;
3 | }
4 |
5 | .center-container .show-empty{
6 | width: 360px;
7 | margin: 0 auto;
8 | height: 260px;
9 | text-align: center;
10 | padding-top: 200px;
11 | background: url(/assets/img/notfound.png) no-repeat center center;
12 | }
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/center.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 尚未创建任何问卷,马上
创建一个吧!
4 |
5 |
6 |
7 |
18 |
19 |
20 |
问卷详情
21 |
22 |
23 |
24 |
25 |
26 |
问卷管理
27 |
28 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/center.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
2 |
3 | import { QuestionnaireService } from '../../core/services/questionnaire.service';
4 | import { QuestionnaireModel, QuestionnaireState } from '../../shared/models/questionnaire.model';
5 |
6 | @Component({
7 | selector: 'sd-center',
8 | templateUrl: 'center.component.html',
9 | styleUrls: ['center.component.css']
10 | })
11 | export class CenterComponent implements OnInit {
12 |
13 | private questionnaires:QuestionnaireModel[] = [];
14 | private selectedQuestionnaire:QuestionnaireModel;
15 | private selectedIndex:number;
16 | private isEmpty:boolean;
17 |
18 | constructor(private cd:ChangeDetectorRef, private questionnaireService:QuestionnaireService) { }
19 |
20 | ngOnInit() {
21 | this.questionnaireService.getQuestionnaires()
22 | .subscribe(
23 | questionnaires => {
24 | // 后端返回空对象或者空的问卷数组
25 | if(!questionnaires || questionnaires.length === 0){
26 | this.isEmpty = true;
27 | return;
28 | }
29 | this.isEmpty = false;
30 | this.questionnaires = questionnaires;
31 | this.selectedQuestionnaire = this.questionnaires[0];
32 | this.selectedIndex = 0;
33 | },
34 | error => console.error(error)
35 | );
36 | }
37 |
38 | onSelect(questionnaire:QuestionnaireModel, index:number) {
39 | this.selectedQuestionnaire = questionnaire;
40 | this.selectedIndex = index;
41 | }
42 |
43 | onDeleteQuestionnaire() {
44 | this.questionnaireService.deleteQuestionnaire(this.selectedQuestionnaire.id)
45 | .subscribe(
46 | res => {
47 | this.questionnaires.splice(this.selectedIndex, 1);
48 | //全部删除
49 | if(this.questionnaires.length === 0){
50 | this.isEmpty = true;
51 | } else {
52 | this.selectedQuestionnaire = this.questionnaires[0];
53 | this.selectedIndex = 0;
54 | }
55 | },
56 | error => console.log(error)
57 |
58 | );
59 | }
60 |
61 | onPublishQuestionnaire(){
62 | this.questionnaireService.updateQuestionnaireState(this.selectedQuestionnaire.id, QuestionnaireState.Published)
63 | .subscribe(
64 | questionnaire => {
65 | this.selectedQuestionnaire.state = QuestionnaireState.Published;
66 | this.questionnaires[this.selectedIndex] = Object.assign({}, this.selectedQuestionnaire);
67 | this.cd.detectChanges();
68 | },
69 | error => console.log(error)
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./center.component";
2 | export * from './shared/questionnaire-controls/index';
3 | export * from './shared/questionnaire-detail/index';
4 | export * from './shared/questionnaire-item/index';
5 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/center-shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { QuestionnaireItemComponent } from './questionnaire-item/index';
5 | import { QuestionnaireDetailComponent } from './questionnaire-detail/index';
6 | import { QuestionnaireControlsComponent } from './questionnaire-controls/index';
7 |
8 | @NgModule({
9 | imports: [CommonModule],
10 | declarations: [QuestionnaireItemComponent, QuestionnaireDetailComponent, QuestionnaireControlsComponent],
11 | exports: [QuestionnaireItemComponent, QuestionnaireDetailComponent, QuestionnaireControlsComponent, CommonModule]
12 | })
13 | export class CenterSharedModule { }
14 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './questionnaire-item/index';
2 | export * from './questionnaire-detail/index';
3 | export * from './questionnaire-controls/index';
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-controls/index.ts:
--------------------------------------------------------------------------------
1 | export * from './questionnaire-controls.component';
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-controls/questionnaire-controls.component.css:
--------------------------------------------------------------------------------
1 | .control-text{
2 | display: block;
3 | text-align: center;
4 | font-size: 16px;
5 | }
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-controls/questionnaire-controls.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
13 |
14 |
15 |
19 |
20 |
21 |
25 |
26 |
27 |
31 |
32 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-controls/questionnaire-controls.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, Output } from '@angular/core';
2 | import { Router } from '@angular/router';
3 |
4 | import { QuestionnaireModel } from '../../../../shared/models/questionnaire.model';
5 |
6 | @Component({
7 | selector: 'questionnaire-controls',
8 | templateUrl: 'questionnaire-controls.component.html',
9 | styleUrls: ['questionnaire-controls.component.css']
10 | })
11 | export class QuestionnaireControlsComponent {
12 |
13 | @Input() questionnaire:QuestionnaireModel;
14 | @Output() deleteQuestionnaire: EventEmitter = new EventEmitter();
15 | @Output() publishQuestionnaire: EventEmitter = new EventEmitter();
16 |
17 | constructor(private router: Router) {}
18 |
19 | onEdit(){
20 | this.router.navigateByUrl('admin/edit/' + this.questionnaire.id);
21 | }
22 |
23 | onPreview(){
24 | this.router.navigateByUrl('published/' + this.questionnaire.id);
25 | }
26 |
27 | onDelete(){
28 | this.deleteQuestionnaire.emit();
29 | }
30 |
31 | onPublish(){
32 | this.publishQuestionnaire.emit();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-detail/index.ts:
--------------------------------------------------------------------------------
1 | export * from './questionnaire-detail.component';
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-detail/questionnaire-detail.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 问卷标题: |
4 | {{questionnaire.title}} |
5 |
6 |
7 | 问卷简介: |
8 | {{questionnaire.starter}} |
9 |
10 |
11 | 问题总数: |
12 | {{questionnaire.questionList.length}} |
13 |
14 |
15 | 创建时间: |
16 | {{questionnaire.createDate}} |
17 |
18 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-detail/questionnaire-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | import { QuestionnaireModel } from '../../../../shared/models/questionnaire.model';
4 |
5 | @Component({
6 | selector: 'questionnaire-detail',
7 | templateUrl: 'questionnaire-detail.component.html'
8 | })
9 | export class QuestionnaireDetailComponent {
10 |
11 | @Input() questionnaire: QuestionnaireModel;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-item/index.ts:
--------------------------------------------------------------------------------
1 | export * from './questionnaire-item.component';
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-item/questionnaire-item.component.html:
--------------------------------------------------------------------------------
1 | {{questionnaire.title}}
2 | {{stateText}}
3 | {{questionnaire.createDate}}
--------------------------------------------------------------------------------
/frontend/src/app/admin/center/shared/questionnaire-item/questionnaire-item.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnChanges, SimpleChanges, Input } from '@angular/core';
2 |
3 | import { QuestionnaireModel, QuestionnaireState } from '../../../../shared/models/questionnaire.model';
4 |
5 | @Component({
6 | selector: 'questionnaire-item',
7 | templateUrl: 'questionnaire-item.component.html'
8 | })
9 | export class QuestionnaireItemComponent implements OnInit, OnChanges {
10 |
11 | @Input() questionnaire:QuestionnaireModel;
12 |
13 | private stateText:String;
14 | private stateClass:String;
15 |
16 | ngOnChanges(changes: SimpleChanges){
17 | let questionnaireChange = changes['questionnaire'];
18 | if(questionnaireChange && questionnaireChange.previousValue && questionnaireChange.previousValue.state &&
19 | questionnaireChange.currentValue.state !== questionnaireChange.previousValue.state){
20 | this.questionnaire = changes['questionnaire'].currentValue;
21 | this.setState();
22 | }
23 | }
24 |
25 | ngOnInit() {
26 | this.setState();
27 | }
28 |
29 | setState(){
30 | switch(this.questionnaire.state){
31 | case QuestionnaireState.Created:
32 | this.stateText = '已创建';
33 | this.stateClass = 'label-warning';
34 | break;
35 | case QuestionnaireState.Published:
36 | this.stateText = '回收中';
37 | this.stateClass = 'label-info';
38 | break;
39 | case QuestionnaireState.Finished:
40 | this.stateText = '已结束';
41 | this.stateClass = 'label-success';
42 | break;
43 | default:
44 | break;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/edit.component.css:
--------------------------------------------------------------------------------
1 | .questionnaire-container{
2 | background-color: #f0f0f0;
3 | min-height: 800px;
4 | }
5 |
6 | .edit-container .sidebar{
7 | padding:20px;
8 | }
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/edit.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/edit.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute, Router } from '@angular/router';
3 |
4 | import { QuestionType } from '../../shared/models/question.model';
5 | import { QuestionnaireService } from '../../core/services/questionnaire.service';
6 | import { QuestionnaireModel, QuestionnaireState } from '../../shared/models/questionnaire.model';
7 |
8 | @Component({
9 | selector: 'sd-edit',
10 | templateUrl: 'edit.component.html',
11 | styleUrls: ['edit.component.css']
12 | })
13 | export class EditComponent implements OnInit {
14 |
15 | private questionnaire: QuestionnaireModel;
16 | private id: string;
17 |
18 | constructor(private questionnaireService:QuestionnaireService, private activatedRoute:ActivatedRoute,
19 | private router: Router) {
20 |
21 | //初始化一个空的问卷对象
22 | this.questionnaire = {
23 | title: '',
24 | starter: '',
25 | ending: '',
26 | state: QuestionnaireState.Created,
27 | questionList: []
28 | };
29 | }
30 |
31 | ngOnInit() {
32 | //初始化问卷数据
33 | this.id = this.activatedRoute.snapshot.params['id'];
34 |
35 | if(this.id && this.id !== '0'){
36 | //id存在,代表当前页面为编辑已有问卷页面,调用服务获取问卷对象信息
37 | this.questionnaireService.getQuestionnaireById(this.id)
38 | .subscribe(
39 | questionnaire => this.questionnaire = questionnaire,
40 | error => console.log(error)
41 | );
42 | }
43 | }
44 |
45 | onAddQuestion(type: QuestionType) {
46 | //添加问题到问卷的问题列表
47 | switch(type){
48 | case QuestionType.Text:
49 | case QuestionType.Score:
50 | this.questionnaire.questionList.push({
51 | title: '问题标题',
52 | type: type,
53 | answer: ''
54 | });
55 | break;
56 | case QuestionType.SingleSelect:
57 | this.questionnaire.questionList.push({
58 | title: '问题标题',
59 | type: type,
60 | options: [{'key': 0, 'value': '选项1'}],
61 | answer:''
62 | });
63 | break;
64 | case QuestionType.MultiSelect:
65 | this.questionnaire.questionList.push({
66 | title: '问题标题',
67 | type: type,
68 | options: [{'key': 0, 'value': '选项1'}],
69 | answer:{}
70 | });
71 | break;
72 | }
73 | }
74 |
75 | onSubmitQuestionniare(questionnaire: QuestionnaireModel) {
76 | //保存问卷或回收答案
77 | if (questionnaire.state === QuestionnaireState.Created) {
78 | if (this.id && this.id !== '0') {
79 | //编辑已有问卷
80 | this.questionnaireService.updateQuestionnaire(questionnaire)
81 | .subscribe(
82 | questionnaire => this.gotoCenter(),
83 | error => console.log(error));
84 | } else {
85 | //创建新问卷
86 | this.questionnaireService.addQuestionnaire(questionnaire)
87 | .subscribe(
88 | questionnaire => this.gotoCenter(),
89 | error=> console.error(error));
90 | }
91 | }
92 | }
93 |
94 | gotoCenter() {
95 | this.router.navigateByUrl('admin/center');
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./edit.component";
2 | export * from './shared/question-select/index';
3 | export * from './shared/questionnaire-outline/index';
4 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/edit-shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { QuestionSelectComponent } from './question-select/index';
5 | import { QuestionnaireOutlineComponent } from './questionnaire-outline/index';
6 |
7 | @NgModule({
8 | imports: [CommonModule],
9 | declarations: [QuestionSelectComponent, QuestionnaireOutlineComponent],
10 | exports: [QuestionSelectComponent, QuestionnaireOutlineComponent, CommonModule]
11 | })
12 | export class EditSharedModule { }
13 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './question-select/index';
2 | export * from './questionnaire-outline/index';
3 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/question-select/index.ts:
--------------------------------------------------------------------------------
1 | export * from './question-select.component';
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/question-select/question-select.component.css:
--------------------------------------------------------------------------------
1 | .question-controls li{
2 | list-style-type: none;
3 | border:none;
4 | }
5 |
6 | .question-controls li.list-group-item{
7 | border-bottom: 1px solid #ccc;
8 | }
9 |
10 | .question-controls li a{
11 | background-repeat: no-repeat;
12 | padding-left: 24px;
13 | cursor: pointer;
14 | text-decoration: none;
15 | }
16 |
17 | .icon-text {
18 | background-image: url(/assets/img/edit/text.png);
19 | }
20 |
21 | .icon-star {
22 | background-image: url(/assets/img/edit/star.png);
23 | }
24 |
25 | .icon-radio {
26 | background-image: url(/assets/img/edit/radio.png);
27 | }
28 |
29 | .icon-checkbox {
30 | background-image: url(/assets/img/edit/checkbox.png);
31 | }
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/question-select/question-select.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/question-select/question-select.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Output } from '@angular/core';
2 | import { QuestionType } from '../../../../shared/models/question.model';
3 |
4 | @Component({
5 | selector: 'question-select',
6 | styleUrls: ['question-select.component.css'],
7 | templateUrl: 'question-select.component.html'
8 | })
9 | export class QuestionSelectComponent {
10 |
11 | @Output() addQuestionRequest = new EventEmitter();
12 |
13 | private controls:any[];
14 |
15 | constructor() {
16 | this.controls = [
17 | {type: QuestionType.Text, label: '文本问题', iconClass: 'icon-text'},
18 | {type: QuestionType.SingleSelect, label: '单选问题', iconClass: 'icon-radio'},
19 | {type: QuestionType.MultiSelect, label: '多选问题', iconClass: 'icon-checkbox'},
20 | {type: QuestionType.Score, label: '分值问题', iconClass: 'icon-star'}
21 | ];
22 | }
23 |
24 | onAddQuestion(control: any) {
25 | this.addQuestionRequest.emit(control.type);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/questionnaire-outline/index.ts:
--------------------------------------------------------------------------------
1 | export * from './questionnaire-outline.component';
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/questionnaire-outline/questionnaire-outline.component.css:
--------------------------------------------------------------------------------
1 | .questionnaire-outline{
2 | margin-top: 20px;
3 | }
4 |
5 | .questionnaire-outline li{
6 | font-size: 12px;
7 | line-height: 20px;
8 | }
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/questionnaire-outline/questionnaire-outline.component.html:
--------------------------------------------------------------------------------
1 |
2 | -
3 | {{(i + 1) + ". " + q.title}}
4 |
-
5 |
--------------------------------------------------------------------------------
/frontend/src/app/admin/edit/shared/questionnaire-outline/questionnaire-outline.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | import { QuestionnaireModel } from '../../../../shared/models/questionnaire.model';
4 |
5 | @Component({
6 | selector: 'questionnaire-outline',
7 | styleUrls: ['questionnaire-outline.component.css'],
8 | templateUrl: 'questionnaire-outline.component.html'
9 | })
10 | export class QuestionnaireOutlineComponent {
11 |
12 | @Input() questionnaire: QuestionnaireModel;
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | export const routes: Routes = [
5 | { path: '', redirectTo: '/home', pathMatch: 'full' }
6 | ];
7 |
8 | @NgModule({
9 | imports: [RouterModule.forRoot(routes)],
10 | exports: [RouterModule]
11 | })
12 | export class AppRoutingModule { }
13 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | color: #369;
3 | font-family: Arial, Helvetica, sans-serif;
4 | font-size: 250%;
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 |
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async(() => {
7 | TestBed.configureTestingModule({
8 | declarations: [
9 | AppComponent
10 | ],
11 | }).compileComponents();
12 | }));
13 |
14 | it('should create the app', async(() => {
15 | const fixture = TestBed.createComponent(AppComponent);
16 | const app = fixture.debugElement.componentInstance;
17 | expect(app).toBeTruthy();
18 | }));
19 |
20 | it(`should have as title 'app works!'`, async(() => {
21 | const fixture = TestBed.createComponent(AppComponent);
22 | const app = fixture.debugElement.componentInstance;
23 | expect(app.title).toEqual('app works!');
24 | }));
25 |
26 | it('should render title in a h1 tag', async(() => {
27 | const fixture = TestBed.createComponent(AppComponent);
28 | fixture.detectChanges();
29 | const compiled = fixture.debugElement.nativeElement;
30 | expect(compiled.querySelector('h1').textContent).toContain('app works!');
31 | }));
32 | });
33 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.css']
7 | })
8 | export class AppComponent {
9 | title = 'My First Anagular App!';
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { AppComponent } from './app.component';
6 | import { AppRoutingModule } from './app-routing.module';
7 | import { CoreModule } from './core/core.module';
8 | import { AdminModule } from './admin/admin.module';
9 | import { AboutModule } from './about/about.module';
10 | import { HomeModule } from './home/home.module';
11 | import { UserModule } from './user/user.module';
12 | import { PublishedModule } from './published/published.module';
13 |
14 |
15 | @NgModule({
16 | declarations: [
17 | AppComponent
18 | ],
19 | imports: [CoreModule, AdminModule, BrowserModule, AppRoutingModule, PublishedModule, UserModule, HomeModule, AboutModule],
20 | providers: [],
21 | bootstrap: [AppComponent]
22 | })
23 | export class AppModule { }
24 |
--------------------------------------------------------------------------------
/frontend/src/app/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, Optional, SkipSelf } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { RouterModule } from '@angular/router';
4 | import { HttpModule } from '@angular/http';
5 | import { HttpClientModule } from "@angular/common/http";
6 |
7 | import { AuthGuard } from './services/auth-guard.service';
8 | import { LoginService } from './services/login.service';
9 | import { QuestionnaireService } from './services/questionnaire.service';
10 | import { RegisterService } from './services/register.service';
11 | import { UserService } from './services/user.service';
12 |
13 | @NgModule({
14 | imports: [CommonModule, RouterModule, HttpClientModule],
15 | declarations: [],
16 | providers: [QuestionnaireService, UserService, LoginService, RegisterService, AuthGuard],
17 | exports: [HttpModule]
18 | })
19 | export class CoreModule {
20 |
21 | constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
22 | if (parentModule) {
23 | throw new Error(
24 | 'CoreModule is already loaded. Import it in the AppModule only');
25 | }
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/frontend/src/app/core/services/auth-guard.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
3 |
4 | import { UserService } from './user.service';
5 |
6 | @Injectable()
7 | export class AuthGuard implements CanActivate {
8 |
9 | constructor(private userService:UserService, private route:Router) { }
10 |
11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
12 | if(this.userService.isLogin) {
13 | return true;
14 | }
15 | this.route.navigate(['/login'],{queryParams:{returnUrl:state.url}});
16 | return false;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/app/core/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user.service';
2 | export * from './questionnaire.service';
3 | export * from './login.service';
4 | export * from './register.service';
5 | export * from './auth-guard.service';
6 |
--------------------------------------------------------------------------------
/frontend/src/app/core/services/login.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { FormControl, FormGroup, Validators } from '@angular/forms';
3 | import { Http } from '@angular/http';
4 | import { Headers } from '@angular/http';
5 | import { Observable } from 'rxjs/Observable';
6 | import { Observer } from 'rxjs/Observer';
7 |
8 | import { FieldBase } from '../../user/shared/field/field-base';
9 | import { FieldText } from '../../user/shared/field/field-text';
10 | import { FieldValidators } from '../../user/shared/field/field-validators';
11 | import { SITE_HOST_URL } from '../../shared/index';
12 |
13 | @Injectable()
14 | export class LoginService {
15 |
16 | private loginUrl = `${SITE_HOST_URL}login`;
17 |
18 | constructor(private http: Http) { }
19 |
20 | getFields() {
21 | let fields: FieldBase[] = [
22 | new FieldText({
23 | key: 'username',
24 | label: '用户名',
25 | value: '',
26 | required: true,
27 | pattern: 'username',
28 | order: 1
29 | }),
30 | new FieldText({
31 | key: 'password',
32 | label: '密码',
33 | type: 'password',
34 | value: '',
35 | required: true,
36 | pattern: 'password',
37 | order: 2
38 | }),
39 | ];
40 | return fields.sort((a, b) => a.order - b.order);
41 | }
42 |
43 | toFormGroup(fields: FieldBase[]) {
44 | let group: any = {};
45 |
46 | fields.forEach(field => {
47 | group[field.key] =
48 | field.pattern ?
49 | new FormControl(field.value || '', (FieldValidators)[field.pattern]) :
50 | field.required ?
51 | new FormControl(field.value || '', Validators.required) :
52 | new FormControl(field.value || '');
53 | });
54 | return new FormGroup(group);
55 | }
56 |
57 | login(data: Object) {
58 | let body = JSON.stringify(data);
59 | let headers = new Headers();
60 | headers.append('Content-Type', 'application/json');
61 | return new Observable((observer: Observer)=>{
62 | this.http.post(this.loginUrl, body, { headers }).subscribe(res=>{
63 | let body = res.json();
64 | if (body && body.success) {
65 | // this.userService.isLogin = true;
66 | // this.userService.userName = data['username'];
67 | observer.next(res);
68 | observer.complete();
69 | }
70 | });
71 | });
72 | }
73 |
74 | logout() {
75 | //this.userService.isLogin = false;
76 | //this.userService.userName = '';
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/frontend/src/app/core/services/questionnaire-old.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http, Headers, RequestOptions, Response } from '@angular/http';
3 | import { Observable } from 'rxjs/Rx';
4 |
5 | import { QuestionnaireModel } from '../../shared/models/questionnaire.model';
6 | import { SITE_HOST_URL } from '../../shared/index';
7 |
8 | @Injectable()
9 | export class QuestionnaireService {
10 | constructor(private http: Http) {}
11 |
12 | private handleError(error: Response) {
13 | console.error(error);
14 | return Observable.throw(error.json().error || 'server error');
15 | }
16 |
17 | //根据id获取问卷信息
18 | getQuestionnaireById(id: string) {
19 | return this.http
20 | .get(SITE_HOST_URL + 'questionnaire/' + id)
21 | .map(res => res.json().data)
22 | .catch(this.handleError);
23 | }
24 |
25 | getQuestionnaires() {
26 | return this.http
27 | .get(SITE_HOST_URL + 'questionnaires')
28 | .map(res => res.json().data)
29 | .catch(this.handleError);
30 | }
31 |
32 | //添加新问卷
33 | addQuestionnaire(questionnaire: QuestionnaireModel) {
34 | let body = JSON.stringify(questionnaire);
35 | let headers = new Headers({ 'Content-Type': 'application/json' });
36 | let options = new RequestOptions({ headers: headers });
37 |
38 | return this.http
39 | .post(SITE_HOST_URL + 'questionnaire/add', body, options)
40 | .map(res => res.json().data)
41 | .catch(this.handleError);
42 | }
43 |
44 | //删除已有问卷
45 | deleteQuestionnaire(id: string) {
46 | return this.http
47 | .get(SITE_HOST_URL + 'questionnaire/delete/' + id)
48 | .map(res =>