├── .babelrc
├── .gitignore
├── .idea
├── .gitignore
├── frontend.iml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── README.md
├── bash.exe.stackdump
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── server
├── nginx.conf
└── vqa_web_api.py
├── src
├── Router
│ └── RouterConfig.js
├── actions
│ └── actionCreators.js
├── assets
│ ├── pic
│ │ ├── add_picture.png
│ │ ├── app_scan.jpg
│ │ ├── login.png
│ │ ├── vqa.png
│ │ └── wechat.jpg
│ ├── scss
│ │ ├── components
│ │ │ ├── calendar.scss
│ │ │ ├── foot.scss
│ │ │ ├── logo.scss
│ │ │ ├── page.scss
│ │ │ ├── page
│ │ │ │ ├── access.scss
│ │ │ │ ├── charts.scss
│ │ │ │ ├── datetime.scss
│ │ │ │ ├── introduce.scss
│ │ │ │ ├── login.scss
│ │ │ │ ├── modify.scss
│ │ │ │ ├── record.scss
│ │ │ │ ├── register.scss
│ │ │ │ └── upload.scss
│ │ │ ├── sider.scss
│ │ │ └── toptitle.scss
│ │ ├── index.scss
│ │ └── variables.scss
│ └── sodagreen.mp3
├── components
│ ├── calendar.jsx
│ ├── foot.jsx
│ ├── logo.jsx
│ ├── sider.jsx
│ └── toptitle.jsx
├── constant
│ └── constants.js
├── containers
│ ├── datetime.jsx
│ ├── echartsAnalysis.jsx
│ ├── introduce.jsx
│ ├── record.jsx
│ ├── user
│ │ ├── login.jsx
│ │ ├── modify.jsx
│ │ └── register.jsx
│ └── vqa
│ │ ├── access.jsx
│ │ └── upload.jsx
├── index.js
├── index.scss
├── redux
│ ├── menu.js
│ ├── reducers.js
│ ├── update.js
│ └── user.js
├── serviceWorker.js
├── setupProxy.js
├── setupTests.js
└── store
│ └── store.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react-app"
5 | ],
6 | "plugins": [
7 | "transform-decorators-legacy",
8 | [
9 | "import",
10 | {
11 | "libraryName": "antd",
12 | "style": "css"
13 | }
14 | ]
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Default ignored files
3 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/frontend.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 | This project is a BlindVQA Mannagement System based on tech stacks as follows.
3 | ### Frontend
4 | * [React.js](https://github.com/facebook/react)
5 | * react-router
6 | * [Redux](https://github.com/reduxjs/redux)
7 | * [axios](https://github.com/axios/axios)
8 | * [Ant Design](https://ant.design/docs/react/introduce-cn)
9 | * [Sass](https://www.sass.hk/docs/)
10 | * [Echarts](https://www.echartsjs.com/zh/index.html)
11 | ### Server
12 | * [Nginx](https://github.com/nginx/nginx)
13 | * [Docker](https://github.com/yeasy/docker_practice)
14 | * [FRP](https://github.com/fatedier/frp)
15 | * [uWSGI](https://github.com/tiangolo/uwsgi-nginx-flask-docker)
16 | ### Backend
17 | * [Flask](https://github.com/pallets/flask)
18 | * MySQL
19 | * [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy)
20 | * [Pytorch](https://github.com/yunjey/pytorch-tutorial) for Deep Learning and [VQA model](https://github.com/MILVLG/mcan-vqa)
21 |
22 | ## Contents
23 | ```js
24 | ### 目录结构介绍
25 | ***├── build // 放在Flask所在目录下的static文件夹内***
26 | ***├── server // 后端Flask web应用程序***
27 | ***├── public ***
28 | ***├── node_modules // 项目的包依赖***
29 | ***├── src // 源码目录***
30 | ***│ ├── assets // 存放项目的图片资源和SCSS文件***
31 | ***│ ├── components // 页面自定义组件***
32 | ***│ ├── containers // 页面容器***
33 | ***│ ├── constant // 变量全局配置***
34 | ***│ ├── store // 创建store仓库***
35 | ***│ ├── redux // 组合reducers***
36 | ***│ ├── actions // 管理actions操作***
37 | ***│ ├── Router // 页面路由配置 ***
38 | ***│ ├── setupProxy.js // 设置跨域请求代理***
39 | ***│ ├── index.js // 程序入口文件,加载各种公共组件***
40 | ***├── .babelrc // babel配置文件 ***
41 | ```
42 | ## Project Deployment
43 | ```bash
44 | npm run/yarn build
45 | rsync /path/to/build root@cloud_server_ip:/build
46 | cd /path/to/vqa_flask_project
47 | python vqa_web_api.py
48 | cd /path/to/frpc
49 | ./frpc -c ./frpc.ini
50 | ssh -o ServerAliveInterval=60 root@cloud_server_ip
51 | nginx -s reload
52 | docker start/restart frps_container_name
53 | ```
54 | ## Login Page
55 | 
56 |
--------------------------------------------------------------------------------
/bash.exe.stackdump:
--------------------------------------------------------------------------------
1 | Stack trace:
2 | Frame Function Args
3 | 000FFFFA1A8 0018005D19E (0018024212D, 00180223C26, 000FFFFA1A8, 000FFFF90A0)
4 | 000FFFFA1A8 001800463F9 (00000000000, 00000000000, 00000000000, 001005F0D44)
5 | 000FFFFA1A8 00180046432 (001802421E9, 000FFFFA058, 000FFFFA1A8, 00000000000)
6 | 000FFFFA1A8 001800A9EEF (00000000000, 00000000000, 00000000000, 00000000000)
7 | 000FFFFA1A8 001800AA13D (000FFFFA1C0, 00000000000, 00000000000, 00000000000)
8 | 000FFFFA430 001800AB3A4 (000FFFFA1C0, 00000000000, 00000000000, 00000000000)
9 | End of stack trace
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^3.26.7",
7 | "antd-mobile": "^2.3.1",
8 | "axios": "^0.19.2",
9 | "babel-cli": "^6.26.0",
10 | "blob": "^0.0.5",
11 | "cssnano": "^4.1.10",
12 | "echarts": "^4.6.0",
13 | "express": "^4.17.1",
14 | "history": "^4.10.1",
15 | "moment": "^2.24.0",
16 | "node-sass": "^4.13.1",
17 | "postcss-aspect-ratio-mini": "^1.0.1",
18 | "postcss-cssnext": "^3.1.0",
19 | "postcss-px-to-viewport": "^1.1.1",
20 | "postcss-viewport-units": "^0.1.6",
21 | "postcss-write-svg": "^3.0.1",
22 | "prop-types": "^15.7.2",
23 | "react": "^16.12.0",
24 | "react-dom": "^16.12.0",
25 | "react-highlight-words": "^0.16.0",
26 | "react-materialize": "^3.5.9",
27 | "react-redux": "^7.1.3",
28 | "react-router": "^5.1.2",
29 | "react-router-dom": "^5.1.2",
30 | "react-router-redux": "^4.0.8",
31 | "react-scripts": "3.3.0",
32 | "react-tooltip": "^3.11.5",
33 | "reactjs-swiper": "^0.1.0",
34 | "redux": "^4.0.5",
35 | "redux-logger": "^3.0.6",
36 | "redux-persist": "^6.0.0",
37 | "redux-saga": "^1.1.3",
38 | "redux-thunk": "^2.3.0",
39 | "sass": "^1.25.0",
40 | "sass-loader": "^8.0.2",
41 | "whatwg-fetch": "^3.0.0",
42 | "yarn": "^1.22.0",
43 | "zrender": "^4.2.0"
44 | },
45 | "scripts": {
46 | "start": "react-scripts start",
47 | "build": "react-scripts build",
48 | "test": "react-scripts test",
49 | "eject": "react-scripts eject",
50 | "prebuild": "rm -rf ../VizWiz-VQA-PyTorch-master/templates/index.html && rm -rf ../VizWiz-VQA-PyTorch-master/static/build",
51 | "postbuild": "cp build/index.html ../VizWiz-VQA-PyTorch-master/templates/ && cp -r build ../VizWiz-VQA-PyTorch-master/static/"
52 | },
53 | "homepage": "/",
54 | "eslintConfig": {
55 | "extends": "react-app"
56 | },
57 | "browserslist": {
58 | "production": [
59 | ">0.2%",
60 | "not dead",
61 | "not op_mini all"
62 | ],
63 | "development": [
64 | "last 1 chrome version",
65 | "last 1 firefox version",
66 | "last 1 safari version"
67 | ]
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/server/nginx.conf:
--------------------------------------------------------------------------------
1 | #user nobody;
2 | worker_processes 4;
3 |
4 | #error_log logs/error.log;
5 | #error_log logs/error.log notice;
6 | #error_log logs/error.log info;
7 |
8 | #pid logs/nginx.pid;
9 |
10 |
11 | events {
12 | use epoll;
13 | #multi_accept on;
14 | worker_connections 1024;
15 | }
16 |
17 |
18 | http {
19 | include /usr/local/nginx/conf/mime.types;
20 | default_type application/octet-stream;
21 |
22 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
23 | # '$status $body_bytes_sent "$http_referer" '
24 | # '"$http_user_agent" "$http_x_forwarded_for"';
25 |
26 | #access_log logs/access.log main;
27 |
28 | sendfile on;
29 | #tcp_nopush on;
30 | keepalive_timeout 65;
31 | gzip on;
32 |
33 | #upstream myserver {
34 | #ip_hash;
35 | #server hducsrao.xyz:8880; # weight=1;
36 | #server hducsrao.xyz:8881 weight=1;
37 | #fair;
38 | #}
39 |
40 | server {
41 | # listen 80;
42 | listen 443 ssl;
43 | # listen 443 ssl http2 default_server;
44 | # listen [::]:443 ssl http2 default_server;
45 | server_name www.hducsrao.xyz;
46 | root /build;
47 |
48 | # charset koi8-r;
49 | # access_log logs/host.access.log main;
50 | client_max_body_size 20M;
51 |
52 | ssl_certificate "/usr/local/nginx/conf/1_www.hducsrao.xyz_bundle.crt";
53 | ssl_certificate_key "/usr/local/nginx/conf/2_www.hducsrao.xyz.key";
54 | ssl_session_cache shared:SSL:1m;
55 | ssl_session_timeout 10m;
56 | # ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
57 | ssl_ciphers HIGH:!aNULL:!MD5;
58 | ssl_prefer_server_ciphers on;
59 |
60 | location / {
61 | root /build;
62 | index index.html index.htm;
63 | try_files $uri /index.html;
64 |
65 | # rewrite .* /index.html break;
66 | # proxy_pass http://hducsrao.xyz:8001/;
67 | # include uwsgi_params;
68 | # uwsgi_pass 127.0.0.1:8089;
69 | # uwsgi_param UWSGI_CHDIR /home/colin;
70 | # uwsgi_param UWSGI_SCRIPT test_flask:app;
71 | # uwsgi_read_timeout 300;
72 | # uwsgi_connect_timeout 300;
73 | # uwsgi_send_timeout 300;
74 | }
75 |
76 | location ^~ /usr_login/ {
77 | # root /build;
78 | # try_files $uri /index.html;
79 | proxy_pass http://hducsrao.xyz:8001/login/;
80 | }
81 | location ^~ /usr_register/ {
82 | proxy_pass http://hducsrao.xyz:8001/register/;
83 | }
84 | location ^~ /usr_modify/ {
85 | proxy_pass http://hducsrao.xyz:8001/modify/;
86 | }
87 | location ^~ /usr_record/ {
88 | proxy_pass http://hducsrao.xyz:8001/record/;
89 | }
90 | location ^~ /api {
91 | proxy_pass http://hducsrao.xyz:8001/api/;
92 | }
93 |
94 | # location / {
95 | # root /build;
96 | # try_files $uri /index.html;
97 | # # rewrite .* /index.html break;
98 | # }
99 |
100 |
101 | # location ^~ /register/ {
102 | # #include uwsgi_params;
103 | # #uwsgi_pass 127.0.0.1:8001/register/;
104 | # #uwsgi_param UWSGI_CHDIR /home/colin/VizWiz-VQA-PyTorch-master;
105 | # #uwsgi_param UWSGI_SCRIPT vqa_web_api:app;
106 | # #uwsgi_read_timeout 300;
107 | # #uwsgi_connect_timeout 300;
108 | # #uwsgi_send_timeout 300;
109 | # #proxy_pass http://myserver/register/;
110 | # proxy_pass http://hducsrao.xyz:8001/register/;
111 | # proxy_read_timeout 300;
112 | # proxy_connect_timeout 300;
113 | # proxy_send_timeout 300;
114 | # }
115 |
116 | # location ^~ /question/ {
117 | # include uwsgi_params;
118 | # uwsgi_pass 127.0.0.1:8089;
119 | # uwsgi_param UWSGI_CHDIR /home/colin/VizWiz-VQA-PyTorch-master;
120 | # uwsgi_param UWSGI_SCRIPT Android_flaskWeb:app;
121 | # uwsgi_read_timeout 300;
122 | # uwsgi_connect_timeout 300;
123 | # uwsgi_send_timeout 300;
124 | # #proxy_pass http://hducsrao.xyz:8001/question/;
125 | # #proxy_read_timeout 240s;
126 | # #proxy_pass http://myserver/question/;
127 | # # include uwsgi_params;
128 | # # uwsgi_pass unix:/root/myproject/myproject.sock;
129 | # # uwsgi_pass http://127.0.0.1:8880;
130 | # }
131 |
132 | error_page 500 502 503 504 /50x.html;
133 | location = /50x.html {
134 | root html;
135 | }
136 | }
137 | }
--------------------------------------------------------------------------------
/server/vqa_web_api.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import json
3 | import os.path
4 | import time
5 | import numpy as np
6 | import torch
7 | import torch.backends.cudnn as cudnn
8 | import torch.nn as nn
9 | import yaml
10 | from torch.autograd import Variable
11 | from tqdm import tqdm
12 | from flask_cors import CORS
13 | from pyspark import SparkContext
14 | import models
15 | import predict_dataset
16 | import os
17 | import os.path
18 | import cv2
19 | import torch.utils.data as data
20 | import torchvision.transforms as transforms
21 | from PIL import Image
22 | from one_predict_forWeb import predict
23 | from flask import Flask, render_template, request, redirect, url_for, make_response,jsonify
24 | from werkzeug.utils import secure_filename
25 | import os
26 | from preprocessing import image_features_extraction as imf
27 | import time
28 | from datetime import timedelta,datetime
29 |
30 | """
31 | 模型预处理部分
32 | """
33 | # Load config yaml file
34 | parser = argparse.ArgumentParser()
35 | parser.add_argument('--path_config', default='config/default.yaml', type=str,
36 | help='path to a yaml config file')
37 | args = parser.parse_args()
38 |
39 | if args.path_config is not None:
40 | with open(args.path_config, 'r') as handle:
41 | config = yaml.load(handle, Loader=yaml.FullLoader)
42 |
43 | class ImageDataset(data.Dataset):
44 |
45 | def __init__(self, img_filename, img_filepath, transform=None): # , path
46 | # self.path = path
47 | self.transform = transform
48 | # Load the paths to the images available in the folder
49 | img_path = []
50 | self.filename = img_filename
51 | self.file_path = img_filepath
52 | img_path.append(self.file_path)
53 | self.image_names = img_path
54 |
55 | def __getitem__(self, index):
56 | item = {}
57 | item['name'] = self.filename
58 | item['path'] = self.file_path
59 | # item['path'] = os.path.join(self.path, item['name'])
60 |
61 | # 使用PIL加载图片数据再提取特征, Image.oopen后维度例如(3, 640, 722)
62 | item['visual'] = Image.open(item['path']).convert('RGB')
63 | if self.transform is not None:
64 | item['visual'] = self.transform(item['visual'])
65 | return item
66 |
67 | def __len__(self):
68 | return len(self.image_names)
69 |
70 | def get_path(self):
71 | return os.path.basename(self.file_path)
72 |
73 | def get_transform(img_size):
74 | return transforms.Compose([
75 | transforms.Resize(img_size),
76 | transforms.CenterCrop(img_size),
77 | transforms.ToTensor(),
78 | # TODO : Compute mean and std of VizWiz
79 | # ImageNet normalization
80 | transforms.Normalize(mean=[0.485, 0.456, 0.406],
81 | std=[0.229, 0.224, 0.225]),
82 | ])
83 |
84 | """
85 | Flask后端配置、数据库连接表
86 | """
87 | from flask_sqlalchemy import SQLAlchemy
88 | from flask import Blueprint
89 | import pymysql
90 | # from sqlalchemy.orm import sessionmaker
91 | pymysql.install_as_MySQLdb()
92 | app = Flask(__name__,template_folder="templates",static_folder="static",static_url_path="/VizWiz-VQA-PyTorch-master/static")
93 | # static_url_path:前端访问资源文件的前缀目录 static_folder:后端存储资源文件的目录。默认是/static,就是指明你后端的资源文件
94 | app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123@localhost:3306/vqa_web?charset=utf8'
95 | # app.config['SQLACHEMY_COMMIT_ON_TEARDOWN']=True # 自动提交
96 | db = SQLAlchemy(app) # 初始化 MySQL database
97 | import time # 格式化成2016-03-20 11:45:39形式:time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
98 |
99 | class Vqaer(db.Model):
100 | """ 创建用户注册登录模型,注意设置表的属性区分大小写 """
101 | __tablename__ = 'login_register'
102 | user = db.Column(db.String(50), nullable=False, primary_key=True)
103 | pwd = db.Column(db.String(50), nullable=False)
104 |
105 | class QandA(db.Model):
106 | """ username, date_time, question, answer 设置为主键的query只会返回一个记录 db.DateTime """
107 | __tablename__ = 'ques_ans'
108 | user = db.Column(db.String(50), nullable=False)
109 | date = db.Column(db.String(50), nullable=False, primary_key=True)
110 | vqa_ques = db.Column(db.String(50), nullable=False)
111 | vqa_ans = db.Column(db.String(50), nullable=False)
112 | interaction = db.Column(db.Float, nullable=False)
113 |
114 | # 单个对象方法
115 | def single_to_dict(self):
116 | return {c.name: getattr(self, c.name) for c in self.__table__.columns}
117 | # 多个对象
118 | def dobule_to_dict(self):
119 | result = {}
120 | for key in self.__mapper__.c.keys():
121 | if getattr(self, key) is not None:
122 | result[key] = str(getattr(self, key))
123 | else:
124 | result[key] = getattr(self, key)
125 | return result
126 |
127 | def to_json(self):
128 | dict = self.__dict__
129 | if "_sa_instance_state" in dict:
130 | del dict["_sa_instance_state"]
131 | return dict
132 |
133 | '''
134 | 路由所需函数
135 | '''
136 | def add_user(username, password): # 添加用户
137 | newuser = Vqaer(user=username, pwd=password)
138 | db.session.add(newuser)
139 | db.session.commit()
140 |
141 | def find_pwd(username):
142 | # 获取对应用户密码
143 | res = db.session.query(Vqaer).filter(Vqaer.user == username).all()
144 | password = ''
145 | # print(res)
146 | if res:
147 | password = res[0].pwd
148 | return password
149 |
150 | def modify_pwd(username, password):
151 | # 修改用户密码
152 | db.session.query(Vqaer).filter(Vqaer.user == username).update({"pwd": password})
153 | db.session.commit()
154 |
155 | # 先按条件查询再删除:session.query(QandA).filter(QandA.user == username).delete()
156 | # def to_json(all_vendors):
157 | # v = [ ven.dobule_to_dict() for ven in all_vendors ]
158 | # return v
159 |
160 | def find_record(username):
161 | # 获取对应用户密码
162 | res = db.session.query(QandA).filter(QandA.user == username).all() #返回的是object对象,注意这里以唯一的时间值作为主键而非用户名
163 | cnt = db.session.query(QandA).filter(QandA.user == username).count() #返回符合条件的记录条数
164 | # print(cnt, res)
165 | record = []
166 | if cnt == 0:
167 | return jsonify({'auth': False, 'msg': '当前用户没有记录!'})
168 | else:
169 | for item in res:
170 | record.append(item.to_json())
171 | # print(record)
172 | return jsonify({'auth': True, 'nums': cnt, 'rec': record}) # json.dumps
173 |
174 | '''
175 | 路由函数,与PC端Web网站的react-axios交互
176 | '''
177 | # @app.route('/', methods=['POST','GET'])
178 | # # React.js的入口文件,若把build放在服务器根目录下在配置则不用响应flask的入口文件
179 | # def index():
180 | # return render_template('index.html')
181 |
182 | @app.route('/login/', methods=['POST','GET'])
183 | def login():
184 | # request.args.get("username") 获取Get请求参数, request.form.get 获取post请求
185 | username = request.args.get("username")
186 | userpwd = request.args.get("password")
187 | # print(username, userpwd)
188 | pwd = find_pwd(username)
189 | if pwd == "":
190 | return jsonify({'auth': False, 'msg': '用户名尚未注册!'})
191 | if pwd != userpwd:
192 | return jsonify({'auth': False, 'msg': '请检查密码是否正确!'})
193 | else:
194 | return jsonify({'auth': True })
195 |
196 | @app.route('/register/', methods=['POST','GET'])
197 | def register():
198 | username = request.args.get("username")
199 | userpwd = request.args.get("password")
200 | if find_pwd(username) != '':
201 | return jsonify({'auth': False, 'msg': '账户已注册,请返回登录!'})
202 | else:
203 | add_user(username, userpwd)
204 | return jsonify({'auth': True})
205 |
206 | @app.route('/modify/', methods=['POST','GET'])
207 | def modify():
208 | username = request.args.get("username")
209 | userpwd = request.args.get("password")
210 | if find_pwd(username) == '':
211 | return jsonify({'auth': False, 'msg': '账户不存在,请先注册!'})
212 | if find_pwd(username) == userpwd:
213 | return jsonify({'auth': False, 'msg': '新密码与旧密码一致!'})
214 | else:
215 | modify_pwd(username, userpwd)
216 | return jsonify({'auth': True})
217 |
218 | @app.route('/record/', methods=['POST','GET'])
219 | def record():
220 | username = request.args.get("username")
221 | return find_record(username)
222 |
223 | '''
224 | VQA预测模型模块
225 | '''
226 | #设置允许的文件格式
227 | ALLOWED_EXTENSIONS = set(['png', 'jpg', 'JPG', 'PNG', 'bmp', 'jpeg'])
228 | def allowed_file(filename):
229 | return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
230 |
231 | @app.route('/api/', methods=['POST','GET'])
232 | def upload():
233 | a = datetime.now()
234 | nowtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
235 | # nowtime = time.strptime(nowtime, "%Y-%m-%d %H:%M:%S")
236 | question = request.form.get("ques") # 前端获取问题字符串,Base64编码图片可以上传到七牛云存储然后返回url链接
237 | username = request.form.get("username")
238 | print(username, nowtime, question)
239 |
240 | f = request.files['image']
241 | if not (f and allowed_file(f.filename)):
242 | return jsonify({"error": 1001, "msg": "请检查上传的图片类型"})
243 | p = os.path.dirname(__file__) # 当前文件所在路径
244 |
245 | # 图片路径用于PIL加载图片的RGB数据,图片名字无所谓
246 | basepath = os.path.join(p, 'static')
247 | img_filename = f.filename
248 |
249 | upload_path = os.path.join(p, 'static', secure_filename(f.filename))
250 | f.save(upload_path)
251 | # 使用Opencv转换一下图片格式和名称
252 | img = cv2.imread(upload_path)
253 | cv2.imwrite(os.path.join(p,'static', 'test.jpg'), img)
254 |
255 | #调用函数进行预测
256 | transform = get_transform(config['images']['img_size'])
257 | dataset = ImageDataset(img_filename, upload_path, transform=transform)
258 | att_fea, noatt_fea, img_namee = imf.feature(dataset)
259 | answer = predict(img_filename, question, att_fea, noatt_fea, img_namee)
260 |
261 | b = datetime.now()
262 | during = float((b-a).seconds)
263 | # 向 MySQL 添加用户问答记录
264 | newRecord = QandA(user=username, date=nowtime, vqa_ques=question, vqa_ans=answer, interaction=during)
265 | db.session.add(newRecord)
266 | db.session.commit()
267 |
268 | return jsonify({'res': answer})
269 | # return render_template('index.html')
270 | # return render_template('upload_ok.html',userinput1=question,userinput2=res,val1=time.time())
271 |
272 | if __name__ == '__main__':
273 | # app.debug = True 8880
274 | app.run(host='0.0.0.0', port=8880, debug=True) # 127.0.0.1:8987/predict 本地测试地址
275 |
276 |
277 |
278 | # if __name__ == '__main__':
279 | # img_name = get_feature()
280 | # res = predict(img_name)
281 | # return res
282 | # print(web_predict())
283 | # fig = {"image": str(img.tolist()).encode('base64')} img是ndarray,无法直接用base64编码,否则会报错,这行是处理本地的图片,不能用
284 | # 从客户端获取图片json数据,预处理从客户端获取的图片,比如我们的 /http://192.168.2.126:5000/predict
285 | # fig = request.form.get("img_name")
286 | # 获取推过来的json,也可以用data然后转换成json再使用 res = json.loads(request.data)
--------------------------------------------------------------------------------
/src/Router/RouterConfig.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {
3 | // Link,
4 | Switch,
5 | Route,
6 | BrowserRouter,
7 | Redirect
8 | } from 'react-router-dom'
9 | import Access from '../containers/vqa/access'
10 | import VQAUpload from '../containers/vqa/upload'
11 | import Login from '../containers/user/login'
12 | import Register from '../containers/user/register'
13 | import Modify from '../containers/user/modify'
14 | import Record from '../containers/record'
15 | import Introduce from '../containers/introduce'
16 | import MyCalendar from '../containers/datetime'
17 | import MyCharts from "../containers/echartsAnalysis";
18 | import {
19 | // createHashHistory,
20 | createBrowserHistory
21 | } from 'history'
22 | // 利用react-router-redux提供的syncHistoryWithStore我们可以结合store同步导航事件
23 | import { syncHistoryWithStore } from 'react-router-redux'
24 | import store from '../store/store'
25 |
26 | // const browserHistory = createHashHistory();
27 | // 使用 URL 中的 hash #部分去创建路由
28 | const browserHistory = createBrowserHistory();
29 | // hash不需要服务器配置,实际生产环境用browserHistory
30 | const history = syncHistoryWithStore(browserHistory, store)
31 | // 创建一个增强版的history来结合store同步导航事件
32 |
33 |
34 | class RouterConfig extends Component {
35 | componentDidMount() { // 组件渲染后调用
36 | document.title = 'BlindVQA'
37 | }
38 | render() {
39 | return(
40 | // basename='/build'
41 |
42 |
43 | (
44 |
45 | )}/>
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 | export default RouterConfig
--------------------------------------------------------------------------------
/src/actions/actionCreators.js:
--------------------------------------------------------------------------------
1 | import { INPUT_QUES,CLEAR_INPUT_QUES,SET_IMAGE_URL,SET_ANSWER,REGI_SUCCESS,AUTH_SUCCESS,MODI_SUCCESS,LOAD_DATA,ERROR_MSG,LOGOUT,CHANGE_MODE,CHANGE_THEME } from "../constant/constants";
2 | import axios from "axios";
3 |
4 | // 这些方法都返回一个action对象
5 | export const handleGetInputValue = ques =>({
6 | type: INPUT_QUES,
7 | ques
8 | })
9 |
10 | export const handleSetImageUrl = imgurl =>({
11 | type: SET_IMAGE_URL,
12 | imgurl
13 | })
14 |
15 | export const handleSetAnswer = ans =>({
16 | type: SET_ANSWER,
17 | ans
18 | })
19 |
20 | export const handleClearInques = () => ({
21 | type: CLEAR_INPUT_QUES
22 | })
23 |
24 | export const handleSetMode = data =>({
25 | type: CHANGE_MODE,
26 | payload: data
27 | })
28 |
29 | export const handleSetTheme = data =>({
30 | type: CHANGE_THEME,
31 | payload: data
32 | })
33 |
34 | // 注册登录模块,访问与Flask后端连接的MySQL数据库
35 | function authSuccess(obj){
36 | const {...data} = obj
37 | return {
38 | type: AUTH_SUCCESS,
39 | payload: data
40 | }
41 | }
42 | function regiSuccess(obj){
43 | const {pwd,...data} = obj
44 | return {
45 | type: REGI_SUCCESS,
46 | payload: data
47 | }
48 | }
49 | function modiSuccess(obj){
50 | const {pwd,...data} = obj
51 | return {
52 | type: MODI_SUCCESS,
53 | payload: data
54 | }
55 | }
56 | function errorMsg(msg){
57 | return {
58 | msg,
59 | type: ERROR_MSG
60 | }
61 | }
62 |
63 | export function loadData(obj){
64 | const { ...data } = obj
65 | console.log(data)
66 | return {
67 | type: LOAD_DATA,
68 | payload: data
69 | }
70 | }
71 |
72 | export function logoutSubmit(){
73 | return {
74 | type: LOGOUT
75 | }
76 | }
77 |
78 | //判断字符串密码是否为数字和字母的组合
79 | function checkPwd (str){
80 | var zg = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]*$/;
81 | if (!zg.test(str)) {
82 | return false;
83 | } else {
84 | return true;
85 | }
86 | }
87 |
88 | // 注册
89 | export function register({user,pwd,repeatpwd}){
90 | if(!user||!pwd){
91 | return errorMsg('用户名密码必须输入!')
92 | }
93 | if(pwd!==repeatpwd){
94 | return errorMsg('密码不一致!')
95 | }
96 | if(!checkPwd(pwd)){
97 | return errorMsg("密码必须只包含字母和数字!")
98 | }
99 | if(pwd.length<6){
100 | return errorMsg("密码不能少于六位数!")
101 | }
102 | return async dispatch=>{
103 | // let form_data = new FormData(); // 创建form对象
104 | // form_data.append('username', user);
105 | // form_data.append('password', pwd);
106 | // console.log(form_data)
107 | await axios.get(`/usr_register?username=${user}&password=${pwd}`
108 | // {
109 | // headers: {
110 | // 'Access-Control-Allow-Origin':'*', //解决cors头问题, 不同端口存在跨域问题,使用proxy代理
111 | // 'Access-Control-Allow-Credentials':'true', //解决session问题
112 | // 'Content-Type': 'application/form-data; charset=UTF-8'
113 | // },
114 | // withCredentials : true}
115 | ).then(res=>{
116 | if(res.data['auth']){ //res.status===200&&res.data.code===0
117 | dispatch(regiSuccess({user,pwd}))
118 | }else{
119 | dispatch(errorMsg(res.data['msg']))
120 | }
121 | }).catch( error => {
122 | dispatch(errorMsg('服务器未响应!'))
123 | });
124 | }
125 | }
126 |
127 | // 登录
128 | export function login({user,pwd}){
129 | if(!user||!pwd){
130 | return errorMsg('用户名密码必须输入!')
131 | }
132 | return async dispatch=>{
133 | await axios.get(`/usr_login?username=${user}&password=${pwd}`
134 | ).then(res=>{
135 | if(res.data['auth']){
136 | dispatch(authSuccess({user,pwd}))
137 | }else{
138 | dispatch(errorMsg(res.data['msg']))
139 | }
140 | }).catch( error => {
141 | dispatch(errorMsg('服务器未响应!'))
142 | });
143 | }
144 | }
145 |
146 | // 忘记密码?用户修改密码
147 | export function modify({user,pwd,repeatpwd}){
148 | if(!user||!pwd){
149 | return errorMsg('用户名密码必须输入!')
150 | }
151 | if(pwd!==repeatpwd){
152 | return errorMsg('密码不一致!')
153 | }
154 | if(!checkPwd(pwd)){
155 | return errorMsg("密码必须只包含字母和数字!")
156 | }
157 | if(pwd.length<6){
158 | return errorMsg("密码不能少于六位数!")
159 | }
160 | return async dispatch=>{
161 | await axios.get(`/usr_modify?username=${user}&password=${pwd}`
162 | ).then(res=>{
163 | if(res.data['auth']){
164 | dispatch(modiSuccess({user,pwd}))
165 | }else{
166 | dispatch(errorMsg(res.data['msg']))
167 | }
168 | }).catch( error => {
169 | dispatch(errorMsg('服务器未响应!'))
170 | });
171 | }
172 | }
173 |
174 | // 查询某用户使用记录
175 | export function search_by_user({user}){
176 | return async dispatch=>{
177 | await axios.get(`/usr_record?username=${user}`
178 | ).then(res=>{
179 | if(res.data['auth']){
180 | let nums = res.data['nums']
181 | let rec = res.data['rec']
182 | dispatch(loadData({nums,rec}))
183 | }
184 | else{
185 | dispatch(errorMsg(res.data['msg']))
186 | }
187 | }).catch( () => {
188 | dispatch(errorMsg('服务器响应出错 !'))
189 | });
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/assets/pic/add_picture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/add_picture.png
--------------------------------------------------------------------------------
/src/assets/pic/app_scan.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/app_scan.jpg
--------------------------------------------------------------------------------
/src/assets/pic/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/login.png
--------------------------------------------------------------------------------
/src/assets/pic/vqa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/vqa.png
--------------------------------------------------------------------------------
/src/assets/pic/wechat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/wechat.jpg
--------------------------------------------------------------------------------
/src/assets/scss/components/calendar.scss:
--------------------------------------------------------------------------------
1 | .box {
2 | position: relative;
3 | width: $mini-side-calendar;
4 | border: 1px solid #d9d9d9;
5 | border-radius: 4;
6 | }
--------------------------------------------------------------------------------
/src/assets/scss/components/foot.scss:
--------------------------------------------------------------------------------
1 | .foot {
2 | // flex: 1;
3 | height: 180px;
4 | // position: absolute;
5 | // bottom: 0;
6 | // width: 100%;
7 | color: $footer-info-text-color;
8 | background-color: $footer-background-color;
9 | display: flex;
10 | flex-direction: row;
11 | justify-content: space-between;
12 | align-items: center;
13 | &-app {
14 | width: 50%;
15 | display: flex;
16 | flex-direction: row;
17 | justify-content: space-between;
18 | align-items: center;
19 | &-left {
20 | width: 60%;
21 | text-align: right;
22 | }
23 | &-right {
24 | text-align: left;
25 | flex: 1;
26 | p {
27 | padding-left: 30px;
28 | }
29 | &-intro {
30 | color: RGB(169,184,202);
31 | }
32 | }
33 | }
34 | &-developer {
35 | text-align: left;
36 | flex: 1;
37 | border-left: 1px solid #646f7c;
38 | p {
39 | color: RGB(169,184,202);
40 | padding-left: 40px;
41 | a {
42 | color: RGB(169,184,202);
43 | &:hover {
44 | color: #ffffff;
45 | }
46 | }
47 | }
48 | &-link {
49 | color: RGB(169,184,202);
50 | &:hover {
51 | color: #ffffff;
52 | }
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/assets/scss/components/logo.scss:
--------------------------------------------------------------------------------
1 | .logo-container{
2 | margin-top: 50px;
3 | text-align: center;
4 | margin-bottom: 20px;
5 | img {
6 | width: 260px;
7 | height: 260px;
8 | border-radius: 50%;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page.scss:
--------------------------------------------------------------------------------
1 | @import './page/login';
2 | @import './page/register';
3 | @import './page/modify';
4 | @import './page/record';
5 | @import './page/introduce';
6 | @import './page/upload';
7 | @import './page/access';
8 | @import './page/charts';
9 | @import './page/datetime';
10 |
11 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page/access.scss:
--------------------------------------------------------------------------------
1 | .whole {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .sider-vqa2 {
7 | flex: 1;
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: space-between;
11 | }
12 |
13 | .vqa2 {
14 | /* display: inline-block; */
15 | /* position: absolute; */
16 | // float: right;
17 | // position: relative;
18 | flex: 1;
19 | display: flex;
20 | flex-direction: row;
21 | justify-content: space-between;
22 | text-align: center;
23 | margin-right: $access-margin-right;
24 | // width: $vqa-access-view;
25 | margin-top: $access-view-margin-top;
26 | color: green;
27 | &-empty {
28 | margin-left: $access-empty-margin-left;
29 | }
30 |
31 | .show_img {
32 | // display: inline-block;
33 | // position: relative;
34 | // position: absolute;
35 | width: $access-image-width;
36 | h2 {
37 | font-size: $access-image-title-font-size;
38 | }
39 | }
40 |
41 | .response {
42 | // float: right;
43 | // position: relative;
44 | // position: absolute;
45 | // text-align: center;
46 | flex: 1;
47 | width: $access-response-width;
48 | // color: green;
49 | margin-top: $access-response-margin-top;
50 | &-ans {
51 | background: $access-response-background;
52 | Paragraph {
53 | font-size: $access-response-ans-font-size;
54 | }
55 | }
56 | h3 {
57 | font-weight: bold;
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page/charts.scss:
--------------------------------------------------------------------------------
1 | .whole {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .sider-charts {
7 | flex: 1;
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: space-between;
11 | }
12 |
13 | .charts {
14 | // float: right;
15 | // position: relative;
16 | flex: 1;
17 | display: flex;
18 | flex-direction: row;
19 | justify-content: space-between;
20 | text-align: center;
21 | margin-right: $access-margin-right;
22 | // width: $vqa-access-view;
23 | margin-top: $user-echarts-margin-top;
24 |
25 | .echart-bar {
26 | // display: inline-block;
27 | //position: relative;
28 | // position: absolute;
29 | width: $access-image-width;
30 | }
31 |
32 | .echart-pie {
33 | // float: right;
34 | //position: relative;
35 | // position: absolute;
36 | //text-align: center;
37 | flex: 1;
38 | width: $access-response-width;
39 | }
40 | }
--------------------------------------------------------------------------------
/src/assets/scss/components/page/datetime.scss:
--------------------------------------------------------------------------------
1 | .whole {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .sider-calen {
7 | flex: 1;
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: space-between;
11 | }
12 |
13 | .calen {
14 | /* display: inline-block; */
15 | // float: right;
16 | // position: relative;
17 | flex: 1;
18 | text-align: center;
19 | margin-right: $date-calendar-margin-right;
20 | // width: $date-calendar-view;
21 | }
22 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page/introduce.scss:
--------------------------------------------------------------------------------
1 | .whole {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .sider-intro {
7 | flex: 1;
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: space-between;
11 | }
12 |
13 | .intro {
14 | /* display: inline-block; */
15 | // float: right;
16 | // position: relative;
17 | flex: 1;
18 | text-align: center;
19 | // width: $vqa-introduce-view;
20 | margin-right: $introduce-margin-right;
21 | &-title {
22 | font-weight: bold;
23 | font-size: $introduce-title-text-size;
24 | color: $introduce-title-text-color;
25 | }
26 | &-card {
27 | background: $introduce-card-background;
28 | padding: $introduce-card-padding;
29 | &-list {
30 | font-size: $introduce-list-font-size;
31 | /* text-align: center; */
32 | background: $introduce-ul-list-background;
33 | }
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page/login.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | text-align: center;
3 | color: black;
4 | // position: relative;
5 | // top: 0;
6 | // bottom: 0;
7 | // width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | .error-msg {
11 | @include error-msg();
12 | }
13 | &-auth {
14 | // position: absolute;
15 | // top: 0;
16 | // bottom: 0;
17 | // width: 100%;
18 | // height: 500;
19 | flex: 1;
20 | background-color: $login-auth-background-color;
21 | &-form {
22 | margin-right: $user-info-label-width;
23 | }
24 | &-preInput {
25 | text-align: right;
26 | padding-right: 10px;
27 | width: $user-info-label-width;
28 | display: inline-block;
29 | vertical-align: top;
30 | }
31 | }
32 | &-user {
33 | width: $user-info-input-width;
34 | }
35 | &-password {
36 | width: $user-info-input-width;
37 | }
38 | }
39 | .forget{
40 | &:hover {
41 | color: $login-hover-text-color;
42 | }
43 | }
44 | .required {
45 | color: #FF0000;
46 | }
47 |
48 | .login::before {
49 | @include login-background();
50 | }
51 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page/modify.scss:
--------------------------------------------------------------------------------
1 | .modify {
2 | text-align: center;
3 | color: black;
4 | // position: relative;
5 | // top: 0;
6 | // bottom: 0;
7 | // width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | &-auth {
11 | // position: absolute;
12 | // top: 0;
13 | // bottom: 0;
14 | //height: 500;
15 | flex: 1;
16 | // width: 100%;
17 | background-color: $login-auth-background-color;
18 | &-form {
19 | margin-right: $user-info-label-width;
20 | }
21 | &-preInput {
22 | text-align: right;
23 | padding-right: 10px;
24 | width: $user-info-label-width;
25 | display: inline-block;
26 | vertical-align: top;
27 | }
28 | }
29 | .error-msg {
30 | @include error-msg();
31 | }
32 | &-user {
33 | width: $user-info-input-width;
34 | }
35 | &-password {
36 | width: $user-info-input-width;
37 | }
38 | &-checkpwd {
39 | width: $user-info-input-width;
40 | }
41 | }
42 | .required {
43 | color: #FF0000;
44 | }
45 | .forget{
46 | &:hover {
47 | color: $login-hover-text-color;
48 | }
49 | }
50 |
51 | .modify::before {
52 | @include login-background();
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page/record.scss:
--------------------------------------------------------------------------------
1 | .whole {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .sider-reco {
7 | flex: 1;
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: space-between;
11 | }
12 |
13 | .reco {
14 | /* display: inline-block; */
15 | // float: right;
16 | // position: relative;
17 | flex: 1;
18 | text-align: center;
19 | margin-right: $record-margin-right;
20 | // width: $user-record-view;
21 | color: green;
22 | h1 {
23 | color: $record-title-text-color;
24 | }
25 | .error-msg {
26 | @include error-msg(10px);
27 | }
28 | }
29 |
30 | .search_input {
31 | width: 100%;
32 | }
33 |
34 | .search_button {
35 | width: 47.5%;
36 | }
37 |
38 | .reset_button {
39 | width: 47.5%;
40 | }
--------------------------------------------------------------------------------
/src/assets/scss/components/page/register.scss:
--------------------------------------------------------------------------------
1 | .register {
2 | text-align: center;
3 | color: black;
4 | // position: absolute;
5 | // top: 0;
6 | // bottom: 0;
7 | // width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | &-auth {
11 | // position: absolute;
12 | // top: 0;
13 | // bottom: 0;
14 | // width: 100%;
15 | flex: 1;
16 | // height: 500;
17 | background-color: $login-auth-background-color;
18 | &-form {
19 | margin-right: $user-info-label-width;
20 | }
21 | &-preInput {
22 | text-align: right;
23 | padding-right: 10px;
24 | width: $user-info-label-width;
25 | display: inline-block;
26 | vertical-align: top;
27 | }
28 | }
29 | .error-msg {
30 | @include error-msg();
31 | }
32 | &-user {
33 | width: $user-info-input-width;
34 | }
35 | &-password {
36 | width: $user-info-input-width;
37 | }
38 | &-checkpwd {
39 | width: $user-info-input-width;
40 | }
41 | }
42 | .required {
43 | color: #FF0000;
44 | }
45 |
46 | .register::before {
47 | @include login-background();
48 | }
49 |
--------------------------------------------------------------------------------
/src/assets/scss/components/page/upload.scss:
--------------------------------------------------------------------------------
1 | .whole {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .sider-vqa1 {
7 | flex: 1;
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: space-between;
11 | }
12 |
13 | .vqa1 {
14 | /* display: inline-block; */
15 | // float: right;
16 | // position: relative;
17 | flex: 1;
18 | text-align: center;
19 | margin-right: $upload-margin-right;
20 | // width: $vqa-upload-view;
21 | color: green;
22 | .h2 {
23 | font-size: $upload-title-size;
24 | font-weight: bold;
25 | }
26 | }
27 |
28 | #form1 {
29 | .upload_file {
30 | margin-top: $upload-image-margin-top;
31 | width: $upload-image-file;
32 |
33 | }
34 | .ques_input {
35 | margin-top: $upload-ques-margin-top;
36 | width: $upload-ques-width;
37 | background: #dddddd;
38 | border: none;
39 | font-size: 1.3em;
40 | font-weight: 700;
41 | outline: none;
42 | border-radius: 30px;
43 | }
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/assets/scss/components/sider.scss:
--------------------------------------------------------------------------------
1 | .sider {
2 | // display: inline-block;
3 | // position: relative;
4 | width: $side-whole-menu;
5 | /* height: 100%; */
6 | &-wrap {
7 | width: $side-wrap-view;
8 | &-content {
9 | width: $side-wrap-content;
10 | }
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/assets/scss/components/toptitle.scss:
--------------------------------------------------------------------------------
1 | .nav {
2 | // position: relative;
3 | height: $top-title-height;
4 | background: $top-title-background;
5 | &-title {
6 | // display: inline-block;
7 | /* float: left; */
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: space-between;
11 | &-name {
12 | text-align: center;
13 | width: $top-title-width;
14 | color: $top-title-text-color;
15 | line-height: $top-title-height; // 文字高度居中对齐
16 | font-size: $top-title-font-size;
17 | }
18 | &-link {
19 | // float: right;
20 | flex: 1;
21 | text-align: right;
22 | color: $top-title-text-color;
23 | line-height: $top-title-height;
24 | margin-right: $top-title-margin-right;
25 | font-size: $top-clock-font-size;
26 | a {
27 | color: $top-title-text-color;
28 | &:hover {
29 | color: $record-title-text-color;
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
36 | .modal {
37 | text-align: center
38 | }
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/assets/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import "./variables";
2 |
3 | //
4 | // All components and pages(containers)
5 | //
6 | @import "./components/page";
7 | @import "./components/calendar";
8 | @import "./components/logo";
9 | @import "./components/sider";
10 | @import "./components/toptitle";
11 | @import "./components/foot";
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/assets/scss/variables.scss:
--------------------------------------------------------------------------------
1 | //
2 | // define Variables or style function
3 | //
4 | // @extend .another_className 继承其它选择器风格
5 |
6 | @mixin error-msg($msg-padding-top: 0px) {
7 | color: $error-message-hint;
8 | padding-left: 10px;
9 | padding-top: $msg-padding-top;
10 | }
11 |
12 | @mixin login-background($background-pic: '', $img-opacity: 0.5) {
13 | // background-image: url( $background-pic ); '../pic/bg.png'
14 | background-color: $background-pic;
15 | background-size: cover;
16 | content: "";
17 | /*-webkit-filter: opacity(50%);
18 | filter: opacity(50%); */
19 | opacity: $img-opacity;
20 | z-index: -1;
21 | width: 100%;
22 | height: 100%;
23 | position: absolute;
24 | top: 0px;
25 | left: 0px;
26 | }
27 |
28 | // color
29 | $error-message-hint: #f50;
30 | $access-response-background: lightgoldenrodyellow;
31 | $top-title-background: RGB(61,68,76);
32 | $introduce-title-text-color: RGB(0,165,241);
33 | $introduce-ul-list-background: lightgoldenrodyellow;
34 | $introduce-card-background: #ECECEC;
35 | $record-title-text-color: MediumTurquoise;
36 | $wechat-mini-title-text-color: #000000;
37 | $login-auth-background-color: RGB(240,240,240);
38 | $footer-background-color: RGB(42,47,53);
39 | $footer-info-text-color: RGB(255,255,255);
40 | $top-title-link-ahover: #ffff00;
41 | $top-title-text-color: #ffffff;
42 | $login-hover-text-color: rgb(36, 190, 36);
43 |
44 | // font
45 | $upload-title-size: 1.6em;
46 | $top-clock-font-size: 1.3em;
47 | $top-title-font-size: 1.8em;
48 | $introduce-list-font-size: 1.3em;
49 | $access-response-ans-font-size: 1.1em;
50 | $access-image-title-font-size: 1.7em;
51 | $introduce-title-text-size: 1.6em;
52 | $login-foot-text-size: 1.2em;
53 |
54 | // height
55 | $top-title-height: 60px;
56 |
57 | // width
58 | $user-info-input-width: 290px; //20%;
59 | $user-info-label-width: 150px;
60 | $login-title-text-width: 370px;
61 |
62 | $vqa-introduce-view: 60%;
63 | $user-record-view: 64.6%;
64 |
65 | $upload-ques-width: 40%;
66 | $upload-image-file: 30%;
67 | $vqa-upload-view: 58.6%;
68 |
69 | $vqa-access-view: 63.6%;
70 | $access-image-width: 50%;
71 | $access-response-width: 50%;
72 |
73 | $side-whole-menu: 26.4%;
74 | $side-wrap-view: 100%;
75 | $side-wrap-content: 55%;
76 |
77 | $top-title-width: 70.5%;
78 | $date-calendar-view: 64%;
79 | $mini-side-calendar: 20%;
80 |
81 | $record-search-input: 100%;
82 | $record-search-button: 47.5%;
83 | $record-reset-button: 47.5%;
84 |
85 | $record-echarts-view: 73%;
86 |
87 | // margin
88 | $access-empty-margin-left: 28%;
89 |
90 | $record-margin-right: 9%;
91 | $upload-margin-right: 15%;
92 | $introduce-margin-right: 13.6%;
93 | $access-margin-right: 10%;
94 | $top-title-margin-right: 1%;
95 | $date-calendar-margin-right: 9.6%;
96 | $echarts-margin-right: 0.6%;
97 |
98 | $upload-image-margin-top: 20px;
99 | $upload-ques-margin-top: 10px;
100 | $top-clock-margin-top: 10px;
101 | $access-view-margin-top: 40px;
102 | $access-response-margin-top: 120px;
103 | $wechat-app-margin-top: 80px;
104 | $user-echarts-margin-top: 90px;
105 |
106 | // padding
107 | $introduce-card-padding: 30px;
108 |
109 |
--------------------------------------------------------------------------------
/src/assets/sodagreen.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/sodagreen.mp3
--------------------------------------------------------------------------------
/src/components/calendar.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import { Calendar } from 'antd'
3 |
4 | function onPanelChange(value, mode) {
5 | console.log(value, mode);
6 | }
7 |
8 | class SideCalendar extends Component {
9 | constructor(props) {
10 | super(props);
11 | }
12 | render() {
13 | return (
14 | // width: 300,
15 |
16 |
20 |
21 | );
22 | }
23 | }
24 |
25 | export default SideCalendar
26 |
--------------------------------------------------------------------------------
/src/components/foot.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {
3 | AUTHOR,
4 | FOOTER,
5 | GITHUB,
6 | ICP,
7 | APP_GITHUB,
8 | ICP_address,
9 | VQA
10 | } from "../constant/constants";
11 | import appScan from '../assets/pic/app_scan.jpg'
12 | import sodagreen from "../assets/sodagreen.mp3" //assets文件先import
13 | import {
14 | Icon,
15 | } from 'antd'
16 |
17 | class LOGINFOOT extends Component{
18 | componentDidMount() {
19 | var audio = document.getElementById('music');
20 | audio.pause();//打开页面时无音乐
21 | }
22 | play_audio = () => {
23 | var audio = document.getElementById('music');
24 | if (audio.paused) {
25 | audio.play();
26 | }else{
27 | audio.pause();
28 | // audio.currentTime = 0; //音乐从头播放
29 | }
30 | }
31 |
32 | render(){
33 | return (
34 |
35 |
36 |
37 |

42 |
43 |
44 |
微信扫一扫 BlindVQA 小程序
45 |
体验视觉问答的多模态智能交互
46 |
47 |
48 |
114 |
115 | )
116 | }
117 | }
118 |
119 | export default LOGINFOOT
120 |
121 |
--------------------------------------------------------------------------------
/src/components/logo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import LogoImg from '../assets/pic/vqa.png'
3 |
4 | class Logo extends React.Component{
5 | render(){
6 | return (
7 |
8 |

12 |
13 | )
14 | }
15 | }
16 |
17 | export default Logo
--------------------------------------------------------------------------------
/src/components/sider.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {
3 | Menu,
4 | Icon,
5 | // Switch
6 | } from 'antd';
7 | import {
8 | withRouter,
9 | Link
10 | } from 'react-router-dom'
11 | import { connect } from "react-redux"
12 | import PropTypes from 'prop-types'
13 | import {
14 | logoutSubmit,
15 | handleSetMode,
16 | handleSetTheme,
17 | search_by_user
18 | } from '../actions/actionCreators'
19 | // import SideCalendar from '../components/calendar'
20 | const { SubMenu } = Menu;
21 |
22 | class Sider extends Component {
23 | constructor(props) {
24 | super(props);
25 | this.state = {
26 | collapsed: false,
27 | };
28 | }
29 | componentDidMount() {
30 | if(document.body.clientWidth < 500 ) { //宽度不够时将导航栏折叠
31 | this.setState({
32 | collapsed: true
33 | })
34 | }
35 | }
36 |
37 | handleLogout = () => {
38 | this.props.logoutSubmit()
39 | }
40 | handleSearchRecord = () => {
41 | const { user } = this.props;
42 | this.props.search_by_user({'user': user})
43 | }
44 |
45 | handleChangeMode = value => {
46 | let nowmode = value ? 'vertical' : 'inline'
47 | this.props.handleSetMode({
48 | 'mode': nowmode
49 | })
50 | };
51 | handleChangeTheme = value => {
52 | let nowtheme = value ? 'dark' : 'light'
53 | this.props.handleSetTheme({
54 | 'theme': nowtheme
55 | })
56 | };
57 |
58 | render() {
59 | const {
60 | user,
61 | // mode,
62 | theme
63 | } = this.props;
64 | return (
65 |
66 |
67 | {/* 切换导航
68 | */}
69 | {/*
70 | 切换导航背景 */}
71 |
189 |
190 | {/*
*/}
191 |
192 | );
193 | }
194 | }
195 | Sider.propTypes = {
196 | user: PropTypes.string.isRequired,
197 | mode: PropTypes.string.isRequired,
198 | theme: PropTypes.string.isRequired
199 | }
200 |
201 | const mapStateToProps = (state) => {
202 | return {
203 | user: state.vqa_user.user,
204 | mode: state.side_menu.mode,
205 | theme: state.side_menu.theme
206 | }
207 | }
208 |
209 | export default withRouter(
210 | connect(
211 | mapStateToProps, {
212 | logoutSubmit,
213 | handleSetMode,
214 | handleSetTheme,
215 | search_by_user
216 | })(Sider)
217 | );
--------------------------------------------------------------------------------
/src/components/toptitle.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | Icon,
4 | Modal
5 | } from 'antd'
6 | // import { Link } from 'react-router-dom'
7 | import {
8 | APP_GITHUB,
9 | MINI_GITHUB,
10 | VQA
11 | } from '../constant/constants'
12 | import AppImg from '../assets/pic/wechat.jpg'
13 |
14 | class Toptitle extends React.Component{
15 | constructor(props){
16 | super(props);
17 | var time = new Date()
18 | function PrefixInteger(num, length) {
19 | // 规定从何处开始选取,如果是负数,那么从数组尾部开始算起的位置,-2指倒数第二个元素
20 | return (Array(length).join('0') + num).slice(-length);
21 | }
22 | this.state = { //先初始化再设置timer计时
23 | year: PrefixInteger(time.getFullYear(), 4),
24 | month: PrefixInteger(time.getMonth()+1, 2), // 1月为0
25 | day: PrefixInteger(time.getDate(), 2),
26 | hour : PrefixInteger(time.getHours(), 2),
27 | minute : PrefixInteger(time.getMinutes(), 2),
28 | second : PrefixInteger(time.getSeconds(), 2),
29 | visible: false
30 | }
31 | }
32 | componentDidMount(){
33 | this.timer = setInterval( ()=>this.GetTime(), 1000 ) // 每秒刷新一次时钟
34 | }
35 |
36 | GetTime = () => {
37 | var time = new Date()
38 | function PrefixInteger(num, length) {
39 | // 规定从何处开始选取,如果是负数,那么从数组尾部开始算起,-2指倒数第二个元素
40 | return (Array(length).join('0') + num).slice(-length);
41 | }
42 | this.setState({
43 | year: PrefixInteger(time.getFullYear(), 4),
44 | month: PrefixInteger(time.getMonth()+1, 2),
45 | day: PrefixInteger(time.getDate(), 2),
46 | hour : PrefixInteger(time.getHours(), 2),
47 | minute : PrefixInteger(time.getMinutes(), 2),
48 | second : PrefixInteger(time.getSeconds(), 2),
49 | })
50 | }
51 |
52 | showModal = () => {
53 | this.setState({
54 | visible: true,
55 | });
56 | };
57 |
58 | handleOk = e => {
59 | console.log(e);
60 | this.setState({
61 | visible: false,
62 | });
63 | };
64 | handleCancel = e => {
65 | this.setState({
66 | visible: false,
67 | });
68 | };
69 |
70 | render(){
71 | const {
72 | year,
73 | month,
74 | day,
75 | hour,
76 | minute,
77 | second
78 | } = this.state
79 | return (
80 |
81 |
82 |
83 | 面向盲人辅助的 BlindVQA 视觉问答系统
84 |
85 |
111 |
112 |
118 |
119 | 融合语音合成、语音识别和翻译,实现图像问答的多模态智能交互
120 |
124 |
128 |
129 |
130 |
131 |

138 |
139 |
140 |
141 | )
142 | }
143 | }
144 |
145 | export default Toptitle
146 |
147 |
148 |
149 | // class App extends React.Component {
150 | // state = {
151 | // current: 'mail',
152 | // };
153 |
154 | // handleClick = e => {
155 | // console.log('click ', e);
156 | // this.setState({
157 | // current: e.key,
158 | // });
159 | // };
160 |
161 | // render() {
162 | // return (
163 | //
195 | // );
196 | // }
197 | // }
--------------------------------------------------------------------------------
/src/constant/constants.js:
--------------------------------------------------------------------------------
1 | // action操作常量
2 | export const INPUT_QUES = 'INPUT_QUES'
3 | export const SET_IMAGE_URL = 'SET_IMAGE_URL'
4 | export const SET_ANSWER = 'SET_ANSWER'
5 | export const CLEAR_INPUT_QUES = 'CLEAR_INPUT_QUES'
6 |
7 | //登录页页脚和图标链接
8 | export const AUTHOR = '1085031736@qq.com'
9 | export const GITHUB = 'https://github.com/JerryWisdom/react-vqa-master'
10 | export const VQA = 'https://visualqa.org/'
11 | export const ICP = '赣ICP备20001362号-1'
12 | export const FOOTER = 'Copyright © 2020. ALL RIGHT RESERVED.'
13 | export const LOGIN_TITLE = '欢迎使用 BlindVQA 视觉问答系统 '
14 | export const APP_GITHUB = 'https://github.com/JerryWisdom/Multifunctional_APP_with_computer_vision'
15 | export const MINI_GITHUB = 'https://github.com/JerryWisdom/BlindVQA'
16 | export const ICP_address = 'http://www.beian.miit.gov.cn/state/outPortal/loginPortal.action'
17 |
18 | // 导航侧栏风格设置
19 | export const CHANGE_MODE = 'CHANGE_MODE'
20 | export const CHANGE_THEME = 'CHANGE_THEME'
21 |
22 | //用户信息操作,包括登录、注册、修改、访问记录
23 | export const REGI_SUCCESS= 'REGI_SUCCESS'
24 | export const AUTH_SUCCESS = 'AUTH_SUCCESS'
25 | export const MODI_SUCCESS = 'MODI_SUCCESS'
26 | export const LOAD_DATA = 'LOAD_DATA'
27 | export const ERROR_MSG = 'ERROR_MSG'
28 | export const LOGOUT = 'LOGOUT'
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/containers/datetime.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Calendar,
3 | // Alert
4 | } from 'antd';
5 | // import moment from 'moment';
6 | import React, {Component} from 'react'
7 | import { withRouter } from 'react-router-dom'
8 | import Sider from '../components//sider'
9 | import Toptitle from '../components/toptitle'
10 |
11 | class MyCalendar extends Component {
12 | onPanelChange = (value, mode) => {
13 | console.log(value, mode)
14 | };
15 |
16 | render() {
17 | return (
18 |
28 | );
29 | }
30 | }
31 |
32 | export default withRouter(MyCalendar)
--------------------------------------------------------------------------------
/src/containers/echartsAnalysis.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | withRouter,
4 | } from 'react-router-dom'
5 | import { connect }from "react-redux";
6 | import Toptitle from '../components/toptitle'
7 | import Sider from '../components/sider'
8 | import echarts from 'echarts'
9 | import PropTypes from 'prop-types'
10 |
11 | class MyCharts extends Component {
12 | componentDidMount() {
13 | const myBar = echarts.init(document.getElementById("bar"))
14 | const myPie = echarts.init(document.getElementById("pie"))
15 | const {
16 | user,
17 | rec,
18 | // nums
19 | } = this.props;
20 |
21 | var ques = ['what', 'what color', 'how many', 'yes/no', 'else']
22 | var ans = ['unanswerable', 'yes', 'no', 'black', 'blue', 'keyboard', 'else']
23 | var ques_cnt = [] // new Array()
24 | var ans_cnt = []
25 |
26 | function prepro_data(obj) {
27 | let what_cnt = 0;
28 | let what_color_cnt = 0;
29 | let how_many_cnt = 0;
30 | let is_not_cnt = 0;
31 | let else_ques_cnt = 0;
32 | let unanswer_cnt = 0;
33 | let yes_cnt = 0;
34 | let no_cnt = 0;
35 | let black_cnt = 0;
36 | let blue_cnt = 0;
37 | let keyboard_cnt = 0;
38 | let else_ans_cnt = 0;
39 |
40 | for(let item of rec) {
41 | console.log(item)
42 | let eachques = item.vqa_ques;
43 | let eachans = item.vqa_ans;
44 | if (eachques.includes('what is') || eachques.includes('What is')
45 | || eachques.includes('What\'s') || eachques.includes('what\'s')) {
46 | what_cnt = what_cnt + 1
47 | }
48 | else if (eachques.includes('what color') || eachques.includes('What color')) {
49 | what_color_cnt = what_color_cnt + 1
50 | }
51 | else if (eachques.includes('how many') || eachques.includes('How many')) {
52 | how_many_cnt = how_many_cnt + 1
53 | }
54 | else if (eachques.slice(0,2)==="Is" || eachques.slice(0,2)==="is"
55 | || eachques.slice(0,3)==="Are" || eachques.slice(0,3)==="are") {
56 | is_not_cnt = is_not_cnt + 1
57 | }
58 | else {
59 | else_ques_cnt = else_ques_cnt + 1
60 | }
61 | if (eachans.includes('unanswerable')) {
62 | unanswer_cnt = unanswer_cnt + 1
63 | }
64 | else if (eachans.includes('yes')) {
65 | yes_cnt = yes_cnt + 1
66 | }
67 | else if (eachans.includes('no')) {
68 | no_cnt = no_cnt + 1
69 | }
70 | else if (eachans.includes('black')) {
71 | black_cnt = black_cnt + 1
72 | }
73 | else if (eachans.includes('blue')) {
74 | blue_cnt = blue_cnt + 1
75 | }
76 | else if (eachans.includes('keyboard')) {
77 | keyboard_cnt = keyboard_cnt + 1
78 | }
79 | else {
80 | else_ans_cnt = else_ans_cnt + 1
81 | }
82 | }
83 | ques_cnt = [what_cnt, what_color_cnt, how_many_cnt, is_not_cnt, else_ques_cnt]
84 | ans_cnt = [unanswer_cnt, yes_cnt, no_cnt, black_cnt, blue_cnt, keyboard_cnt, else_ans_cnt]
85 | }
86 | prepro_data(rec)
87 |
88 | myBar.setOption({
89 | title: {
90 | text: `用户 ${user} VQA 历史回答分析`,
91 | // subtext: ''
92 | },
93 | tooltip: {
94 | trigger: 'axis'
95 | },
96 | // legend: {
97 | // data: ['answer']
98 | // },
99 | toolbox: {
100 | show: true,
101 | feature: {
102 | dataView: { show: true, readOnly: false },
103 | magicType:{ show: true, type:['line', 'bar'] },
104 | restore: { show: true },
105 | saveAsImage: { show: true }
106 | }
107 | },
108 | xAxis:[
109 | {
110 | type: 'category',
111 | data: ans
112 | }
113 | ],
114 | yAxis: [{
115 | type: 'value'
116 | }],
117 | series: [
118 | {
119 | name: 'answer',
120 | type: 'bar',
121 | data: ans_cnt
122 | }
123 | ]
124 | })
125 |
126 | var pie_data = []
127 | var index = 0
128 | for(let item of ques_cnt){
129 | let ques_obj = {}
130 | ques_obj['value'] = item
131 | ques_obj['name'] = ques[index]
132 | pie_data.push(ques_obj)
133 | index = index + 1
134 | }
135 | myPie.setOption({
136 | title: {
137 | text: `VQA 历史问题分析`,
138 | // subtext: '',
139 | x: 'center'
140 | },
141 | tooltip: {
142 | trigger: 'item',
143 | formatter: "{a} {b} : {c} ({d}%)"
144 | },
145 | toolbox: {
146 | show: true,
147 | feature: {
148 | dataView: { show: true, readOnly: false },
149 | restore: { show: true },
150 | saveAsImage: { show: true }
151 | }
152 | },
153 | legend: {
154 | orient: 'vertical',
155 | left: 'left',
156 | data: ques
157 | },
158 | series: [{
159 | name: 'question',
160 | type: 'pie',
161 | radius: '55%',
162 | center: ['50%', '60%'],
163 | data: pie_data,
164 | itemStyle: {
165 | emphasis: {
166 | shadowBlur: 10,
167 | shadowOffsetX: 0,
168 | shadowColor: 'rgba(0,0,0,0.5)'
169 | }
170 | }
171 | }]
172 | })
173 | }
174 |
175 | render() {
176 | return (
177 |
178 |
179 |
180 |
181 |
182 |
187 |
188 |
193 |
194 |
195 |
196 |
197 | );
198 | }
199 | }
200 | // 验证组件中的参数类型
201 | MyCharts.propTypes = {
202 | user: PropTypes.string.isRequired,
203 | rec: PropTypes.array.isRequired,
204 | nums: PropTypes.number.isRequired
205 | }
206 |
207 | const mapStateToProps = (state) => {
208 | return (state => state.vqa_user)
209 | }
210 |
211 | export default withRouter(
212 | connect(
213 | mapStateToProps,
214 | )
215 | (MyCharts)
216 | );
--------------------------------------------------------------------------------
/src/containers/introduce.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {
3 | withRouter,
4 | // Link
5 | } from 'react-router-dom'
6 | import {
7 | Tooltip,
8 | // Icon,
9 | Card,
10 | Col,
11 | Row
12 | } from 'antd'
13 | import Toptitle from '../components/toptitle'
14 | import Sider from '../components/sider'
15 | import 'antd/dist/antd.css'
16 |
17 | class Introduce extends Component {
18 |
19 | render () {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | BlindVQA 是帮助盲人与外界基于图片进行视觉交互问答的平台
29 |
30 | {/* 以卡片的形式呈现介绍 */}
31 |
32 |
33 |
34 |
35 |
36 |
40 | - React.js
41 |
42 |
43 |
47 | - react-router
48 |
49 |
50 |
54 | - Redux
55 |
56 |
57 |
61 | - axios后台交互
62 |
63 |
64 |
68 | - antd
69 |
70 |
71 |
75 | - Sass
76 |
77 |
78 |
82 | - Echarts
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
95 | - Flask框架
96 |
97 |
98 |
102 | - MySQL数据库
103 |
104 |
105 |
109 | - SQLAlchemy
110 |
111 |
112 |
116 | - Pytorch框架
117 |
118 |
119 |
123 | - 深度学习模型
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
136 | - Nginx代理服务器
137 |
138 |
139 |
143 | - Docker容器化
144 |
145 |
146 |
150 | - Frp内网渗透
151 |
152 |
153 |
157 | - uWSGI服务器
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | )
168 | }
169 | }
170 |
171 | export default withRouter(Introduce)
172 |
--------------------------------------------------------------------------------
/src/containers/record.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | withRouter,
4 | } from 'react-router-dom'
5 | import { connect } from "react-redux"
6 | import {
7 | Table,
8 | Input,
9 | message,
10 | Button,
11 | Icon
12 | } from 'antd'
13 | import { search_by_user } from '../actions/actionCreators'
14 | import PropTypes from 'prop-types'
15 | import Toptitle from '../components/toptitle'
16 | import Sider from '../components/sider'
17 | import Highlighter from 'react-highlight-words'
18 | import 'antd/dist/antd.css'
19 |
20 | class Record extends Component {
21 | constructor(props) {
22 | super(props);
23 | this.state = {
24 | data: [],
25 | loading: false,
26 | search: false,
27 | searchText: '',
28 | searchedColumn: '',
29 | }
30 | }
31 |
32 | // 列内查询并展示结果
33 | getColumnSearchProps = (dataIndex) => ({
34 | filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
35 |
36 | {
39 | this.searchInput = node;
40 | }}
41 | placeholder={`Search ${dataIndex}`}
42 | value={selectedKeys[0]}
43 | onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
44 | onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
45 | style={{ marginBottom: 8, display: 'block' }}
46 | // width: 188,
47 | />
48 |
59 |
67 |
68 | ),
69 | filterIcon: filtered => (
70 |
71 | ),
72 | onFilter: (value, record) =>
73 | record[dataIndex]
74 | .toString()
75 | .toLowerCase()
76 | .includes(value.toLowerCase()),
77 | onFilterDropdownVisibleChange: visible => {
78 | if (visible) {
79 | setTimeout(() => this.searchInput.select());
80 | }
81 | },
82 | render: text =>
83 | this.state.searchedColumn === dataIndex ? (
84 |
90 | ) : (
91 | text
92 | ),
93 | });
94 |
95 | // 列内查询操作
96 | handleSearch = (selectedKeys, confirm, dataIndex) => {
97 | confirm();
98 | this.setState({
99 | searchText: selectedKeys[0],
100 | searchedColumn: dataIndex,
101 | });
102 | };
103 |
104 | // 清楚查询操作
105 | handleReset = clearFilters => {
106 | clearFilters();
107 | this.setState({ searchText: '' });
108 | };
109 |
110 | // 查询后台数据库中用户访问记录
111 | handleRecordSearch = () => {
112 | this.setState({ loading: true });
113 | const {
114 | user,
115 | nums,
116 | rec,
117 | msg
118 | } = this.props
119 | this.props.search_by_user({'user': user})
120 | const pagination = { ...this.state.pagination };
121 | pagination.total = nums
122 | this.setState({
123 | loading: false,
124 | search: true,
125 | data: rec,
126 | pagination,
127 | });
128 | if(msg){
129 | message.error(msg)
130 | }
131 | }
132 |
133 | handleTableChange = (pagination, filters, sorter, extra) => { // Chrome-F12中打印信息
134 | console.log('params', pagination, filters, sorter, extra)
135 | }
136 |
137 | render () {
138 | const {
139 | user,
140 | nums
141 | } = this.props
142 | const columns = [
143 | {
144 | title: '名称',
145 | dataIndex: 'user', // 此处应和后端数据库表的对应列名一致
146 | key: 'user',
147 | width: '10%'
148 | },
149 | {
150 | title: '访问时间',
151 | dataIndex: 'date',
152 | key: 'date',
153 | sorter: (a,b) => {
154 | var date_a = new Date(a.date)
155 | var date_b = new Date(b.date)
156 | var da = date_a.getTime()
157 | var db = date_b.getTime() //日期字符串转换为时间戳排序
158 | return da > db ? 1 : -1;
159 | },
160 | width: '20%',
161 | defaultSortOrder: 'descend',
162 | ...this.getColumnSearchProps('date'),
163 | },
164 | {
165 | title: '问题',
166 | dataIndex: 'vqa_ques',
167 | key: 'vqa_ques',
168 | width: '39%',
169 | ...this.getColumnSearchProps('vqa_ques'),
170 | },
171 | {
172 | title: '回答',
173 | dataIndex: 'vqa_ans',
174 | key: 'vqa_ans',
175 | width: '16%',
176 | ...this.getColumnSearchProps('vqa_ans'),
177 | },
178 | {
179 | title: '响应时间(秒)',
180 | dataIndex: 'interaction',
181 | key: 'interaction',
182 | width: '15%',
183 | sorter: (a,b) => a.interaction-b.interaction,
184 | // sorter: true,
185 | defaultSortOrder: 'ascend'
186 | }
187 | ];
188 | return (
189 |
190 |
191 |
192 |
193 |
194 |
195 | 用户 {user} 的访问记录
196 |
202 |
203 | { this.state.search ? `共 ${nums} 条` : null }
204 |
205 |
`${range[0]}-${range[1]} of ${nums} items`}
212 | />
213 |
214 |
215 |
216 | )
217 | }
218 | }
219 | // 验证组件中的参数类型
220 | Record.propTypes = {
221 | user: PropTypes.string.isRequired,
222 | rec: PropTypes.array.isRequired,
223 | nums: PropTypes.number.isRequired,
224 | msg: PropTypes.string.isRequired
225 | }
226 |
227 | const mapStateToProps = (state) => {
228 | return (state => state.vqa_user)
229 | }
230 |
231 | export default withRouter(connect(
232 | mapStateToProps,
233 | { search_by_user }
234 | )(Record)
235 | )
--------------------------------------------------------------------------------
/src/containers/user/login.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import Logo from '../../components/logo'
3 | import {
4 | Input,
5 | Button,
6 | Icon,
7 | message,
8 | Checkbox,
9 | //List,Form,Card,Switch,Radio,Mentions,slider,Select,Cascader,TreeSlectDataPicker,Transfer
10 | } from 'antd'
11 | import {
12 | LOGIN_TITLE
13 | } from '../../constant/constants'
14 | import {
15 | withRouter,
16 | Link
17 | } from 'react-router-dom'
18 | import { connect } from 'react-redux'
19 | import { login } from '../../actions/actionCreators'
20 | import LOGINFOOT from "../../components/foot"
21 | import PropTypes from 'prop-types'
22 |
23 | //设置cookie
24 | function setCookie(name, value, day) {
25 | var date = new Date();
26 | date.setDate(date.getDate() + day);
27 | document.cookie = name + '=' + value + ';expires=' + date;
28 | }
29 | //获取cookie
30 | function getCookie(name) {
31 | var reg = RegExp(name + '=([^;]+)');
32 | var arr = document.cookie.match(reg);
33 | if (arr) {
34 | return arr[1];
35 | } else {
36 | return '';
37 | }
38 | }
39 | //删除cookie
40 | function delCookie(name) {
41 | setCookie(name, null, -1);
42 | }
43 |
44 | class Login extends Component{
45 | constructor(props){
46 | super(props)
47 | this.state = {
48 | 'user': '',
49 | 'pwd': '',
50 | ischecked: false
51 | }
52 | this.register = this.register.bind(this)
53 | this.handleLogin = this.handleLogin.bind(this)
54 | this.handleChange1 = this.handleChange1.bind(this)
55 | this.handleChange2 = this.handleChange2.bind(this)
56 | }
57 | onCheckboxChange = (e) => {
58 | if (!e.target.checked) {
59 | delCookie('username');
60 | delCookie('password');
61 | }
62 | else {
63 | this.setState({
64 | ischecked: true
65 | })
66 | }
67 | }
68 |
69 | register(){
70 | this.props.history.push('/register')
71 | }
72 | handleLogin(){
73 | const { redirectTo } = this.props
74 | const { user, pwd, ischecked } = this.state
75 | this.props.login(this.state)
76 | if( redirectTo && redirectTo!=='/login') {
77 | message.success(`登录成功,当前用户为 ${this.props.user}`)
78 | this.props.history.push(redirectTo)
79 | if(ischecked) {
80 | setCookie('username', user, 7);
81 | setCookie('password', pwd, 7); //保存密码到cookie,有效期7天
82 | }
83 | }
84 | }
85 | handleChange1(val) {
86 | if(!val){
87 | document.getElementById('username').innerHTML = getCookie('username')
88 | this.setState({
89 | 'user': getCookie('username')
90 | })
91 | }
92 | else{
93 | this.setState({
94 | 'user': val
95 | })
96 | }
97 | }
98 | handleChange2(val) {
99 | if(!val){
100 | document.getElementById('password').innerHTML = getCookie('password')
101 | this.setState({
102 | 'pwd': getCookie('password')
103 | })
104 | }
105 | else{
106 | this.setState({
107 | 'pwd': val
108 | })
109 | }
110 | }
111 |
112 | render(){
113 | const { msg } = this.props
114 | return (
115 |
116 |
117 |
118 |
119 | { LOGIN_TITLE }
120 |
121 | {/* label为表单标签,用for绑定输入框的id,再在css中设置宽度和左/右对齐
122 | 还可以设置tr内嵌td制表对齐内容,
170 |
171 |
172 | )
173 | }
174 | }
175 | // 验证组件中的参数类型
176 | Login.propTypes = {
177 | redirectTo: PropTypes.string.isRequired,
178 | msg: PropTypes.string.isRequired,
179 | user: PropTypes.string.isRequired,
180 | pwd: PropTypes.string.isRequired
181 | }
182 |
183 | export default withRouter(connect(
184 | state=>state.vqa_user,
185 | {login}
186 | )(Login))
187 |
188 |
189 |
190 | // 如果使用Form组件登录
191 | // const FormItem = Form.Item;
192 | // class FormLogin extends React.Component{
193 | // handleSubmit = ()=>{
194 | // let userInfo = this.props.form.getFieldsValue();
195 | // this.props.form.validateFields((err,values)=>{
196 | // if(!err){
197 | // message.success(`${userInfo.userName}欢迎您 ,当前密码为:${userInfo.userPwd}`)
198 | // }
199 | // })
200 | // }
201 | // render(){
202 | // const { getFieldDecorator } = this.props.form;
203 | // return (
204 | //
205 | //
206 | //
255 | //
256 | //
257 | // );
258 | // }
259 | // }
260 | // export default Form.create()(FormLogin);
--------------------------------------------------------------------------------
/src/containers/user/modify.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import Logo from '../../components/logo'
3 | import {
4 | List,
5 | Input,
6 | Button,
7 | Icon
8 | } from 'antd'
9 | import { connect } from 'react-redux'
10 | import {
11 | withRouter,
12 | Link
13 | } from 'react-router-dom'
14 | import {
15 | LOGIN_TITLE,
16 | } from '../../constant/constants'
17 | import { modify } from '../../actions/actionCreators'
18 | import LOGINFOOT from "../../components/foot"
19 | import PropTypes from 'prop-types'
20 |
21 | class Modify extends Component{
22 | constructor(props){
23 | super(props);
24 | this.state = {
25 | 'user': '',
26 | 'pwd': '',
27 | 'repeatpwd': ''
28 | }
29 | this.handleModify = this.handleModify.bind(this)
30 | this.login = this.login.bind(this)
31 | this.handleChange1 = this.handleChange1.bind(this)
32 | this.handleChange2 = this.handleChange2.bind(this)
33 | this.handleChange3 = this.handleChange3.bind(this)
34 | }
35 |
36 | login(){
37 | this.props.history.push('/login')
38 | }
39 | handleModify(){
40 | // console.log(this.state)
41 | this.props.modify(this.state)
42 | }
43 |
44 | handleChange1(val) { //不是箭头函数就需要声明.bind(this)
45 | this.setState({
46 | 'user': val
47 | })
48 | }
49 | handleChange2(val) {
50 | this.setState({
51 | 'pwd': val
52 | })
53 | }
54 | handleChange3(val) {
55 | this.setState({
56 | 'repeatpwd': val
57 | })
58 | }
59 |
60 | render(){
61 | const { msg } = this.props
62 | return (
63 |
64 |
65 | {/* {redirectTo && redirectTo!=='/login' ?
:null} */}
66 |
67 |
68 | { LOGIN_TITLE }
69 |
70 | { msg ?
{ msg }
: null }
71 |
72 |
73 | }
75 | id='username'
76 | className='modify-user'
77 | placeholder="请输入用户名"
78 | allowClear
79 | onChange={v=>this.handleChange1(v.target.value)}
80 | />
81 |
82 |
83 |
84 | }
86 | id='password'
87 | className='modify-password'
88 | placeholder="请输入新密码"
89 | allowClear
90 | onChange={v=>this.handleChange2(v.target.value)}
91 | />
92 |
93 |
94 |
95 | }
97 | id='password'
98 | className='modify-checkpwd'
99 | placeholder="请确认新密码"
100 | allowClear
101 | onChange={v=>this.handleChange3(v.target.value)}
102 | />
103 |
104 | {/*
*/}
105 |
111 |
112 |
113 |
114 | 返回注册
115 |
116 |
117 |
118 |
124 |
125 |
126 |
127 |
128 | )
129 | }
130 | }
131 | // 验证组件中的参数类型
132 | Modify.propTypes = {
133 | redirectTo: PropTypes.string.isRequired,
134 | msg: PropTypes.string.isRequired,
135 | user: PropTypes.string.isRequired,
136 | pwd: PropTypes.string.isRequired
137 | }
138 |
139 | export default withRouter(connect(
140 | state => state.vqa_user,
141 | {modify}
142 | )(Modify))
--------------------------------------------------------------------------------
/src/containers/user/register.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import Logo from '../../components/logo'
3 | import {
4 | List,
5 | Input,
6 | Button,
7 | Icon
8 | } from 'antd'
9 | import { connect } from 'react-redux'
10 | import {
11 | withRouter
12 | } from 'react-router-dom'
13 | import {
14 | LOGIN_TITLE,
15 | } from '../../constant/constants'
16 | import { register } from '../../actions/actionCreators'
17 | import LOGINFOOT from "../../components/foot"
18 | import PropTypes from 'prop-types'
19 |
20 | class Register extends Component{
21 | constructor(props){
22 | super(props);
23 | this.state = {
24 | 'user': '',
25 | 'pwd': '',
26 | 'repeatpwd': ''
27 | }
28 | this.handleRegister = this.handleRegister.bind(this)
29 | this.login = this.login.bind(this)
30 | this.handleChange1 = this.handleChange1.bind(this)
31 | this.handleChange2 = this.handleChange2.bind(this)
32 | this.handleChange3 = this.handleChange3.bind(this)
33 | }
34 | login(){
35 | this.props.history.push('/login')
36 | }
37 | handleRegister(){
38 | // console.log(this.state)
39 | this.props.register(this.state)
40 | }
41 | handleChange1(val) {
42 | this.setState({
43 | 'user': val
44 | })
45 | }
46 | handleChange2(val) {
47 | this.setState({
48 | 'pwd': val
49 | })
50 | }
51 | handleChange3(val) {
52 | this.setState({
53 | 'repeatpwd': val
54 | })
55 | }
56 |
57 | render(){
58 | const { msg } = this.props
59 | return (
60 |
61 |
62 |
63 |
64 | { LOGIN_TITLE }
65 |
66 | { msg ?
{ msg }
: null }
67 |
68 |
69 | }
71 | id='username'
72 | className='register-user'
73 | placeholder="请输入用户名"
74 | allowClear
75 | onChange={v=>this.handleChange1(v.target.value)}
76 | />
77 |
78 |
79 |
80 | }
82 | id='password'
83 | className='register-password'
84 | placeholder="请输入密码"
85 | allowClear
86 | onChange={v=>this.handleChange2(v.target.value)}
87 | />
88 |
89 |
90 |
91 | }
93 | id='password'
94 | className='register-checkpwd'
95 | placeholder="请确认密码"
96 | allowClear
97 | onChange={v=>this.handleChange3(v.target.value)}
98 | />
99 |
100 | {/*
*/}
101 |
107 |
108 |
114 |
115 |
116 |
117 |
118 | )
119 | }
120 | }
121 | // 验证组件中的参数类型
122 | Register.propTypes = {
123 | redirectTo: PropTypes.string.isRequired,
124 | msg: PropTypes.string.isRequired,
125 | user: PropTypes.string.isRequired,
126 | pwd: PropTypes.string.isRequired
127 | }
128 |
129 | export default withRouter(connect(
130 | state => state.vqa_user,
131 | {register}
132 | )(Register))
--------------------------------------------------------------------------------
/src/containers/vqa/access.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {
3 | withRouter
4 | } from 'react-router-dom'
5 | import { connect } from 'react-redux'
6 | import PropTypes from 'prop-types'
7 | import {
8 | Result,
9 | Icon,
10 | Typography,
11 | Rate,
12 | // message
13 | } from 'antd'
14 | import Toptitle from '../../components/toptitle'
15 | import Sider from '../../components/sider'
16 | const { Paragraph } = Typography;
17 |
18 | const desc = ['terrible', 'bad', 'normal', 'good', 'wonderful'];
19 |
20 | class Access extends Component {
21 | constructor(props) {
22 | super(props);
23 | this.state = {
24 | value: 3,
25 | };
26 | }
27 | handleChange = value => {
28 | this.setState({
29 | value
30 | });
31 | };
32 |
33 | render () {
34 | const {
35 | InputValue,
36 | answer,
37 | image_url
38 | } = this.props
39 | const { value } = this.state
40 | const success = (
41 |
42 |
43 |
44 |
48 | 已上传的图片
49 |
50 |

57 |
58 |
59 |
60 |
61 | 针对图片提出的问题:
62 |
66 | {InputValue}
67 |
68 |
69 |
70 | 回答:
71 |
75 | {answer}
76 |
77 |
78 |
79 |
80 | 请给结果评分吧~
81 |
82 |
83 |
88 | {value ? {desc[value - 1]} : ''}
89 |
90 |
91 |
92 | )
93 | const failure = (
94 |
95 |
101 |
102 | )
103 | return (
104 |
105 |
106 |
107 |
108 |
109 | {/* 选择的视图要被完整div包围,margin-right是div外的,right是距离右内边距离 */}
110 | { answer ? success : failure }
111 |
112 |
113 | )
114 | }
115 | }
116 | // 验证组件中的参数类型
117 | Access.propTypes = {
118 | InputValue: PropTypes.string.isRequired,
119 | image_url: PropTypes.string.isRequired,
120 | answer: PropTypes.string.isRequired
121 | }
122 |
123 | const getElements = (state) => {
124 | return {
125 | InputValue: state.update.InputValue,
126 | answer: state.update.answer,
127 | image_url: state.update.image_url
128 | }
129 | }
130 |
131 | export default withRouter(
132 | connect(getElements)(Access)
133 | )
134 |
135 |
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/src/containers/vqa/upload.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import axios from 'axios' //前端ajax插件
3 | import { withRouter } from 'react-router-dom'
4 | import { connect } from "react-redux"
5 | import {
6 | // Upload,
7 | Input,
8 | Button,
9 | Icon,
10 | Spin,
11 | message,
12 | // Modal
13 | } from 'antd'
14 | import {
15 | handleGetInputValue,
16 | handleSetImageUrl,
17 | handleSetAnswer,
18 | handleClearInques
19 | } from '../../actions/actionCreators'
20 | import PropTypes from 'prop-types'
21 | import Sider from '../../components/sider'
22 | import Toptitle from '../../components/toptitle'
23 | import 'antd/dist/antd.css'
24 |
25 | class VQAUpload extends Component {
26 | constructor(props) {
27 | super(props);
28 | this.state = {
29 | loading: false
30 | }
31 | }
32 | handleInputQues = (e) => {
33 | const { handleGetInputValue } = this.props
34 | handleGetInputValue(e.target.value)
35 | }
36 | handleClear = () => {
37 | const { handleClearInques } = this.props
38 | handleClearInques()
39 | }
40 |
41 | handleGetImage = () => {
42 | var file = document.getElementById("upload_image").files[0];
43 | var reads = new FileReader()
44 | reads.readAsDataURL(file); //把文件对象读成base64,读完直接放到src中
45 | const { handleSetImageUrl } = this.props
46 | reads.onload = (e) => {
47 | document.getElementById('img').src = e.target.result
48 | handleSetImageUrl(e.target.result)
49 | };
50 | }
51 |
52 | handlePost = () => {
53 | var _this = this;
54 | const {
55 | user,
56 | handleSetAnswer
57 | } = this.props;
58 | let q = document.getElementById("ques").value
59 | let i = document.getElementById("upload_image").files[0]
60 | if(q && !i) {
61 | message.error("未选择图片!")
62 | }
63 | else if(i && !q) {
64 | message.error("未输入问题!")
65 | }
66 | else if(!i && !q) {
67 | message.error("请先选择图片并输入问题!")
68 | }
69 | else {
70 | var iq_form = document.getElementById("form1")
71 | var form_data = new FormData(iq_form)
72 | form_data.append("username", user)
73 | this.setState({
74 | loading: true
75 | })
76 | message.loading('图片和问题已上传,请稍后~'); //message.info
77 | axios.post('/api', form_data, {
78 | headers: {
79 | 'Access-Control-Allow-Origin':'*', //解决cors头问题, 不同端口存在跨域问题,使用proxy代理
80 | 'Access-Control-Allow-Credentials':'true', //解决session问题
81 | 'Content-Type': 'application/form-data; charset=UTF-8' //将表单数据传递转化为form-data类型
82 | },
83 | withCredentials : true
84 | }).then(response => {
85 | handleSetAnswer(response.data['res'])
86 | _this.props.history.push({
87 | pathname: '/access' // state: { ... } 跳转组件用this.props.location.state.xxx获取
88 | })
89 | message.success("获取问答结果成功~")
90 | }).catch( error => {
91 | // alert(error)
92 | console.log(error)
93 | message.error("服务器出错!")
94 | this.setState({
95 | loading: false
96 | })
97 | });
98 | }
99 | }
100 |
101 | render () {
102 | const {
103 | InputValue,
104 | image_url,
105 | // answer
106 | } = this.props
107 | const { loading } = this.state
108 | return (
109 |
110 |
111 |
112 |
113 |
114 |
115 | 点击拍照或上传本地图片
116 |
117 |
155 |
161 |
162 |
168 |
169 | { InputValue && image_url && loading ?
: null }
170 |
171 |
172 |
173 | )
174 | }
175 | }
176 | // 验证组件中的参数类型
177 | VQAUpload.propTypes = {
178 | InputValue: PropTypes.string.isRequired,
179 | image_url: PropTypes.string.isRequired,
180 | answer: PropTypes.string.isRequired
181 | }
182 |
183 | const mapStateToProps = (state) => { // (state => state.update)
184 | return {
185 | InputValue: state.update.InputValue,
186 | image_url: state.update.image_url,
187 | answer: state.update.answer,
188 | user: state.vqa_user.user
189 | }
190 | }
191 |
192 | export default withRouter(
193 | connect(
194 | mapStateToProps, {
195 | handleGetInputValue,
196 | handleSetImageUrl,
197 | handleClearInques,
198 | handleSetAnswer
199 | }
200 | )(VQAUpload)
201 | )
202 |
203 |
204 | // ant design:UI组件参考 https://ant.design/docs/react/introduce-cn
205 | // react-redux详细文档:https://www.redux.org.cn/docs/react-redux/api.html
206 | // connect第一个参数建立一个从外部的state对象到UI组件的props对象的映射关系
207 | // URL.createObjectURL(file)得到本地内存容器的URL地址,方便预览,多次使用需要注意手动释放内存的问题,性能优秀
208 | // var URL = window.URL || window.webkitURL; // 如果当前document消失,比如页面被刷新,URL就失效了
209 | // _this.setState({
210 | // image_url: URL.createObjectURL(file)
211 | // })
212 | // FileReader.readAsDataURL(file)胜在直接转为base64格式,可以直接用于业务,无需二次转换格式
213 | // 注意将 px 换算成各层内外 div 的包含比例,以适应页面伸缩
214 |
215 | // import { bindActionCreators } from 'redux'
216 | // const mapDispatchToProps = (dispatch) => {
217 | // return {
218 | // handleGetInputValue: bindActionCreators(handleGetInputValue, dispatch),
219 | // handleSetImageUrl: bindActionCreators(handleSetImageUrl, dispatch),
220 | // handleSetAnswer: bindActionCreators(handleSetAnswer, dispatch)
221 | // }
222 | // }
223 | // const params = {
224 | // name: 'image',
225 | // onChange: this.handleGetImage.bind(this)
226 | // } ...params
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.scss';
4 | import * as serviceWorker from './serviceWorker';
5 | import RouterConfig from './Router/RouterConfig'
6 | import 'antd/dist/antd.css';
7 | import { Provider } from "react-redux";
8 | import store from './store/store'
9 | import './assets/scss/index.scss'
10 |
11 | import { persistStore } from 'redux-persist'
12 | import { PersistGate } from 'redux-persist/integration/react'
13 | const persistor = persistStore(store)
14 |
15 | ReactDOM.render(
16 |
17 |
21 |
22 |
23 | ,
24 | document.getElementById('root')
25 | );
26 |
27 | serviceWorker.unregister();
28 |
29 | // If you want your app to work offline and load faster, you can change unregister() to register() below.
30 | // Note this comes with some pitfalls. Learn more about service workers: https://bit.ly/CRA-PWA
31 |
32 | // 在项目目录下,运行npm run build,进行打包,打包完成后会在目录下生成一个build文件夹,build生成的这些东西要放在服务器root下
33 | // 部署的时候你可以把build里的文件直接放到服务器的根路径下,比如,你的服务器IP是47.96.134.256,应用服务器端口为8080,
34 | // 这样就保证http://47.96.134.256:8080这种访问方式,访问到的是你的build下的文件。
35 | // 如果你希望以http://47.96.134.256:8080/build/index.htm这种方式访问应用
36 | // 那么你可以在package.json文件中增加一个homepage字段:"homepage": "." 或 "/"
37 |
38 |
--------------------------------------------------------------------------------
/src/index.scss:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | min-width: 1300px; //这样缩小或打开调试页面不影响界面显示
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
5 | -webkit-font-smoothing: antialiased;
6 | -moz-osx-font-smoothing: grayscale;
7 | }
8 |
9 | code {
10 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
11 | }
--------------------------------------------------------------------------------
/src/redux/menu.js:
--------------------------------------------------------------------------------
1 | import {
2 | CHANGE_MODE,
3 | CHANGE_THEME
4 | } from "../constant/constants";
5 |
6 | const initState={
7 | mode: 'inline', //inline
8 | theme: 'light'
9 | }
10 |
11 | const side_menu = (state=initState, action) => {
12 | switch(action.type){
13 | case CHANGE_MODE:
14 | return {...state, ...action.payload }
15 | case CHANGE_THEME:
16 | return {...state, ...action.payload }
17 | default:
18 | return state
19 | }
20 | }
21 |
22 | export default side_menu
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/redux/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux' // 利用combineReducers 合并reducers
2 | import { routerReducer } from 'react-router-redux' // 将routerReducer一起合并管理
3 | import { persistReducer } from 'redux-persist' //使redux和store管理的数据持久化,刷新页面数据不丢失
4 | import storage from 'redux-persist/lib/storage'
5 |
6 | import update from './update'
7 | import vqa_user from './user'
8 | import side_menu from './menu'
9 |
10 | const combine_reducer = combineReducers({
11 | update,
12 | side_menu,
13 | vqa_user,
14 | routing: routerReducer
15 | })
16 |
17 | const reducer = persistReducer({
18 | key: 'root',
19 | storage
20 | }, combine_reducer)
21 |
22 | export default reducer
--------------------------------------------------------------------------------
/src/redux/update.js:
--------------------------------------------------------------------------------
1 | import {
2 | INPUT_QUES,
3 | CLEAR_INPUT_QUES,
4 | SET_IMAGE_URL,
5 | SET_ANSWER
6 | } from '../constant/constants';
7 |
8 | // 初始化state数据
9 | const initialState = {
10 | InputValue: "",
11 | image_url: "",
12 | answer: ""
13 | }
14 |
15 | // 通过dispatch action进入
16 | const update = (state = initialState, action) => {
17 | // 根据不同的action type进行state的更新
18 | switch(action.type) {
19 | case INPUT_QUES:
20 | return Object.assign({}, state, { InputValue: action.ques })
21 | case SET_IMAGE_URL:
22 | return Object.assign({}, state, { image_url: action.imgurl })
23 | case SET_ANSWER:
24 | return Object.assign({}, state, { answer: action.ans })
25 | case CLEAR_INPUT_QUES:
26 | return Object.assign({}, state, { InputValue: '' })
27 | default:
28 | return state
29 | }
30 | }
31 |
32 | export default update
--------------------------------------------------------------------------------
/src/redux/user.js:
--------------------------------------------------------------------------------
1 | import {
2 | REGI_SUCCESS,
3 | MODI_SUCCESS,
4 | AUTH_SUCCESS,
5 | LOAD_DATA,
6 | ERROR_MSG,
7 | LOGOUT
8 | } from "../constant/constants";
9 |
10 | const initState={
11 | redirectTo:'/login',
12 | msg:'',
13 | user:'',
14 | pwd:'',
15 | nums: 0,
16 | rec: []
17 | }
18 |
19 | const vqa_user = (state=initState, action) => {
20 | switch(action.type){
21 | case AUTH_SUCCESS:
22 | return {...state, msg:'', redirectTo:'/upload', ...action.payload} //后面的为...state重新部分赋值
23 | case REGI_SUCCESS:
24 | return {...state, msg:'注册成功,请返回登录!', ...action.payload}
25 | case MODI_SUCCESS:
26 | return {...state, msg:'修改密码成功,请返回登录!', ...action.payload}
27 | case ERROR_MSG:
28 | return {...state, msg:action.msg}
29 | case LOGOUT:
30 | return {...initState, redirectTo:'/login', pwd:''}
31 | case LOAD_DATA:
32 | return {...state, msg:'', ...action.payload}
33 | default:
34 | return state
35 | }
36 | }
37 |
38 | export default vqa_user
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready.then(registration => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | const proxy = require('http-proxy-middleware') // 代理中间件
2 | // 第一个参数为要代理的路由,第二参数中target为代理后的请求网址,changeOrigin是否改变请求头
3 |
4 | const Nginx_https_deploy = 'https://hducsrao.xyz:8880'
5 | // const Nginx_https_deploy = 'http://localhost:8880'
6 |
7 | module.exports = function(app) {
8 | app.use(
9 | proxy('/api', {
10 | target: Nginx_https_deploy,
11 | changeOrigin:true,
12 | pathRewrite:{
13 | '^/api': '/'
14 | }
15 | })
16 | );
17 | app.use(
18 | proxy('/usr_login', {
19 | target: Nginx_https_deploy,
20 | changeOrigin:true,
21 | })
22 | );
23 | app.use(
24 | proxy('/usr_register', {
25 | target: Nginx_https_deploy,
26 | changeOrigin:true,
27 | })
28 | );
29 | app.use(
30 | proxy('/usr_modify', {
31 | target: Nginx_https_deploy,
32 | changeOrigin:true,
33 | })
34 | );
35 | app.use(
36 | proxy('/usr_record', {
37 | target: Nginx_https_deploy,
38 | changeOrigin:true,
39 | })
40 | )
41 | }
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import {
2 | createStore,
3 | applyMiddleware,
4 | compose
5 | } from 'redux'
6 | import { routerMiddleware } from 'react-router-redux'
7 | import thunk from 'redux-thunk'
8 | // import createLogger from 'redux-logger'
9 | import reducer from '../redux/reducers' // 将reducer作为参数传给createStore创建store仓库
10 | import {
11 | // createHashHistory,
12 | createBrowserHistory
13 | } from 'history'
14 |
15 | // const browserHistory = createHashHistory();
16 | const browserHistory = createBrowserHistory();
17 |
18 | let routerWare = routerMiddleware(browserHistory);
19 |
20 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
21 | const store = createStore(
22 | reducer,
23 | composeEnhancers(
24 | applyMiddleware(
25 | thunk,
26 | routerWare
27 | )
28 | )
29 | );
30 |
31 | export default store;
--------------------------------------------------------------------------------